4 * The filter/plugin API is located in this file, which allows for creating filters
5 * and hooking functions, and methods. The functions or methods will be run when
6 * the filter is called.
8 * Any of the syntaxes explained in the PHP documentation for the
9 * {@link http://us2.php.net/manual/en/language.pseudo-types.php#language.types.callback 'callback'}
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.
18 if( !isset( $yourls_filters ) )
19 $yourls_filters = array();
20 /* This global var will collect filters with the following structure:
21 * $yourls_filters['hook']['array of priorities']['serialized function names']['array of ['array (functions, accepted_args, filter or action)]']
24 if( !isset( $yourls_actions ) )
25 $yourls_actions = array();
26 /* This global var will collect 'done' actions with the following structure:
27 * $yourls_actions['hook'] => number of time this action was done
31 * Registers a filtering function
34 * yourls_add_filter('some_hook', 'function_handler_for_hook');
36 * @global array $yourls_filters Storage for all of the filters
37 * @param string $hook the name of the YOURLS element to be filtered or YOURLS action to be triggered
38 * @param callback $function_name the name of the function that is to be called.
39 * @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)
40 * @param int $accepted_args optional. The number of arguments the function accept (default is the number provided).
43 function yourls_add_filter( $hook, $function_name, $priority = 10, $accepted_args = NULL, $type = 'filter' ) {
44 global $yourls_filters;
45 // At this point, we cannot check if the function exists, as it may well be defined later (which is OK)
46 $id = yourls_filter_unique_id( $hook, $function_name, $priority );
48 $yourls_filters[ $hook ][ $priority ][ $id ] = array(
49 'function' => $function_name,
50 'accepted_args' => $accepted_args,
56 * Hooks a function on to a specific action.
58 * Actions are the hooks that YOURLS launches at specific points
59 * during execution, or when specific events occur. Plugins can specify that
60 * one or more of its PHP functions are executed at these points, using the
63 * @param string $hook The name of the action to which the $function_to_add is hooked.
64 * @param callback $function_name The name of the function you wish to be called.
65 * @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.
66 * @param int $accepted_args optional. The number of arguments the function accept (default 1).
68 function yourls_add_action( $hook, $function_name, $priority = 10, $accepted_args = 1 ) {
69 return yourls_add_filter( $hook, $function_name, $priority, $accepted_args, 'action' );
75 * Build Unique ID for storage and retrieval.
77 * Simply using a function name is not enough, as several functions can have the same name when they are enclosed in classes.
79 * @global array $yourls_filters storage for all of the filters
80 * @param string $hook hook to which the function is attached
81 * @param string|array $function used for creating unique id
82 * @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.
83 * @return string unique ID for usage as array key
85 function yourls_filter_unique_id( $hook, $function, $priority ) {
86 global $yourls_filters;
88 // If function then just skip all of the tests and not overwrite the following.
89 if ( is_string( $function ) ) {
93 if( is_object($function) ) {
94 // Closures are currently implemented as objects
95 $function = array( $function, '' );
97 $function = (array) $function;
100 // Object Class Calling
101 if ( is_object( $function[0] ) ) {
102 return spl_object_hash( $function[0] ) . $function[1];
106 if ( is_string( $function[0] ) ) {
107 return $function[0]. '::' .$function[1];
113 * Performs a filtering operation on a YOURLS element or event.
117 * 1) Modify a variable if a function is attached to hook 'yourls_hook'
118 * $yourls_var = "default value";
119 * $yourls_var = yourls_apply_filter( 'yourls_hook', $yourls_var );
121 * 2) Trigger functions is attached to event 'yourls_event'
122 * yourls_apply_filter( 'yourls_event' );
123 * (see yourls_do_action() )
125 * Returns an element which may have been filtered by a filter.
127 * @global array $yourls_filters storage for all of the filters
128 * @param string $hook the name of the YOURLS element or action
129 * @param mixed $value the value of the element before filtering
132 function yourls_apply_filter( $hook, $value = '' ) {
133 global $yourls_filters;
134 if ( !isset( $yourls_filters[ $hook ] ) )
137 $args = func_get_args();
139 // Sort filters by priority
140 ksort( $yourls_filters[ $hook ] );
142 // Loops through each filter
143 reset( $yourls_filters[ $hook ] );
145 foreach( (array) current( $yourls_filters[ $hook ] ) as $the_ ) {
146 if ( !is_null( $the_['function'] ) ){
148 $count = $the_['accepted_args'];
149 if ( is_null( $count ) ) {
150 $_value = call_user_func_array( $the_['function'], array_slice( $args, 1 ) );
152 $_value = call_user_func_array( $the_['function'], array_slice( $args, 1, (int) $count ) );
155 if( $the_['type'] == 'filter' )
159 } while ( next( $yourls_filters[ $hook ] ) !== false );
161 if( $the_['type'] == 'filter' )
166 * Performs an action triggered by a YOURLS event.
168 * @param string $hook the name of the YOURLS action
169 * @param mixed $arg action arguments
171 function yourls_do_action( $hook, $arg = '' ) {
172 global $yourls_actions;
174 // Keep track of actions that are "done"
175 if ( !isset( $yourls_actions ) )
176 $yourls_actions = array();
177 if ( !isset( $yourls_actions[ $hook ] ) ) {
178 $yourls_actions[ $hook ] = 1;
180 ++$yourls_actions[ $hook ];
184 if ( is_array( $arg ) && 1 == count( $arg ) && isset( $arg[0] ) && is_object( $arg[0] ) ) // array(&$this)
188 for ( $a = 2; $a < func_num_args(); $a++ )
189 $args[] = func_get_arg( $a );
191 yourls_apply_filter( $hook, $args );
195 * Retrieve the number times an action is fired.
197 * @param string $hook Name of the action hook.
198 * @return int The number of times action hook <tt>$hook</tt> is fired
200 function yourls_did_action( $hook ) {
201 global $yourls_actions;
202 if ( !isset( $yourls_actions ) || !isset( $yourls_actions[ $hook ] ) )
204 return $yourls_actions[ $hook ];
208 * Removes a function from a specified filter hook.
210 * This function removes a function attached to a specified filter hook. This
211 * method can be used to remove default functions attached to a specific filter
212 * hook and possibly replace them with a substitute.
214 * To remove a hook, the $function_to_remove and $priority arguments must match
215 * when the hook was added.
217 * @global array $yourls_filters storage for all of the filters
218 * @param string $hook The filter hook to which the function to be removed is hooked.
219 * @param callback $function_to_remove The name of the function which should be removed.
220 * @param int $priority optional. The priority of the function (default: 10).
221 * @return boolean Whether the function was registered as a filter before it was removed.
223 function yourls_remove_filter( $hook, $function_to_remove, $priority = 10 ) {
224 global $yourls_filters;
226 $function_to_remove = yourls_filter_unique_id( $hook, $function_to_remove, $priority );
228 $remove = isset( $yourls_filters[ $hook ][ $priority ][ $function_to_remove ] );
230 if ( $remove === true ) {
231 unset ( $yourls_filters[$hook][$priority][$function_to_remove] );
232 if ( empty( $yourls_filters[$hook][$priority] ) )
233 unset( $yourls_filters[$hook] );
239 * Removes a function from a specified action hook.
241 * @see yourls_remove_filter()
243 * @param string $hook The action hook to which the function to be removed is hooked.
244 * @param callback $function_to_remove The name of the function which should be removed.
245 * @param int $priority optional. The priority of the function (default: 10).
246 * @return boolean Whether the function was registered as an action before it was removed.
249 function yourls_remove_action( $hook, $function_to_remove, $priority = 10 ) {
250 return yourls_remove_filter( $hook, $function_to_remove, $priority );
254 * Removes all functions from a specified action hook.
256 * @see yourls_remove_all_filters()
259 * @param string $hook The action to remove hooks from
260 * @param int $priority optional. The priority of the functions to remove
261 * @return boolean true when it's finished
264 function yourls_remove_all_actions( $hook, $priority = false ) {
265 return yourls_remove_all_filters( $hook, $priority );
269 * Removes all functions from a specified filter hook.
273 * @param string $hook The filter to remove hooks from
274 * @param int $priority optional. The priority of the functions to remove
275 * @return boolean true when it's finished
278 function yourls_remove_all_filters( $hook, $priority = false ) {
279 global $yourls_filters;
281 if( isset( $yourls_filters[ $hook ] ) ) {
282 if( $priority === false ) {
283 unset( $yourls_filters[ $hook ] );
284 } else if ( isset( $yourls_filters[ $hook ][ $priority ] ) ) {
285 unset( $yourls_filters[ $hook ][ $priority ] );
293 * Check if any filter has been registered for a hook.
295 * @global array $yourls_filters storage for all of the filters
296 * @param string $hook The name of the filter hook.
297 * @param callback $function_to_check optional. If specified, return the priority of that function on this hook or false if not attached.
298 * @return int|boolean Optionally returns the priority on that hook for the specified function.
300 function yourls_has_filter( $hook, $function_to_check = false ) {
301 global $yourls_filters;
303 $has = !empty( $yourls_filters[ $hook ] );
304 if ( false === $function_to_check || false == $has ) {
308 if ( !$idx = yourls_filter_unique_id( $hook, $function_to_check, false ) )
311 foreach ( (array) array_keys( $yourls_filters[ $hook ] ) as $priority ) {
312 if ( isset( $yourls_filters[ $hook ][ $priority ][ $idx ] ) )
318 function yourls_has_action( $hook, $function_to_check = false ) {
319 return yourls_has_filter( $hook, $function_to_check );
323 * Return number of active plugins
325 * @return integer Number of activated plugins
327 function yourls_has_active_plugins( ) {
330 if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins )
331 $ydb->plugins = array();
333 return count( $ydb->plugins );
338 * List plugins in /user/plugins
340 * @return array Array of [/plugindir/plugin.php]=>array('Name'=>'Ozh', 'Title'=>'Hello', )
342 function yourls_get_plugins( ) {
343 $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php');
348 foreach( $plugins as $key => $plugin ) {
349 $_plugin = yourls_plugin_basename( $plugin );
350 $plugins[ $_plugin ] = yourls_get_plugin_data( $plugin );
351 unset( $plugins[ $key ] );
358 * Check if a plugin is active
360 * @param string $plugin Physical path to plugin file
363 function yourls_is_active_plugin( $plugin ) {
364 if( !yourls_has_active_plugins( ) )
368 $plugin = yourls_plugin_basename( $plugin );
370 return in_array( $plugin, $ydb->plugins );
375 * Parse a plugin header
377 * @param string $file Physical path to plugin file
378 * @return array Array of 'Field'=>'Value' from plugin comment header lines of the form "Field: Value"
380 function yourls_get_plugin_data( $file ) {
381 $fp = fopen( $file, 'r' ); // assuming $file is readable, since yourls_load_plugins() filters this
382 $data = fread( $fp, 8192 ); // get first 8kb
385 // Capture all the header within first comment block
386 if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) )
389 // Capture each line with "Something: some text"
391 $lines = preg_split( "[\n|\r]", $matches[1] );
394 $plugin_data = array();
395 foreach( $lines as $line ) {
396 if( !preg_match( '!(.*?):\s+(.*)!', $line, $matches ) )
399 list( $null, $field, $value ) = array_map( 'trim', $matches);
400 $plugin_data[ $field ] = $value;
406 // Include active plugins
407 function yourls_load_plugins() {
408 // Don't load plugins when installing or updating
409 if( yourls_is_installing() OR yourls_is_upgrading() )
412 $active_plugins = yourls_get_option( 'active_plugins' );
413 if( false === $active_plugins )
417 $ydb->plugins = array();
419 foreach( (array)$active_plugins as $key=>$plugin ) {
420 if( yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$plugin ) ) {
421 include_once( YOURLS_PLUGINDIR.'/'.$plugin );
422 $ydb->plugins[] = $plugin;
423 unset( $active_plugins[$key] );
427 // $active_plugins should be empty now, if not, a plugin could not be find: remove it
428 if( count( $active_plugins ) ) {
429 yourls_update_option( 'active_plugins', $ydb->plugins );
430 $message = yourls_n( 'Could not find and deactivated plugin :', 'Could not find and deactivated plugins :', count( $active_plugins ) );
431 $missing = '<strong>'.join( '</strong>, <strong>', $active_plugins ).'</strong>';
432 yourls_add_notice( $message .' '. $missing );
437 * Check if a file is safe for inclusion (well, "safe", no guarantee)
439 * @param string $file Full pathname to a file
442 function yourls_validate_plugin_file( $file ) {
444 false !== strpos( $file, '..' )
446 false !== strpos( $file, './' )
448 'plugin.php' !== substr( $file, -10 ) // a plugin must be named 'plugin.php'
450 !is_readable( $file )
460 * @param string $plugin Plugin filename (full or relative to plugins directory)
461 * @return mixed string if error or true if success
463 function yourls_activate_plugin( $plugin ) {
465 $plugin = yourls_plugin_basename( $plugin );
466 $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
467 if( !yourls_validate_plugin_file( $plugindir.'/'.$plugin ) )
468 return yourls__( 'Not a valid plugin file' );
470 // check not activated already
472 if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) )
473 return yourls__( 'Plugin already activated' );
475 // attempt activation. TODO: uber cool fail proof sandbox like in WP.
477 include_once( YOURLS_PLUGINDIR.'/'.$plugin );
478 if ( ob_get_length() > 0 ) {
479 // there was some output: error
480 // @codeCoverageIgnoreStart
481 $output = ob_get_clean();
482 return yourls_s( 'Plugin generated unexpected output. Error was: <br/><pre>%s</pre>', $output );
483 // @codeCoverageIgnoreEnd
487 // so far, so good: update active plugin list
488 $ydb->plugins[] = $plugin;
489 yourls_update_option( 'active_plugins', $ydb->plugins );
490 yourls_do_action( 'activated_plugin', $plugin );
491 yourls_do_action( 'activated_' . $plugin );
497 * Deactivate a plugin
499 * @param string $plugin Plugin filename (full relative to plugins directory)
500 * @return mixed string if error or true if success
502 function yourls_deactivate_plugin( $plugin ) {
503 $plugin = yourls_plugin_basename( $plugin );
505 // Check plugin is active
506 if( !yourls_is_active_plugin( $plugin ) )
507 return yourls__( 'Plugin not active' );
509 // Deactivate the plugin
511 $key = array_search( $plugin, $ydb->plugins );
512 if( $key !== false ) {
513 array_splice( $ydb->plugins, $key, 1 );
516 yourls_update_option( 'active_plugins', $ydb->plugins );
517 yourls_do_action( 'deactivated_plugin', $plugin );
518 yourls_do_action( 'deactivated_' . $plugin );
524 * Return the path of a plugin file, relative to the plugins directory
526 function yourls_plugin_basename( $file ) {
527 $file = yourls_sanitize_filename( $file );
528 $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
529 $file = str_replace( $plugindir, '', $file );
530 return trim( $file, '/' );
534 * Return the URL of the directory a plugin
536 function yourls_plugin_url( $file ) {
537 $url = YOURLS_PLUGINURL . '/' . yourls_plugin_basename( $file );
538 if( yourls_is_ssl() or yourls_needs_ssl() )
539 $url = str_replace( 'http://', 'https://', $url );
540 return yourls_apply_filter( 'plugin_url', $url, $file );
544 * Display list of links to plugin admin pages, if any
546 function yourls_list_plugin_admin_pages() {
549 if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )
552 $plugin_links = array();
553 foreach( (array)$ydb->plugin_pages as $plugin => $page ) {
554 $plugin_links[ $plugin ] = array(
555 'url' => yourls_admin_url( 'plugins.php?page='.$page['slug'] ),
556 'anchor' => $page['title'],
559 return $plugin_links;
563 * Register a plugin administration page
565 function yourls_register_plugin_page( $slug, $title, $function ) {
568 if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )
569 $ydb->plugin_pages = array();
571 $ydb->plugin_pages[ $slug ] = array(
574 'function' => $function,
579 * Handle plugin administration page
582 function yourls_plugin_admin_page( $plugin_page ) {
585 // Check the plugin page is actually registered
586 if( !isset( $ydb->plugin_pages[$plugin_page] ) ) {
587 yourls_die( yourls__( 'This page does not exist. Maybe a plugin you thought was activated is inactive?' ), yourls__( 'Invalid link' ) );
590 // Draw the page itself
591 yourls_do_action( 'load-' . $plugin_page);
592 yourls_html_head( 'plugin_page_' . $plugin_page, $ydb->plugin_pages[$plugin_page]['title'] );
596 call_user_func( $ydb->plugin_pages[$plugin_page]['function'] );
598 yourls_html_footer();
603 * Callback function: Sort plugins
605 * @link http://php.net/uasort
606 * @codeCoverageIgnore
608 * @param array $plugin_a
609 * @param array $plugin_b
610 * @return int 0, 1 or -1, see uasort()
612 function yourls_plugins_sort_callback( $plugin_a, $plugin_b ) {
613 $orderby = yourls_apply_filter( 'plugins_sort_callback', 'Plugin Name' );
614 $order = yourls_apply_filter( 'plugins_sort_callback', 'ASC' );
616 $a = isset( $plugin_a[ $orderby ] ) ? $plugin_a[ $orderby ] : '';
617 $b = isset( $plugin_b[ $orderby ] ) ? $plugin_b[ $orderby ] : '';
622 if ( 'DESC' == $order )
623 return ( $a < $b ) ? 1 : -1;
625 return ( $a < $b ) ? -1 : 1;