4 * The filter/plugin API is located in this file, which allows for creating filters
\r
5 * and hooking functions, and methods. The functions or methods will be run when
\r
6 * the filter is called.
\r
8 * Any of the syntaxes explained in the PHP documentation for the
\r
9 * {@link http://us2.php.net/manual/en/language.pseudo-types.php#language.types.callback 'callback'}
\r
12 * This API is heavily inspired by the one I implemented in Zenphoto 1.3, which was heavily inspired by the one used in WordPress.
\r
18 $yourls_filters = array();
\r
19 /* This global var will collect filters with the following structure:
\r
20 * $yourls_filters['hook']['array of priorities']['serialized function names']['array of ['array (functions, accepted_args)]']
\r
24 * Registers a filtering function
\r
27 * yourls_add_filter('some_hook', 'function_handler_for_hook');
\r
29 * @global array $yourls_filters Storage for all of the filters
\r
30 * @param string $hook the name of the YOURLS element to be filtered or YOURLS action to be triggered
\r
31 * @param callback $function_name the name of the function that is to be called.
\r
32 * @param integer $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default=10, lower=earlier execution, and functions with the same priority are executed in the order in which they were added to the filter)
\r
33 * @param int $accepted_args optional. The number of arguments the function accept (default is the number provided).
\r
35 function yourls_add_filter( $hook, $function_name, $priority = 10, $accepted_args = NULL ) {
\r
36 global $yourls_filters;
\r
37 // At this point, we cannot check if the function exists, as it may well be defined later (which is OK)
\r
38 $id = yourls_filter_unique_id( $hook, $function_name, $priority );
\r
40 $yourls_filters[$hook][$priority][$id] = array(
\r
41 'function' => $function_name,
\r
42 'accepted_args' => $accepted_args,
\r
47 * Hooks a function on to a specific action.
\r
49 * Actions are the hooks that YOURLS launches at specific points
\r
50 * during execution, or when specific events occur. Plugins can specify that
\r
51 * one or more of its PHP functions are executed at these points, using the
\r
54 * @param string $hook The name of the action to which the $function_to_add is hooked.
\r
55 * @param callback $function_name The name of the function you wish to be called.
\r
56 * @param int $priority optional. Used to specify the order in which the functions associated with a particular action are executed (default: 10). Lower numbers correspond with earlier execution, and functions with the same priority are executed in the order in which they were added to the action.
\r
57 * @param int $accepted_args optional. The number of arguments the function accept (default 1).
\r
59 function yourls_add_action( $hook, $function_name, $priority = 10, $accepted_args = 1 ) {
\r
60 return yourls_add_filter( $hook, $function_name, $priority, $accepted_args );
\r
66 * Build Unique ID for storage and retrieval.
\r
68 * Simply using a function name is not enough, as several functions can have the same name when they are enclosed in classes.
\r
70 * @global array $yourls_filters storage for all of the filters
\r
71 * @param string $hook hook to which the function is attached
\r
72 * @param string|array $function used for creating unique id
\r
73 * @param int|bool $priority used in counting how many hooks were applied. If === false and $function is an object reference, we return the unique id only if it already has one, false otherwise.
\r
74 * @param string $type filter or action
\r
75 * @return string unique ID for usage as array key
\r
77 function yourls_filter_unique_id( $hook, $function, $priority ) {
\r
78 global $yourls_filters;
\r
80 // If function then just skip all of the tests and not overwrite the following.
\r
81 if ( is_string($function) )
\r
83 // Object Class Calling
\r
84 else if (is_object($function[0]) ) {
\r
85 $obj_idx = get_class($function[0]).$function[1];
\r
86 if ( !isset($function[0]->_yourls_filters_id) ) {
\r
87 if ( false === $priority )
\r
89 $count = isset($yourls_filters[$hook][$priority]) ? count((array)$yourls_filters[$hook][$priority]) : 0;
\r
90 $function[0]->_yourls_filters_id = $count;
\r
94 $obj_idx .= $function[0]->_yourls_filters_id;
\r
98 else if ( is_string($function[0]) )
\r
99 return $function[0].$function[1];
\r
104 * Performs a filtering operation on a YOURLS element or event.
\r
108 * 1) Modify a variable if a function is attached to hook 'yourls_hook'
\r
109 * $yourls_var = "default value";
\r
110 * $yourls_var = yourls_apply_filter( 'yourls_hook', $yourls_var );
\r
112 * 2) Trigger functions is attached to event 'yourls_event'
\r
113 * yourls_apply_filter( 'yourls_event' );
\r
114 * (see yourls_do_action() )
\r
116 * Returns an element which may have been filtered by a filter.
\r
118 * @global array $yourls_filters storage for all of the filters
\r
119 * @param string $hook the name of the YOURLS element or action
\r
120 * @param mixed $value the value of the element before filtering
\r
123 function yourls_apply_filter( $hook, $value = '' ) {
\r
124 global $yourls_filters;
\r
125 if ( !isset( $yourls_filters[$hook] ) )
\r
128 $args = func_get_args();
\r
130 // Sort filters by priority
\r
131 ksort( $yourls_filters[$hook] );
\r
133 // Loops through each filter
\r
134 reset( $yourls_filters[$hook] );
\r
136 foreach( (array) current($yourls_filters[$hook]) as $the_ )
\r
137 if ( !is_null($the_['function']) ){
\r
139 $count = $the_['accepted_args'];
\r
140 if (is_null($count)) {
\r
141 $value = call_user_func_array($the_['function'], array_slice($args, 1));
\r
143 $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $count));
\r
147 } while ( next($yourls_filters[$hook]) !== false );
\r
152 function yourls_do_action( $hook, $arg = '' ) {
\r
154 if ( is_array($arg) && 1 == count($arg) && isset($arg[0]) && is_object($arg[0]) ) // array(&$this)
\r
155 $args[] =& $arg[0];
\r
158 for ( $a = 2; $a < func_num_args(); $a++ )
\r
159 $args[] = func_get_arg($a);
\r
161 yourls_apply_filter( $hook, $args );
\r
166 * Removes a function from a specified filter hook.
\r
168 * This function removes a function attached to a specified filter hook. This
\r
169 * method can be used to remove default functions attached to a specific filter
\r
170 * hook and possibly replace them with a substitute.
\r
172 * To remove a hook, the $function_to_remove and $priority arguments must match
\r
173 * when the hook was added.
\r
175 * @global array $yourls_filters storage for all of the filters
\r
176 * @param string $hook The filter hook to which the function to be removed is hooked.
\r
177 * @param callback $function_to_remove The name of the function which should be removed.
\r
178 * @param int $priority optional. The priority of the function (default: 10).
\r
179 * @param int $accepted_args optional. The number of arguments the function accepts (default: 1).
\r
180 * @return boolean Whether the function was registered as a filter before it was removed.
\r
182 function yourls_remove_filter( $hook, $function_to_remove, $priority = 10, $accepted_args = 1 ) {
\r
183 global $yourls_filters;
\r
185 $function_to_remove = yourls_filter_unique_id($hook, $function_to_remove, $priority);
\r
187 $remove = isset ($yourls_filters[$hook][$priority][$function_to_remove]);
\r
189 if ( $remove === true ) {
\r
190 unset ($yourls_filters[$hook][$priority][$function_to_remove]);
\r
191 if ( empty($yourls_filters[$hook][$priority]) )
\r
192 unset ($yourls_filters[$hook]);
\r
199 * Check if any filter has been registered for a hook.
\r
201 * @global array $yourls_filters storage for all of the filters
\r
202 * @param string $hook The name of the filter hook.
\r
203 * @param callback $function_to_check optional. If specified, return the priority of that function on this hook or false if not attached.
\r
204 * @return int|boolean Optionally returns the priority on that hook for the specified function.
\r
206 function yourls_has_filter( $hook, $function_to_check = false ) {
\r
207 global $yourls_filters;
\r
209 $has = !empty($yourls_filters[$hook]);
\r
210 if ( false === $function_to_check || false == $has ) {
\r
213 if ( !$idx = yourls_filter_unique_id($hook, $function_to_check, false) ) {
\r
217 foreach ( (array) array_keys($yourls_filters[$hook]) as $priority ) {
\r
218 if ( isset($yourls_filters[$hook][$priority][$idx]) )
\r
224 function yourls_has_action( $hook, $function_to_check = false ) {
\r
225 return yourls_has_filter( $hook, $function_to_check );
\r
229 * Return number of active plugins
\r
231 * @return integer Number of activated plugins
\r
233 function yourls_has_active_plugins( ) {
\r
236 if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins )
\r
237 $ydb->plugins = array();
\r
239 return count( $ydb->plugins );
\r
244 * List plugins in /user/plugins
\r
246 * @global $ydb Storage of mostly everything YOURLS needs to know
\r
247 * @return array Array of [/plugindir/plugin.php]=>array('Name'=>'Ozh', 'Title'=>'Hello', )
\r
249 function yourls_get_plugins( ) {
\r
252 $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php');
\r
257 foreach( $plugins as $key=>$plugin ) {
\r
258 $_plugin = yourls_plugin_basename( $plugin );
\r
259 $plugins[ $_plugin ] = yourls_get_plugin_data( $plugin );
\r
260 unset( $plugins[ $key ] );
\r
267 * Check if a plugin is active
\r
269 * @param string $file Physical path to plugin file
\r
272 function yourls_is_active_plugin( $plugin ) {
\r
273 if( !yourls_has_active_plugins( ) )
\r
277 $plugin = yourls_plugin_basename( $plugin );
\r
279 return in_array( $plugin, $ydb->plugins );
\r
284 * Parse a plugin header
\r
286 * @param string $file Physical path to plugin file
\r
287 * @return array Array of 'Field'=>'Value' from plugin comment header lines of the form "Field: Value"
\r
289 function yourls_get_plugin_data( $file ) {
\r
290 $fp = fopen( $file, 'r' ); // assuming $file is readable, since yourls_load_plugins() filters this
\r
291 $data = fread( $fp, 8192 ); // get first 8kb
\r
294 // Capture all the header within first comment block
\r
295 if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) )
\r
298 // Capture each line with "Something: some text"
\r
300 $lines = explode( "\n", $matches[1] );
\r
303 $plugin_data = array();
\r
304 foreach( $lines as $line ) {
\r
305 if( !preg_match( '!(.*?):\s*(.*)!', $line, $matches ) )
\r
308 list( $null, $field, $value ) = array_map( 'trim', $matches);
\r
309 $plugin_data[ $field ] = $value;
\r
312 return $plugin_data;
\r
315 // Include active plugins
\r
316 function yourls_load_plugins() {
\r
318 $ydb->plugins = array();
\r
319 $active_plugins = yourls_get_option( 'active_plugins' );
\r
321 // Don't load plugins when installing or updating
\r
322 if( !$active_plugins OR ( defined( 'YOURLS_INSTALLING' ) AND YOURLS_INSTALLING ) OR yourls_upgrade_is_needed() )
\r
325 foreach( (array)$active_plugins as $key=>$plugin ) {
\r
326 if( yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$plugin ) ) {
\r
327 include_once( YOURLS_PLUGINDIR.'/'.$plugin );
\r
328 $ydb->plugins[] = $plugin;
\r
329 unset( $active_plugins[$key] );
\r
333 // $active_plugins should be empty now, if not, a plugin could not be find: remove it
\r
334 if( count( $active_plugins ) ) {
\r
335 $missing = '<strong>'.join( '</strong>, <strong>', $active_plugins ).'</strong>';
\r
336 yourls_update_option( 'active_plugins', $ydb->plugins );
\r
337 $message = 'Could not find and deactivated '. yourls_plural( 'plugin', count( $active_plugins ) ) .' '. $missing;
\r
338 yourls_add_notice( $message );
\r
343 * Check if a file is safe for inclusion (well, "safe", no guarantee)
\r
345 * @param string $file Full pathname to a file
\r
347 function yourls_validate_plugin_file( $file ) {
\r
349 false !== strpos( $file, '..' )
\r
351 false !== strpos( $file, './' )
\r
353 'plugin.php' !== substr( $file, -10 ) // a plugin must be named 'plugin.php'
\r
355 !is_readable( $file )
\r
363 * Activate a plugin
\r
365 * @param string $plugin Plugin filename (full or relative to plugins directory)
\r
366 * @return mixed string if error or true if success
\r
368 function yourls_activate_plugin( $plugin ) {
\r
370 $plugin = yourls_plugin_basename( $plugin );
\r
371 $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
\r
372 if( !yourls_validate_plugin_file( $plugindir.'/'.$plugin ) )
\r
373 return 'Not a valid plugin file';
\r
375 // check not activated already
\r
377 if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) )
\r
378 return 'Plugin already activated';
\r
380 // attempt activation. TODO: uber cool fail proof sandbox like in WP.
\r
382 include( YOURLS_PLUGINDIR.'/'.$plugin );
\r
383 if ( ob_get_length() > 0 ) {
\r
384 // there was some output: error
\r
385 $output = ob_get_clean();
\r
386 return 'Plugin generated expected output. Error was: <br/><pre>'.$output.'</pre>';
\r
389 // so far, so good: update active plugin list
\r
390 $ydb->plugins[] = $plugin;
\r
391 yourls_update_option( 'active_plugins', $ydb->plugins );
\r
392 yourls_do_action( 'activated_plugin', $plugin );
\r
393 yourls_do_action( 'activated_' . $plugin );
\r
399 * Dectivate a plugin
\r
401 * @param string $plugin Plugin filename (full relative to plugins directory)
\r
402 * @return mixed string if error or true if success
\r
404 function yourls_deactivate_plugin( $plugin ) {
\r
405 $plugin = yourls_plugin_basename( $plugin );
\r
407 // Check plugin is active
\r
408 if( !yourls_is_active_plugin( $plugin ) )
\r
409 return 'Plugin not active';
\r
411 // Deactivate the plugin
\r
413 $key = array_search( $plugin, $ydb->plugins );
\r
414 if( $key !== false ) {
\r
415 array_splice( $ydb->plugins, $key, 1 );
\r
418 yourls_update_option( 'active_plugins', $ydb->plugins );
\r
419 yourls_do_action( 'deactivated_plugin', $plugin );
\r
420 yourls_do_action( 'deactivated_' . $plugin );
\r
426 * Return the path of a plugin file, relative to the plugins directory
\r
428 function yourls_plugin_basename( $file ) {
\r
429 $file = yourls_sanitize_filename( $file );
\r
430 $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
\r
431 $file = str_replace( $plugindir, '', $file );
\r
432 return trim( $file, '/' );
\r