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