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