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