]> git.mxchange.org Git - friendica.git/blob - include/plugin.php
Merge develop into 20171122_-_port_tagcloud
[friendica.git] / include / plugin.php
1 <?php
2 /**
3  * @file include/plugin.php
4  *
5  * @brief Some functions to handle addons and themes.
6  */
7
8 use Friendica\App;
9 use Friendica\Core\Config;
10 use Friendica\Core\System;
11 use Friendica\Database\DBM;
12
13 /**
14  * @brief uninstalls an addon.
15  *
16  * @param string $plugin name of the addon
17  * @return boolean
18  */
19 function uninstall_plugin($plugin) {
20         logger("Addons: uninstalling " . $plugin);
21         dba::delete('addon', array('name' => $plugin));
22
23         @include_once('addon/' . $plugin . '/' . $plugin . '.php');
24         if (function_exists($plugin . '_uninstall')) {
25                 $func = $plugin . '_uninstall';
26                 $func();
27         }
28 }
29
30 /**
31  * @brief installs an addon.
32  *
33  * @param string $plugin name of the addon
34  * @return bool
35  */
36 function install_plugin($plugin) {
37         // silently fail if plugin was removed
38
39         if (!file_exists('addon/' . $plugin . '/' . $plugin . '.php')) {
40                 return false;
41         }
42         logger("Addons: installing " . $plugin);
43         $t = @filemtime('addon/' . $plugin . '/' . $plugin . '.php');
44         @include_once('addon/' . $plugin . '/' . $plugin . '.php');
45         if (function_exists($plugin . '_install')) {
46                 $func = $plugin . '_install';
47                 $func();
48
49                 $plugin_admin = (function_exists($plugin."_plugin_admin") ? 1 : 0);
50
51                 dba::insert('addon', array('name' => $plugin, 'installed' => true,
52                                         'timestamp' => $t, 'plugin_admin' => $plugin_admin));
53
54                 // we can add the following with the previous SQL
55                 // once most site tables have been updated.
56                 // This way the system won't fall over dead during the update.
57
58                 if (file_exists('addon/' . $plugin . '/.hidden')) {
59                         dba::update('addon', array('hidden' => true), array('name' => $plugin));
60                 }
61                 return true;
62         } else {
63                 logger("Addons: FAILED installing " . $plugin);
64                 return false;
65         }
66 }
67
68 // reload all updated plugins
69
70 function reload_plugins() {
71         $plugins = Config::get('system', 'addon');
72         if (strlen($plugins)) {
73
74                 $r = q("SELECT * FROM `addon` WHERE `installed` = 1");
75                 if (DBM::is_result($r)) {
76                         $installed = $r;
77                 } else {
78                         $installed = array();
79                 }
80
81                 $parr = explode(',',$plugins);
82
83                 if (count($parr)) {
84                         foreach ($parr as $pl) {
85
86                                 $pl = trim($pl);
87
88                                 $fname = 'addon/' . $pl . '/' . $pl . '.php';
89
90                                 if (file_exists($fname)) {
91                                         $t = @filemtime($fname);
92                                         foreach ($installed as $i) {
93                                                 if (($i['name'] == $pl) && ($i['timestamp'] != $t)) {
94                                                         logger('Reloading plugin: ' . $i['name']);
95                                                         @include_once($fname);
96
97                                                         if (function_exists($pl . '_uninstall')) {
98                                                                 $func = $pl . '_uninstall';
99                                                                 $func();
100                                                         }
101                                                         if (function_exists($pl . '_install')) {
102                                                                 $func = $pl . '_install';
103                                                                 $func();
104                                                         }
105                                                         dba::update('addon', array('timestamp' => $t), array('id' => $i['id']));
106                                                 }
107                                         }
108                                 }
109                         }
110                 }
111         }
112
113 }
114
115 /**
116  * @brief check if addon is enabled
117  *
118  * @param string $plugin
119  * @return boolean
120  */
121 function plugin_enabled($plugin) {
122         return dba::exists('addon', array('installed' => true, 'name' => $plugin));
123 }
124
125
126 /**
127  * @brief registers a hook.
128  *
129  * @param string $hook the name of the hook
130  * @param string $file the name of the file that hooks into
131  * @param string $function the name of the function that the hook will call
132  * @param int $priority A priority (defaults to 0)
133  * @return mixed|bool
134  */
135 function register_hook($hook, $file, $function, $priority=0) {
136         $condition = array('hook' => $hook, 'file' => $file, 'function' => $function);
137         $exists = dba::exists('hook', $condition);
138         if ($exists) {
139                 return true;
140         }
141
142         $r = dba::insert('hook', array('hook' => $hook, 'file' => $file, 'function' => $function, 'priority' => $priority));
143
144         return $r;
145 }
146
147 /**
148  * @brief unregisters a hook.
149  *
150  * @param string $hook the name of the hook
151  * @param string $file the name of the file that hooks into
152  * @param string $function the name of the function that the hook called
153  * @return array
154  */
155 function unregister_hook($hook, $file, $function) {
156         $condition = array('hook' => $hook, 'file' => $file, 'function' => $function);
157         $r = dba::delete('hook', $condition);
158         return $r;
159 }
160
161
162 function load_hooks() {
163         $a = get_app();
164         $a->hooks = array();
165         $r = dba::select('hook', array('hook', 'file', 'function'), array(), array('order' => array('priority' => 'desc', 'file')));
166
167         while ($rr = dba::fetch($r)) {
168                 if (! array_key_exists($rr['hook'],$a->hooks)) {
169                         $a->hooks[$rr['hook']] = array();
170                 }
171                 $a->hooks[$rr['hook']][] = array($rr['file'],$rr['function']);
172         }
173         dba::close($r);
174 }
175
176 /**
177  * @brief Calls a hook.
178  *
179  * Use this function when you want to be able to allow a hook to manipulate
180  * the provided data.
181  *
182  * @param string $name of the hook to call
183  * @param string|array &$data to transmit to the callback handler
184  */
185 function call_hooks($name, &$data = null) {
186         $stamp1 = microtime(true);
187
188         $a = get_app();
189
190         if (is_array($a->hooks) && array_key_exists($name, $a->hooks))
191                 foreach ($a->hooks[$name] as $hook)
192                         call_single_hook($a, $name, $hook, $data);
193 }
194
195 /**
196  * @brief Calls a single hook.
197  *
198  * @param string $name of the hook to call
199  * @param array $hook Hook data
200  * @param string|array &$data to transmit to the callback handler
201  */
202 function call_single_hook($a, $name, $hook, &$data = null) {
203         // Don't run a theme's hook if the user isn't using the theme
204         if (strpos($hook[0], 'view/theme/') !== false && strpos($hook[0], 'view/theme/'.current_theme()) === false)
205                 return;
206
207         @include_once($hook[0]);
208         if (function_exists($hook[1])) {
209                 $func = $hook[1];
210                 $func($a, $data);
211         } else {
212                 // remove orphan hooks
213                 $condition = array('hook' => $name, 'file' => $hook[0], 'function' => $hook[1]);
214                 dba::delete('hook', $condition);
215         }
216 }
217
218 //check if an app_menu hook exist for plugin $name.
219 //Return true if the plugin is an app
220 function plugin_is_app($name) {
221         $a = get_app();
222
223         if (is_array($a->hooks) && (array_key_exists('app_menu',$a->hooks))) {
224                 foreach ($a->hooks['app_menu'] as $hook) {
225                         if ($hook[0] == 'addon/'.$name.'/'.$name.'.php')
226                                 return true;
227                 }
228         }
229
230         return false;
231 }
232
233 /**
234  * @brief Parse plugin comment in search of plugin infos.
235  *
236  * like
237  * \code
238  *...* Name: Plugin
239  *   * Description: A plugin which plugs in
240  * . * Version: 1.2.3
241  *   * Author: John <profile url>
242  *   * Author: Jane <email>
243  *   *
244  *  *\endcode
245  * @param string $plugin the name of the plugin
246  * @return array with the plugin information
247  */
248
249 function get_plugin_info($plugin) {
250
251         $a = get_app();
252
253         $info=Array(
254                 'name' => $plugin,
255                 'description' => "",
256                 'author' => array(),
257                 'version' => "",
258                 'status' => ""
259         );
260
261         if (!is_file("addon/$plugin/$plugin.php")) return $info;
262
263         $stamp1 = microtime(true);
264         $f = file_get_contents("addon/$plugin/$plugin.php");
265         $a->save_timestamp($stamp1, "file");
266
267         $r = preg_match("|/\*.*\*/|msU", $f, $m);
268
269         if ($r) {
270                 $ll = explode("\n", $m[0]);
271                 foreach ( $ll as $l ) {
272                         $l = trim($l,"\t\n\r */");
273                         if ($l != "") {
274                                 list($k,$v) = array_map("trim", explode(":",$l,2));
275                                 $k= strtolower($k);
276                                 if ($k == "author") {
277                                         $r=preg_match("|([^<]+)<([^>]+)>|", $v, $m);
278                                         if ($r) {
279                                                 $info['author'][] = array('name'=>$m[1], 'link'=>$m[2]);
280                                         } else {
281                                                 $info['author'][] = array('name'=>$v);
282                                         }
283                                 } else {
284                                         if (array_key_exists($k,$info)) {
285                                                 $info[$k]=$v;
286                                         }
287                                 }
288
289                         }
290                 }
291
292         }
293         return $info;
294 }
295
296
297 /**
298  * @brief Parse theme comment in search of theme infos.
299  *
300  * like
301  * \code
302  * ..* Name: My Theme
303  *   * Description: My Cool Theme
304  * . * Version: 1.2.3
305  *   * Author: John <profile url>
306  *   * Maintainer: Jane <profile url>
307  *   *
308  * \endcode
309  * @param string $theme the name of the theme
310  * @return array
311  */
312
313 function get_theme_info($theme) {
314         $info=Array(
315                 'name' => $theme,
316                 'description' => "",
317                 'author' => array(),
318                 'maintainer' => array(),
319                 'version' => "",
320                 'credits' => "",
321                 'experimental' => false,
322                 'unsupported' => false
323         );
324
325         if (file_exists("view/theme/$theme/experimental"))
326                 $info['experimental'] = true;
327         if (file_exists("view/theme/$theme/unsupported"))
328                 $info['unsupported'] = true;
329
330         if (!is_file("view/theme/$theme/theme.php")) return $info;
331
332         $a = get_app();
333         $stamp1 = microtime(true);
334         $f = file_get_contents("view/theme/$theme/theme.php");
335         $a->save_timestamp($stamp1, "file");
336
337         $r = preg_match("|/\*.*\*/|msU", $f, $m);
338
339         if ($r) {
340                 $ll = explode("\n", $m[0]);
341                 foreach ( $ll as $l ) {
342                         $l = trim($l,"\t\n\r */");
343                         if ($l != "") {
344                                 list($k,$v) = array_map("trim", explode(":",$l,2));
345                                 $k= strtolower($k);
346                                 if ($k == "author") {
347
348                                         $r=preg_match("|([^<]+)<([^>]+)>|", $v, $m);
349                                         if ($r) {
350                                                 $info['author'][] = array('name'=>$m[1], 'link'=>$m[2]);
351                                         } else {
352                                                 $info['author'][] = array('name'=>$v);
353                                         }
354                                 } elseif ($k == "maintainer") {
355                                         $r=preg_match("|([^<]+)<([^>]+)>|", $v, $m);
356                                         if ($r) {
357                                                 $info['maintainer'][] = array('name'=>$m[1], 'link'=>$m[2]);
358                                         } else {
359                                                 $info['maintainer'][] = array('name'=>$v);
360                                         }
361                                 } else {
362                                         if (array_key_exists($k,$info)) {
363                                                 $info[$k]=$v;
364                                         }
365                                 }
366
367                         }
368                 }
369
370         }
371         return $info;
372 }
373
374 /**
375  * @brief Returns the theme's screenshot.
376  *
377  * The screenshot is expected as view/theme/$theme/screenshot.[png|jpg].
378  *
379  * @param sring $theme The name of the theme
380  * @return string
381  */
382 function get_theme_screenshot($theme) {
383         $exts = array('.png','.jpg');
384         foreach ($exts as $ext) {
385                 if (file_exists('view/theme/' . $theme . '/screenshot' . $ext)) {
386                         return(System::baseUrl() . '/view/theme/' . $theme . '/screenshot' . $ext);
387                 }
388         }
389         return(System::baseUrl() . '/images/blank.png');
390 }
391
392 // install and uninstall theme
393 function uninstall_theme($theme) {
394         logger("Addons: uninstalling theme " . $theme);
395
396         include_once("view/theme/$theme/theme.php");
397         if (function_exists("{$theme}_uninstall")) {
398                 $func = "{$theme}_uninstall";
399                 $func();
400         }
401 }
402
403 function install_theme($theme) {
404         // silently fail if theme was removed
405
406         if (! file_exists("view/theme/$theme/theme.php")) {
407                 return false;
408         }
409
410         logger("Addons: installing theme $theme");
411
412         include_once("view/theme/$theme/theme.php");
413
414         if (function_exists("{$theme}_install")) {
415                 $func = "{$theme}_install";
416                 $func();
417                 return true;
418         } else {
419                 logger("Addons: FAILED installing theme $theme");
420                 return false;
421         }
422
423 }
424
425 /**
426  * @brief Get the full path to relevant theme files by filename
427  *
428  * This function search in the theme directory (and if not present in global theme directory)
429  * if there is a directory with the file extension and  for a file with the given
430  * filename.
431  *
432  * @param string $file Filename
433  * @param string $root Full root path
434  * @return string Path to the file or empty string if the file isn't found
435  */
436 function theme_include($file, $root = '') {
437         $file = basename($file);
438
439         // Make sure $root ends with a slash / if it's not blank
440         if ($root !== '' && $root[strlen($root)-1] !== '/') {
441                 $root = $root . '/';
442         }
443         $theme_info = get_app()->theme_info;
444         if (is_array($theme_info) && array_key_exists('extends',$theme_info)) {
445                 $parent = $theme_info['extends'];
446         } else {
447                 $parent = 'NOPATH';
448         }
449         $theme = current_theme();
450         $thname = $theme;
451         $ext = substr($file,strrpos($file,'.')+1);
452         $paths = array(
453                 "{$root}view/theme/$thname/$ext/$file",
454                 "{$root}view/theme/$parent/$ext/$file",
455                 "{$root}view/$ext/$file",
456         );
457         foreach ($paths as $p) {
458                 // strpos() is faster than strstr when checking if one string is in another (http://php.net/manual/en/function.strstr.php)
459                 if (strpos($p,'NOPATH') !== false) {
460                         continue;
461                 } elseif (file_exists($p)) {
462                         return $p;
463                 }
464         }
465         return '';
466 }