]> git.wh0rd.org Git - tt-rss.git/blob - classes/pluginhost.php
afe4da49ef6a652fa1097dacd00b7a41731c3bd1
[tt-rss.git] / classes / pluginhost.php
1 <?php
2 class PluginHost {
3         private $dbh;
4         private $hooks = array();
5         private $plugins = array();
6         private $handlers = array();
7         private $commands = array();
8         private $storage = array();
9         private $feeds = array();
10         private $api_methods = array();
11         private $plugin_actions = array();
12         private $owner_uid;
13         private $debug;
14         private $last_registered;
15         private static $instance;
16
17         const API_VERSION = 2;
18
19         // Hooks marked with *1 are run in global context and available
20         // to plugins loaded in config.php only
21
22         const HOOK_ARTICLE_BUTTON = 1;
23         const HOOK_ARTICLE_FILTER = 2;
24         const HOOK_PREFS_TAB = 3;
25         const HOOK_PREFS_TAB_SECTION = 4;
26         const HOOK_PREFS_TABS = 5;
27         const HOOK_FEED_PARSED = 6;
28         const HOOK_UPDATE_TASK = 7; // *1
29         const HOOK_AUTH_USER = 8;
30         const HOOK_HOTKEY_MAP = 9;
31         const HOOK_RENDER_ARTICLE = 10;
32         const HOOK_RENDER_ARTICLE_CDM = 11;
33         const HOOK_FEED_FETCHED = 12;
34         const HOOK_SANITIZE = 13;
35         const HOOK_RENDER_ARTICLE_API = 14;
36         const HOOK_TOOLBAR_BUTTON = 15;
37         const HOOK_ACTION_ITEM = 16;
38         const HOOK_HEADLINE_TOOLBAR_BUTTON = 17;
39         const HOOK_HOTKEY_INFO = 18;
40         const HOOK_ARTICLE_LEFT_BUTTON = 19;
41         const HOOK_PREFS_EDIT_FEED = 20;
42         const HOOK_PREFS_SAVE_FEED = 21;
43         const HOOK_FETCH_FEED = 22;
44         const HOOK_QUERY_HEADLINES = 23;
45         const HOOK_HOUSE_KEEPING = 24; // *1
46         const HOOK_SEARCH = 25;
47         const HOOK_FORMAT_ENCLOSURES = 26;
48         const HOOK_SUBSCRIBE_FEED = 27;
49         const HOOK_HEADLINES_BEFORE = 28;
50         const HOOK_RENDER_ENCLOSURE = 29;
51         const HOOK_ARTICLE_FILTER_ACTION = 30;
52         const HOOK_ARTICLE_EXPORT_FEED = 31;
53         const HOOK_MAIN_TOOLBAR_BUTTON = 32;
54         const HOOK_ENCLOSURE_ENTRY = 33;
55         const HOOK_FORMAT_ARTICLE = 34;
56         const HOOK_FORMAT_ARTICLE_CDM = 35;
57
58         const KIND_ALL = 1;
59         const KIND_SYSTEM = 2;
60         const KIND_USER = 3;
61
62         function __construct() {
63                 $this->dbh = Db::get();
64
65                 $this->storage = array();
66         }
67
68         private function __clone() {
69                 //
70         }
71
72         public static function getInstance() {
73                 if (self::$instance == null)
74                         self::$instance = new self();
75
76                 return self::$instance;
77         }
78
79         private function register_plugin($name, $plugin) {
80                 //array_push($this->plugins, $plugin);
81                 $this->plugins[$name] = $plugin;
82         }
83
84         // needed for compatibility with API 1
85         function get_link() {
86                 return false;
87         }
88
89         function get_dbh() {
90                 return $this->dbh;
91         }
92
93         function get_plugin_names() {
94                 $names = array();
95
96                 foreach ($this->plugins as $p) {
97                         array_push($names, get_class($p));
98                 }
99
100                 return $names;
101         }
102
103         function get_plugins() {
104                 return $this->plugins;
105         }
106
107         function get_plugin($name) {
108                 return $this->plugins[strtolower($name)];
109         }
110
111         function run_hooks($type, $method, $args) {
112                 foreach ($this->get_hooks($type) as $hook) {
113                         $hook->$method($args);
114                 }
115         }
116
117         function add_hook($type, $sender) {
118                 if (!is_array($this->hooks[$type])) {
119                         $this->hooks[$type] = array();
120                 }
121
122                 array_push($this->hooks[$type], $sender);
123         }
124
125         function del_hook($type, $sender) {
126                 if (is_array($this->hooks[$type])) {
127                         $key = array_Search($sender, $this->hooks[$type]);
128                         if ($key !== FALSE) {
129                                 unset($this->hooks[$type][$key]);
130                         }
131                 }
132         }
133
134         function get_hooks($type) {
135                 if (isset($this->hooks[$type])) {
136                         return $this->hooks[$type];
137                 } else {
138                         return array();
139                 }
140         }
141         function load_all($kind, $owner_uid = false, $skip_init = false) {
142
143                 $plugins = array_merge(glob("plugins/*"), glob("plugins.local/*"));
144                 $plugins = array_filter($plugins, "is_dir");
145                 $plugins = array_map("basename", $plugins);
146
147                 asort($plugins);
148
149                 $this->load(join(",", $plugins), $kind, $owner_uid, $skip_init);
150         }
151
152         function load($classlist, $kind, $owner_uid = false, $skip_init = false) {
153                 $plugins = explode(",", $classlist);
154
155                 $this->owner_uid = (int) $owner_uid;
156
157                 foreach ($plugins as $class) {
158                         $class = trim($class);
159                         $class_file = strtolower(basename($class));
160
161                         if (!is_dir(__DIR__."/../plugins/$class_file") &&
162                                         !is_dir(__DIR__."/../plugins.local/$class_file")) continue;
163
164                         // try system plugin directory first
165                         $file = __DIR__ . "/../plugins/$class_file/init.php";
166
167                         if (!file_exists($file)) {
168                                 $file = __DIR__ . "/../plugins.local/$class_file/init.php";
169                         }
170
171                         if (!isset($this->plugins[$class])) {
172                                 if (file_exists($file)) require_once $file;
173
174                                 if (class_exists($class) && is_subclass_of($class, "Plugin")) {
175                                         $plugin = new $class($this);
176
177                                         $plugin_api = $plugin->api_version();
178
179                                         if ($plugin_api < PluginHost::API_VERSION) {
180                                                 user_error("Plugin $class is not compatible with current API version (need: " . PluginHost::API_VERSION . ", got: $plugin_api)", E_USER_WARNING);
181                                                 continue;
182                                         }
183
184                                         $this->last_registered = $class;
185
186                                         switch ($kind) {
187                                         case $this::KIND_SYSTEM:
188                                                 if ($this->is_system($plugin)) {
189                                                         if (!$skip_init) $plugin->init($this);
190                                                         $this->register_plugin($class, $plugin);
191                                                 }
192                                                 break;
193                                         case $this::KIND_USER:
194                                                 if (!$this->is_system($plugin)) {
195                                                         if (!$skip_init) $plugin->init($this);
196                                                         $this->register_plugin($class, $plugin);
197                                                 }
198                                                 break;
199                                         case $this::KIND_ALL:
200                                                 if (!$skip_init) $plugin->init($this);
201                                                 $this->register_plugin($class, $plugin);
202                                                 break;
203                                         }
204                                 }
205                         }
206                 }
207         }
208
209         function is_system($plugin) {
210                 $about = $plugin->about();
211
212                 return @$about[3];
213         }
214
215         // only system plugins are allowed to modify routing
216         function add_handler($handler, $method, $sender) {
217                 $handler = str_replace("-", "_", strtolower($handler));
218                 $method = strtolower($method);
219
220                 if ($this->is_system($sender)) {
221                         if (!is_array($this->handlers[$handler])) {
222                                 $this->handlers[$handler] = array();
223                         }
224
225                         $this->handlers[$handler][$method] = $sender;
226                 }
227         }
228
229         function del_handler($handler, $method, $sender) {
230                 $handler = str_replace("-", "_", strtolower($handler));
231                 $method = strtolower($method);
232
233                 if ($this->is_system($sender)) {
234                         unset($this->handlers[$handler][$method]);
235                 }
236         }
237
238         function lookup_handler($handler, $method) {
239                 $handler = str_replace("-", "_", strtolower($handler));
240                 $method = strtolower($method);
241
242                 if (is_array($this->handlers[$handler])) {
243                         if (isset($this->handlers[$handler]["*"])) {
244                                 return $this->handlers[$handler]["*"];
245                         } else {
246                                 return $this->handlers[$handler][$method];
247                         }
248                 }
249
250                 return false;
251         }
252
253         function add_command($command, $description, $sender, $suffix = "", $arghelp = "") {
254                 $command = str_replace("-", "_", strtolower($command));
255
256                 $this->commands[$command] = array("description" => $description,
257                         "suffix" => $suffix,
258                         "arghelp" => $arghelp,
259                         "class" => $sender);
260         }
261
262         function del_command($command) {
263                 $command = "-" . strtolower($command);
264
265                 unset($this->commands[$command]);
266         }
267
268         function lookup_command($command) {
269                 $command = "-" . strtolower($command);
270
271                 if (is_array($this->commands[$command])) {
272                         return $this->commands[$command]["class"];
273                 } else {
274                         return false;
275                 }
276
277                 return false;
278         }
279
280         function get_commands() {
281                 return $this->commands;
282         }
283
284         function run_commands($args) {
285                 foreach ($this->get_commands() as $command => $data) {
286                         if (isset($args[$command])) {
287                                 $command = str_replace("-", "", $command);
288                                 $data["class"]->$command($args);
289                         }
290                 }
291         }
292
293         function load_data() {
294                 if ($this->owner_uid)  {
295                         $result = $this->dbh->query("SELECT name, content FROM ttrss_plugin_storage
296                                 WHERE owner_uid = '".$this->owner_uid."'");
297
298                         while ($line = $this->dbh->fetch_assoc($result)) {
299                                 $this->storage[$line["name"]] = unserialize($line["content"]);
300                         }
301                 }
302         }
303
304         private function save_data($plugin) {
305                 if ($this->owner_uid) {
306                         $plugin = $this->dbh->escape_string($plugin);
307
308                         $this->dbh->query("BEGIN");
309
310                         $result = $this->dbh->query("SELECT id FROM ttrss_plugin_storage WHERE
311                                 owner_uid= '".$this->owner_uid."' AND name = '$plugin'");
312
313                         if (!isset($this->storage[$plugin]))
314                                 $this->storage[$plugin] = array();
315
316                         $content = $this->dbh->escape_string(serialize($this->storage[$plugin]),
317                                 false);
318
319                         if ($this->dbh->num_rows($result) != 0) {
320                                 $this->dbh->query("UPDATE ttrss_plugin_storage SET content = '$content'
321                                         WHERE owner_uid= '".$this->owner_uid."' AND name = '$plugin'");
322
323                         } else {
324                                 $this->dbh->query("INSERT INTO ttrss_plugin_storage
325                                         (name,owner_uid,content) VALUES
326                                         ('$plugin','".$this->owner_uid."','$content')");
327                         }
328
329                         $this->dbh->query("COMMIT");
330                 }
331         }
332
333         function set($sender, $name, $value, $sync = true) {
334                 $idx = get_class($sender);
335
336                 if (!isset($this->storage[$idx]))
337                         $this->storage[$idx] = array();
338
339                 $this->storage[$idx][$name] = $value;
340
341                 if ($sync) $this->save_data(get_class($sender));
342         }
343
344         function get($sender, $name, $default_value = false) {
345                 $idx = get_class($sender);
346
347                 if (isset($this->storage[$idx][$name])) {
348                         return $this->storage[$idx][$name];
349                 } else {
350                         return $default_value;
351                 }
352         }
353
354         function get_all($sender) {
355                 $idx = get_class($sender);
356
357                 return $this->storage[$idx];
358         }
359
360         function clear_data($sender) {
361                 if ($this->owner_uid) {
362                         $idx = get_class($sender);
363
364                         unset($this->storage[$idx]);
365
366                         $this->dbh->query("DELETE FROM ttrss_plugin_storage WHERE name = '$idx'
367                                 AND owner_uid = " . $this->owner_uid);
368                 }
369         }
370
371         function set_debug($debug) {
372                 $this->debug = $debug;
373         }
374
375         function get_debug() {
376                 return $this->debug;
377         }
378
379         // Plugin feed functions are *EXPERIMENTAL*!
380
381         // cat_id: only -1 is supported (Special)
382         function add_feed($cat_id, $title, $icon, $sender) {
383                 if (!$this->feeds[$cat_id]) $this->feeds[$cat_id] = array();
384
385                 $id = count($this->feeds[$cat_id]);
386
387                 array_push($this->feeds[$cat_id],
388                         array('id' => $id, 'title' => $title, 'sender' => $sender, 'icon' => $icon));
389
390                 return $id;
391         }
392
393         function get_feeds($cat_id) {
394                 return $this->feeds[$cat_id];
395         }
396
397         // convert feed_id (e.g. -129) to pfeed_id first
398         function get_feed_handler($pfeed_id) {
399                 foreach ($this->feeds as $cat) {
400                         foreach ($cat as $feed) {
401                                 if ($feed['id'] == $pfeed_id) {
402                                         return $feed['sender'];
403                                 }
404                         }
405                 }
406         }
407
408         static function pfeed_to_feed_id($label) {
409                 return PLUGIN_FEED_BASE_INDEX - 1 - abs($label);
410         }
411
412         static function feed_to_pfeed_id($feed) {
413                 return PLUGIN_FEED_BASE_INDEX - 1 + abs($feed);
414         }
415
416         function add_api_method($name, $sender) {
417                 if ($this->is_system($sender)) {
418                         $this->api_methods[strtolower($name)] = $sender;
419                 }
420         }
421
422         function get_api_method($name) {
423                 return $this->api_methods[$name];
424         }
425
426         function add_filter_action($sender, $action_name, $action_desc) {
427                 $sender_class = get_class($sender);
428
429                 if (!isset($this->plugin_actions[$sender_class]))
430                         $this->plugin_actions[$sender_class] = array();
431
432                 array_push($this->plugin_actions[$sender_class],
433                         array("action" => $action_name, "description" => $action_desc, "sender" => $sender));
434         }
435
436         function get_filter_actions() {
437                 return $this->plugin_actions;
438         }
439 }