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