]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/functions-plugins.php
Simpler syntax
[Github/YOURLS.git] / includes / functions-plugins.php
1 <?php
2
3 /**
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.
7  *
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'}
10  * type are valid.
11  *
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.
13  *
14  * @author Ozh
15  * @since 1.5
16  */
17
18 $yourls_filters = array();
19 /* This global var will collect filters with the following structure:
20  * $yourls_filters['hook']['array of priorities']['serialized function names']['array of ['array (functions, accepted_args)]']
21  */
22
23 /**
24  * Registers a filtering function
25  * 
26  * Typical use:
27  *              yourls_add_filter('some_hook', 'function_handler_for_hook');
28  *
29  * @global array $yourls_filters Storage for all of the filters
30  * @param string $hook the name of the YOURLS element to be filtered or YOURLS action to be triggered
31  * @param callback $function_name the name of the function that is to be called.
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)
33  * @param int $accepted_args optional. The number of arguments the function accept (default is the number provided).
34  * @param string $type
35  */
36 function yourls_add_filter( $hook, $function_name, $priority = 10, $accepted_args = NULL, $type = 'filter' ) {
37         global $yourls_filters;
38         // At this point, we cannot check if the function exists, as it may well be defined later (which is OK)
39         $id = yourls_filter_unique_id( $hook, $function_name, $priority );
40         
41         $yourls_filters[ $hook ][ $priority ][ $id ] = array(
42                 'function'      => $function_name,
43                 'accepted_args' => $accepted_args,
44                 'type'          => $type,
45         );
46 }
47
48 /**
49  * Hooks a function on to a specific action.
50  *
51  * Actions are the hooks that YOURLS launches at specific points
52  * during execution, or when specific events occur. Plugins can specify that
53  * one or more of its PHP functions are executed at these points, using the
54  * Action API.
55  *
56  * @param string $hook The name of the action to which the $function_to_add is hooked.
57  * @param callback $function_name The name of the function you wish to be called.
58  * @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.
59  * @param int $accepted_args optional. The number of arguments the function accept (default 1).
60  */
61 function yourls_add_action( $hook, $function_name, $priority = 10, $accepted_args = 1 ) {
62         return yourls_add_filter( $hook, $function_name, $priority, $accepted_args, 'action' );
63 }
64
65
66
67 /**
68  * Build Unique ID for storage and retrieval.
69  *
70  * Simply using a function name is not enough, as several functions can have the same name when they are enclosed in classes.
71  *
72  * @global array $yourls_filters storage for all of the filters
73  * @param string $hook hook to which the function is attached
74  * @param string|array $function used for creating unique id
75  * @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.
76  * @return string unique ID for usage as array key
77  */
78 function yourls_filter_unique_id( $hook, $function, $priority ) {
79         global $yourls_filters;
80
81         // If function then just skip all of the tests and not overwrite the following.
82         if ( is_string( $function ) ) {
83                 return $function;
84     }
85     
86         if( is_object($function) ) {
87                 // Closures are currently implemented as objects
88                 $function = array( $function, '' );
89         } else {
90                 $function = (array) $function;
91         }
92
93         // Object Class Calling
94         if ( is_object( $function[0] ) ) {
95         return spl_object_hash( $function[0] ) . $function[1];
96         }
97     
98     // Static Calling
99     if ( is_string( $function[0] ) ) {
100                 return $function[0]. '::' .$function[1];
101     }
102
103 }
104
105 /**
106  * Performs a filtering operation on a YOURLS element or event.
107  *
108  * Typical use:
109  *
110  *              1) Modify a variable if a function is attached to hook 'yourls_hook'
111  *              $yourls_var = "default value";
112  *              $yourls_var = yourls_apply_filter( 'yourls_hook', $yourls_var );
113  *
114  *              2) Trigger functions is attached to event 'yourls_event'
115  *              yourls_apply_filter( 'yourls_event' );
116  *      (see yourls_do_action() )
117  * 
118  * Returns an element which may have been filtered by a filter.
119  *
120  * @global array $yourls_filters storage for all of the filters
121  * @param string $hook the name of the YOURLS element or action
122  * @param mixed $value the value of the element before filtering
123  * @return mixed
124  */
125 function yourls_apply_filter( $hook, $value = '' ) {
126         global $yourls_filters;
127         if ( !isset( $yourls_filters[ $hook ] ) )
128                 return $value;
129         
130         $args = func_get_args();
131         
132         // Sort filters by priority
133         ksort( $yourls_filters[ $hook ] );
134         
135         // Loops through each filter
136         reset( $yourls_filters[ $hook ] );
137         do {
138                 foreach( (array) current( $yourls_filters[ $hook ] ) as $the_ ) {
139                         if ( !is_null( $the_['function'] ) ){
140                                 $args[1] = $value;
141                                 $count = $the_['accepted_args'];
142                                 if ( is_null( $count ) ) {
143                                         $_value = call_user_func_array( $the_['function'], array_slice( $args, 1 ) );
144                                 } else {
145                                         $_value = call_user_func_array( $the_['function'], array_slice( $args, 1, (int) $count ) );
146                                 }
147                         }
148                         if( $the_['type'] == 'filter' )
149                                 $value = $_value;
150                 }
151
152         } while ( next( $yourls_filters[ $hook ] ) !== false );
153         
154         if( $the_['type'] == 'filter' )
155                 return $value;
156 }
157
158 /**
159  * Performs an action triggered by a YOURLS event.
160
161  * @param string $hook the name of the YOURLS action
162  * @param mixed $arg action arguments
163  */
164 function yourls_do_action( $hook, $arg = '' ) {
165         global $yourls_actions;
166         
167         // Keep track of actions that are "done"
168         if ( !isset( $yourls_actions ) )
169                 $yourls_actions = array();
170         if ( !isset( $yourls_actions[ $hook ] ) ) {
171                 $yourls_actions[ $hook ] = 1;
172     } else {
173                 ++$yourls_actions[ $hook ];
174     }
175
176         $args = array();
177         if ( is_array( $arg ) && 1 == count( $arg ) && isset( $arg[0] ) && is_object( $arg[0] ) ) // array(&$this)
178                 $args[] =& $arg[0];
179         else
180                 $args[] = $arg;
181         for ( $a = 2; $a < func_num_args(); $a++ )
182                 $args[] = func_get_arg( $a );
183         
184         yourls_apply_filter( $hook, $args );
185 }
186
187 /**
188 * Retrieve the number times an action is fired.
189 *
190 * @param string $hook Name of the action hook.
191 * @return int The number of times action hook <tt>$hook</tt> is fired
192 */
193 function yourls_did_action( $hook ) {
194         global $yourls_actions;
195         if ( !isset( $yourls_actions ) || !isset( $yourls_actions[ $hook ] ) )
196                 return 0;
197         return $yourls_actions[ $hook ];
198 }
199
200 /**
201  * Removes a function from a specified filter hook.
202  *
203  * This function removes a function attached to a specified filter hook. This
204  * method can be used to remove default functions attached to a specific filter
205  * hook and possibly replace them with a substitute.
206  *
207  * To remove a hook, the $function_to_remove and $priority arguments must match
208  * when the hook was added.
209  *
210  * @global array $yourls_filters storage for all of the filters
211  * @param string $hook The filter hook to which the function to be removed is hooked.
212  * @param callback $function_to_remove The name of the function which should be removed.
213  * @param int $priority optional. The priority of the function (default: 10).
214  * @return boolean Whether the function was registered as a filter before it was removed.
215  */
216 function yourls_remove_filter( $hook, $function_to_remove, $priority = 10 ) {
217         global $yourls_filters;
218         
219         $function_to_remove = yourls_filter_unique_id( $hook, $function_to_remove, $priority );
220
221         $remove = isset( $yourls_filters[ $hook ][ $priority ][ $function_to_remove ] );
222
223         if ( $remove === true ) {
224                 unset ( $yourls_filters[$hook][$priority][$function_to_remove] );
225                 if ( empty( $yourls_filters[$hook][$priority] ) )
226                         unset( $yourls_filters[$hook] );
227         }
228         return $remove;
229 }
230
231 /**
232  * Removes a function from a specified action hook.
233  *
234  * @see yourls_remove_filter()
235  *
236  * @param string $hook The action hook to which the function to be removed is hooked.
237  * @param callback $function_to_remove The name of the function which should be removed.
238  * @param int $priority optional. The priority of the function (default: 10).
239  * @return boolean Whether the function was registered as an action before it was removed.
240  */
241
242 function yourls_remove_action( $hook, $function_to_remove, $priority = 10 ) {
243     return yourls_remove_filter( $hook, $function_to_remove, $priority );
244 }
245
246 /**
247  * Check if any filter has been registered for a hook.
248  *
249  * @global array $yourls_filters storage for all of the filters
250  * @param string $hook The name of the filter hook.
251  * @param callback $function_to_check optional.  If specified, return the priority of that function on this hook or false if not attached.
252  * @return int|boolean Optionally returns the priority on that hook for the specified function.
253  */
254 function yourls_has_filter( $hook, $function_to_check = false ) {
255         global $yourls_filters;
256
257         $has = !empty( $yourls_filters[ $hook ] );
258         if ( false === $function_to_check || false == $has ) {
259                 return $has;
260         }
261         if ( !$idx = yourls_filter_unique_id( $hook, $function_to_check, false ) ) {
262                 return false;
263         }
264
265         foreach ( (array) array_keys( $yourls_filters[ $hook ] ) as $priority ) {
266                 if ( isset( $yourls_filters[ $hook ][ $priority ][ $idx ] ) )
267                         return $priority;
268         }
269         return false;
270 }
271
272 function yourls_has_action( $hook, $function_to_check = false ) {
273         return yourls_has_filter( $hook, $function_to_check );
274 }
275
276 /**
277  * Return number of active plugins
278  *
279  * @return integer Number of activated plugins
280  */
281 function yourls_has_active_plugins( ) {
282         global $ydb;
283         
284         if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins )
285                 $ydb->plugins = array();
286                 
287         return count( $ydb->plugins );
288 }
289
290
291 /**
292  * List plugins in /user/plugins
293  *
294  * @global object $ydb Storage of mostly everything YOURLS needs to know
295  * @return array Array of [/plugindir/plugin.php]=>array('Name'=>'Ozh', 'Title'=>'Hello', )
296  */
297 function yourls_get_plugins( ) {
298         $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php');
299         
300         if( !$plugins )
301                 return array();
302         
303         foreach( $plugins as $key => $plugin ) {
304                 $_plugin = yourls_plugin_basename( $plugin );
305                 $plugins[ $_plugin ] = yourls_get_plugin_data( $plugin );
306                 unset( $plugins[ $key ] );
307         }
308         
309         return $plugins;
310 }
311
312 /**
313  * Check if a plugin is active
314  *
315  * @param string $plugin Physical path to plugin file
316  * @return bool
317  */
318 function yourls_is_active_plugin( $plugin ) {
319         if( !yourls_has_active_plugins( ) )
320                 return false;
321         
322         global $ydb;
323         $plugin = yourls_plugin_basename( $plugin );
324         
325         return in_array( $plugin, $ydb->plugins );
326
327 }
328
329 /**
330  * Parse a plugin header
331  *
332  * @param string $file Physical path to plugin file
333  * @return array Array of 'Field'=>'Value' from plugin comment header lines of the form "Field: Value"
334  */
335 function yourls_get_plugin_data( $file ) {
336         $fp = fopen( $file, 'r' ); // assuming $file is readable, since yourls_load_plugins() filters this
337         $data = fread( $fp, 8192 ); // get first 8kb
338         fclose( $fp );
339         
340         // Capture all the header within first comment block
341         if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) )
342                 return array();
343         
344         // Capture each line with "Something: some text"
345         unset( $data );
346         $lines = preg_split( "[\n|\r]", $matches[1] );
347         unset( $matches );
348
349         $plugin_data = array();
350         foreach( $lines as $line ) {
351                 if( !preg_match( '!(.*?):\s+(.*)!', $line, $matches ) )
352                         continue;
353                 
354                 list( $null, $field, $value ) = array_map( 'trim', $matches);
355                 $plugin_data[ $field ] = $value;
356         }
357         
358         return $plugin_data;
359 }
360
361 // Include active plugins
362 function yourls_load_plugins() {
363         // Don't load plugins when installing or updating
364         if( yourls_is_installing() OR yourls_is_upgrading() )
365                 return;
366         
367         $active_plugins = yourls_get_option( 'active_plugins' );
368         if( false === $active_plugins )
369                 return;
370         
371         global $ydb;
372         $ydb->plugins = array();
373         
374         foreach( (array)$active_plugins as $key=>$plugin ) {
375                 if( yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$plugin ) ) {
376                         include_once( YOURLS_PLUGINDIR.'/'.$plugin );
377                         $ydb->plugins[] = $plugin;
378                         unset( $active_plugins[$key] );
379                 }
380         }
381         
382         // $active_plugins should be empty now, if not, a plugin could not be find: remove it
383         if( count( $active_plugins ) ) {
384                 yourls_update_option( 'active_plugins', $ydb->plugins );
385                 $message = yourls_n( 'Could not find and deactivated plugin :', 'Could not find and deactivated plugins :', count( $active_plugins ) );
386                 $missing = '<strong>'.join( '</strong>, <strong>', $active_plugins ).'</strong>';
387                 yourls_add_notice( $message .' '. $missing );
388         }
389 }
390
391 /**
392  * Check if a file is safe for inclusion (well, "safe", no guarantee)
393  *
394  * @param string $file Full pathname to a file
395  * @return bool
396  */
397 function yourls_validate_plugin_file( $file ) {
398         if (
399                 false !== strpos( $file, '..' )
400                 OR
401                 false !== strpos( $file, './' )
402                 OR
403                 'plugin.php' !== substr( $file, -10 )   // a plugin must be named 'plugin.php'
404                 OR
405                 !is_readable( $file )
406         )
407                 return false;
408                 
409         return true;
410 }
411
412 /**
413  * Activate a plugin
414  *
415  * @param string $plugin Plugin filename (full or relative to plugins directory)
416  * @return mixed string if error or true if success
417  */
418 function yourls_activate_plugin( $plugin ) {
419         // validate file
420         $plugin = yourls_plugin_basename( $plugin );
421         $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
422         if( !yourls_validate_plugin_file( $plugindir.'/'.$plugin ) )
423                 return yourls__( 'Not a valid plugin file' );
424                 
425         // check not activated already
426         global $ydb;
427         if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) )
428                 return yourls__( 'Plugin already activated' );
429         
430         // attempt activation. TODO: uber cool fail proof sandbox like in WP.
431         ob_start();
432         include_once( YOURLS_PLUGINDIR.'/'.$plugin );
433         if ( ob_get_length() > 0 ) {
434                 // there was some output: error
435                 $output = ob_get_clean();
436                 return yourls_s( 'Plugin generated unexpected output. Error was: <br/><pre>%s</pre>', $output );
437         }
438     ob_end_clean();
439
440         // so far, so good: update active plugin list
441         $ydb->plugins[] = $plugin;
442         yourls_update_option( 'active_plugins', $ydb->plugins );
443         yourls_do_action( 'activated_plugin', $plugin );
444         yourls_do_action( 'activated_' . $plugin );
445         
446         return true;
447 }
448
449 /**
450  * Deactivate a plugin
451  *
452  * @param string $plugin Plugin filename (full relative to plugins directory)
453  * @return mixed string if error or true if success
454  */
455 function yourls_deactivate_plugin( $plugin ) {
456         $plugin = yourls_plugin_basename( $plugin );
457
458         // Check plugin is active
459         if( !yourls_is_active_plugin( $plugin ) )
460                 return yourls__( 'Plugin not active' );
461         
462         // Deactivate the plugin
463         global $ydb;
464         $key = array_search( $plugin, $ydb->plugins );
465         if( $key !== false ) {
466                 array_splice( $ydb->plugins, $key, 1 );
467         }
468         
469         yourls_update_option( 'active_plugins', $ydb->plugins );
470         yourls_do_action( 'deactivated_plugin', $plugin );
471         yourls_do_action( 'deactivated_' . $plugin );
472         
473         return true;
474 }
475
476 /**
477  * Return the path of a plugin file, relative to the plugins directory
478  */
479 function yourls_plugin_basename( $file ) {
480         $file = yourls_sanitize_filename( $file );
481         $plugindir = yourls_sanitize_filename( YOURLS_PLUGINDIR );
482         $file = str_replace( $plugindir, '', $file );
483         return trim( $file, '/' );
484 }
485
486 /**
487  * Return the URL of the directory a plugin
488  */
489 function yourls_plugin_url( $file ) {
490         $url = YOURLS_PLUGINURL . '/' . yourls_plugin_basename( $file );
491         if( yourls_is_ssl() or yourls_needs_ssl() )
492                 $url = str_replace( 'http://', 'https://', $url );
493         return yourls_apply_filter( 'plugin_url', $url, $file );
494 }
495
496 /**
497  * Display list of links to plugin admin pages, if any
498  */
499 function yourls_list_plugin_admin_pages() {
500         global $ydb;
501         
502         if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )
503                 return;
504         
505         $plugin_links = array();
506         foreach( (array)$ydb->plugin_pages as $plugin => $page ) {
507                 $plugin_links[ $plugin ] = array(
508                         'url'    => yourls_admin_url( 'plugins.php?page='.$page['slug'] ),
509                         'anchor' => $page['title'],
510                 );
511         }
512         return $plugin_links;
513 }
514
515 /**
516  * Register a plugin administration page
517  */
518 function yourls_register_plugin_page( $slug, $title, $function ) {
519         global $ydb;
520         
521         if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )
522                 $ydb->plugin_pages = array();
523
524         $ydb->plugin_pages[ $slug ] = array(
525                 'slug'     => $slug,
526                 'title'    => $title,
527                 'function' => $function,
528         );
529 }
530
531 /**
532  * Handle plugin administration page
533  *
534  */
535 function yourls_plugin_admin_page( $plugin_page ) {
536         global $ydb;
537
538         // Check the plugin page is actually registered
539         if( !isset( $ydb->plugin_pages[$plugin_page] ) ) {
540                 yourls_die( yourls__( 'This page does not exist. Maybe a plugin you thought was activated is inactive?' ), yourls__( 'Invalid link' ) );
541         }
542         
543         // Draw the page itself
544         yourls_do_action( 'load-' . $plugin_page);
545         yourls_html_head( 'plugin_page_' . $plugin_page, $ydb->plugin_pages[$plugin_page]['title'] );
546         yourls_html_logo();
547         yourls_html_menu();
548         
549         call_user_func( $ydb->plugin_pages[$plugin_page]['function'] );
550         
551         yourls_html_footer();
552         
553         die();
554 }
555
556
557 /**
558  * Callback function: Sort plugins 
559  *
560  * @link http://php.net/uasort
561  *
562  * @param array $plugin_a
563  * @param array $plugin_b
564  * @return int 0, 1 or -1, see uasort()
565  */
566 function yourls_plugins_sort_callback( $plugin_a, $plugin_b ) {
567         $orderby = yourls_apply_filter( 'plugins_sort_callback', 'Plugin Name' );
568         $order   = yourls_apply_filter( 'plugins_sort_callback', 'ASC' );
569
570         $a = isset( $plugin_a[ $orderby ] ) ? $plugin_a[ $orderby ] : '';
571         $b = isset( $plugin_b[ $orderby ] ) ? $plugin_b[ $orderby ] : '';
572
573         if ( $a == $b )
574                 return 0;
575
576         if ( 'DESC' == $order )
577                 return ( $a < $b ) ? 1 : -1;
578         else
579                 return ( $a < $b ) ? -1 : 1;            
580 }