]> CyberLeo.Net >> Repos - Github/YOURLS.git/blob - includes/functions-plugins.php
Fully filterable admin menu links. Mucho mas cleano.
[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 ) {\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         );\r
44 }\r
45 \r
46 /**\r
47  * Hooks a function on to a specific action.\r
48  *\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
52  * Action API.\r
53  *\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
58  */\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
61 }\r
62 \r
63 \r
64 \r
65 /**\r
66  * Build Unique ID for storage and retrieval.\r
67  *\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
69  *\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
76  */\r
77 function yourls_filter_unique_id( $hook, $function, $priority ) {\r
78         global $yourls_filters;\r
79 \r
80         // If function then just skip all of the tests and not overwrite the following.\r
81         if ( is_string($function) )\r
82                 return $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
88                                 return false;\r
89                         $count = isset($yourls_filters[$hook][$priority]) ? count((array)$yourls_filters[$hook][$priority]) : 0;\r
90                         $function[0]->_yourls_filters_id = $count;\r
91                         $obj_idx .= $count;\r
92                         unset($count);\r
93                 } else\r
94                         $obj_idx .= $function[0]->_yourls_filters_id;\r
95                 return $obj_idx;\r
96         }\r
97         // Static Calling\r
98         else if ( is_string($function[0]) )\r
99                 return $function[0].$function[1];\r
100 \r
101 }\r
102 \r
103 /**\r
104  * Performs a filtering operation on a YOURLS element or event.\r
105  *\r
106  * Typical use:\r
107  *\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
111  *\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
115  * \r
116  * Returns an element which may have been filtered by a filter.\r
117  *\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
121  * @return mixed\r
122  */\r
123 function yourls_apply_filter( $hook, $value = '' ) {\r
124         global $yourls_filters;\r
125         if ( !isset( $yourls_filters[$hook] ) )\r
126                 return $value;\r
127         \r
128         $args = func_get_args();\r
129         \r
130         // Sort filters by priority\r
131         ksort( $yourls_filters[$hook] );\r
132         \r
133         // Loops through each filter\r
134         reset( $yourls_filters[$hook] );\r
135         do {\r
136                 foreach( (array) current($yourls_filters[$hook]) as $the_ )\r
137                         if ( !is_null($the_['function']) ){\r
138                                 $args[1] = $value;\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
142                                 } else {\r
143                                         $value = call_user_func_array($the_['function'], array_slice($args, 1, (int) $count));\r
144                                 }\r
145                         }\r
146 \r
147         } while ( next($yourls_filters[$hook]) !== false );\r
148         \r
149         return $value;\r
150 }\r
151 \r
152 function yourls_do_action( $hook, $arg = '' ) {\r
153         $args = array();\r
154         if ( is_array($arg) && 1 == count($arg) && isset($arg[0]) && is_object($arg[0]) ) // array(&$this)\r
155                 $args[] =& $arg[0];\r
156         else\r
157                 $args[] = $arg;\r
158         for ( $a = 2; $a < func_num_args(); $a++ )\r
159                 $args[] = func_get_arg($a);\r
160         \r
161         yourls_apply_filter( $hook, $args );\r
162 }\r
163 \r
164 \r
165 /**\r
166  * Removes a function from a specified filter hook.\r
167  *\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
171  *\r
172  * To remove a hook, the $function_to_remove and $priority arguments must match\r
173  * when the hook was added.\r
174  *\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
181  */\r
182 function yourls_remove_filter( $hook, $function_to_remove, $priority = 10, $accepted_args = 1 ) {\r
183         global $yourls_filters;\r
184         \r
185         $function_to_remove = yourls_filter_unique_id($hook, $function_to_remove, $priority);\r
186 \r
187         $remove = isset ($yourls_filters[$hook][$priority][$function_to_remove]);\r
188 \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
193         }\r
194         return $remove;\r
195 }\r
196 \r
197 \r
198 /**\r
199  * Check if any filter has been registered for a hook.\r
200  *\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
205  */\r
206 function yourls_has_filter( $hook, $function_to_check = false ) {\r
207         global $yourls_filters;\r
208 \r
209         $has = !empty($yourls_filters[$hook]);\r
210         if ( false === $function_to_check || false == $has ) {\r
211                 return $has;\r
212         }\r
213         if ( !$idx = yourls_filter_unique_id($hook, $function_to_check, false) ) {\r
214                 return false;\r
215         }\r
216 \r
217         foreach ( (array) array_keys($yourls_filters[$hook]) as $priority ) {\r
218                 if ( isset($yourls_filters[$hook][$priority][$idx]) )\r
219                         return $priority;\r
220         }\r
221         return false;\r
222 }\r
223 \r
224 function yourls_has_action( $hook, $function_to_check = false ) {\r
225         return yourls_has_filter( $hook, $function_to_check );\r
226 }\r
227 \r
228 /**\r
229  * Return number of active plugins\r
230  *\r
231  * @return integer Number of activated plugins\r
232  */\r
233 function yourls_has_active_plugins( ) {\r
234         global $ydb;\r
235         \r
236         if( !property_exists( $ydb, 'plugins' ) || !$ydb->plugins )\r
237                 $ydb->plugins = array();\r
238                 \r
239         return count( $ydb->plugins );\r
240 }\r
241 \r
242 \r
243 /**\r
244  * List plugins in /user/plugins\r
245  *\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
248  */\r
249 function yourls_get_plugins( ) {\r
250         global $ydb;\r
251         \r
252         $plugins = (array) glob( YOURLS_PLUGINDIR .'/*/plugin.php');\r
253         \r
254         if( !$plugins )\r
255                 return array();\r
256         \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
261         }\r
262         \r
263         return $plugins;\r
264 }\r
265 \r
266 /**\r
267  * Check if a plugin is active\r
268  *\r
269  * @param string $file Physical path to plugin file\r
270  * @return bool\r
271  */\r
272 function yourls_is_active_plugin( $plugin ) {\r
273         if( !yourls_has_active_plugins( ) )\r
274                 return false;\r
275         \r
276         global $ydb;\r
277         $plugin = yourls_plugin_basename( $plugin );\r
278         \r
279         return in_array( $plugin, $ydb->plugins );\r
280 \r
281 }\r
282 \r
283 /**\r
284  * Parse a plugin header\r
285  *\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
288  */\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
292         fclose( $fp );\r
293         \r
294         // Capture all the header within first comment block\r
295         if( !preg_match( '!.*?/\*(.*?)\*/!ms', $data, $matches ) )\r
296                 return array();\r
297         \r
298         // Capture each line with "Something: some text"\r
299         unset( $data );\r
300         $lines = preg_split( "[\n|\r]", $matches[1] );\r
301         unset( $matches );\r
302 \r
303         $plugin_data = array();\r
304         foreach( $lines as $line ) {\r
305                 if( !preg_match( '!(.*?):\s+(.*)!', $line, $matches ) )\r
306                         continue;\r
307                 \r
308                 list( $null, $field, $value ) = array_map( 'trim', $matches);\r
309                 $plugin_data[ $field ] = $value;\r
310         }\r
311         \r
312         return $plugin_data;\r
313 }\r
314 \r
315 // Include active plugins\r
316 function yourls_load_plugins() {\r
317         global $ydb;\r
318         $ydb->plugins = array();\r
319         $active_plugins = yourls_get_option( 'active_plugins' );\r
320         \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
323                 return;\r
324         \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
330                 }\r
331         }\r
332         \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
339         }\r
340 }\r
341 \r
342 /**\r
343  * Check if a file is safe for inclusion (well, "safe", no guarantee)\r
344  *\r
345  * @param string $file Full pathname to a file\r
346  */\r
347 function yourls_validate_plugin_file( $file ) {\r
348         if (\r
349                 false !== strpos( $file, '..' )\r
350                 OR\r
351                 false !== strpos( $file, './' )\r
352                 OR\r
353                 'plugin.php' !== substr( $file, -10 )   // a plugin must be named 'plugin.php'\r
354                 OR\r
355                 !is_readable( $file )\r
356         )\r
357                 return false;\r
358                 \r
359         return true;\r
360 }\r
361 \r
362 /**\r
363  * Activate a plugin\r
364  *\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
367  */\r
368 function yourls_activate_plugin( $plugin ) {\r
369         // validate file\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
374                 \r
375         // check not activated already\r
376         global $ydb;\r
377         if( yourls_has_active_plugins() && in_array( $plugin, $ydb->plugins ) )\r
378                 return 'Plugin already activated';\r
379         \r
380         // attempt activation. TODO: uber cool fail proof sandbox like in WP.\r
381         ob_start();\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
387         }\r
388         \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
394         \r
395         return true;\r
396 }\r
397 \r
398 /**\r
399  * Dectivate a plugin\r
400  *\r
401  * @param string $plugin Plugin filename (full relative to plugins directory)\r
402  * @return mixed string if error or true if success\r
403  */\r
404 function yourls_deactivate_plugin( $plugin ) {\r
405         $plugin = yourls_plugin_basename( $plugin );\r
406 \r
407         // Check plugin is active\r
408         if( !yourls_is_active_plugin( $plugin ) )\r
409                 return 'Plugin not active';\r
410         \r
411         // Deactivate the plugin\r
412         global $ydb;\r
413         $key = array_search( $plugin, $ydb->plugins );\r
414         if( $key !== false ) {\r
415                 array_splice( $ydb->plugins, $key, 1 );\r
416         }\r
417         \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
421         \r
422         return true;\r
423 }\r
424 \r
425 /**\r
426  * Return the path of a plugin file, relative to the plugins directory\r
427  */\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
433 }\r
434 \r
435 /**\r
436  * Return the URL of the directory a plugin\r
437  */\r
438 function yourls_plugin_url( $file ) {\r
439         $url = YOURLS_PLUGINURL . '/' . yourls_plugin_basename( $file );\r
440         if( yourls_is_ssl() or yourls_needs_ssl() )\r
441                 $url = str_replace('http://', 'https://', $url);\r
442         return yourls_apply_filter( 'plugin_url', $url, $file );\r
443 }\r
444 \r
445 /**\r
446  * Display list of links to plugin admin pages, if any\r
447  */\r
448 function yourls_list_plugin_admin_pages() {\r
449         global $ydb;\r
450         \r
451         if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )\r
452                 return;\r
453         \r
454         $plugin_links = array();\r
455         foreach( (array)$ydb->plugin_pages as $plugin => $page ) {\r
456                 $plugin_links[$plugin] = array(\r
457                         'url'    => yourls_admin_url( 'plugins.php?page='.$page['slug'] ),\r
458                         'anchor' => $page['title'],\r
459                 );\r
460         }\r
461         return $plugin_links;\r
462 }\r
463 \r
464 /**\r
465  * Register a plugin administration page\r
466  */\r
467 function yourls_register_plugin_page( $slug, $title, $function ) {\r
468         global $ydb;\r
469         \r
470         if( !property_exists( $ydb, 'plugin_pages' ) || !$ydb->plugin_pages )\r
471                 $ydb->plugin_pages = array();\r
472 \r
473         $ydb->plugin_pages[ $slug ] = array(\r
474                 'slug'  => $slug,\r
475                 'title' => $title,\r
476                 'function' => $function,\r
477         );\r
478 }\r
479 \r
480 /**\r
481  * Handle plugin administration page\r
482  *\r
483  */\r
484 function yourls_plugin_admin_page( $plugin_page ) {\r
485         global $ydb;\r
486 \r
487         // Check the plugin page is actually registered\r
488         if( !isset( $ydb->plugin_pages[$plugin_page] ) ) {\r
489                 yourls_die( 'This page does not exist. Maybe a plugin you thought was activated is inactive?', 'Invalid link' );\r
490         }\r
491         \r
492         // Draw the page itself\r
493         yourls_do_action( 'load-' . $plugin_page);\r
494         yourls_html_head( 'plugin_page_' . $plugin_page, $ydb->plugin_pages[$plugin_page]['title'] );\r
495         yourls_html_logo();\r
496         yourls_html_menu();\r
497         \r
498         call_user_func( $ydb->plugin_pages[$plugin_page]['function'] );\r
499         \r
500         yourls_html_footer();\r
501         \r
502         die();\r
503 }