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();
13 private $last_registered;
14 private static $instance;
16 const API_VERSION
= 2;
18 // Hooks marked with *1 are run in global context and available
19 // to plugins loaded in config.php only
21 const HOOK_ARTICLE_BUTTON
= 1;
22 const HOOK_ARTICLE_FILTER
= 2;
23 const HOOK_PREFS_TAB
= 3;
24 const HOOK_PREFS_TAB_SECTION
= 4;
25 const HOOK_PREFS_TABS
= 5;
26 const HOOK_FEED_PARSED
= 6;
27 const HOOK_UPDATE_TASK
= 7; // *1
28 const HOOK_AUTH_USER
= 8;
29 const HOOK_HOTKEY_MAP
= 9;
30 const HOOK_RENDER_ARTICLE
= 10;
31 const HOOK_RENDER_ARTICLE_CDM
= 11;
32 const HOOK_FEED_FETCHED
= 12;
33 const HOOK_SANITIZE
= 13;
34 const HOOK_RENDER_ARTICLE_API
= 14;
35 const HOOK_TOOLBAR_BUTTON
= 15;
36 const HOOK_ACTION_ITEM
= 16;
37 const HOOK_HEADLINE_TOOLBAR_BUTTON
= 17;
38 const HOOK_HOTKEY_INFO
= 18;
39 const HOOK_ARTICLE_LEFT_BUTTON
= 19;
40 const HOOK_PREFS_EDIT_FEED
= 20;
41 const HOOK_PREFS_SAVE_FEED
= 21;
42 const HOOK_FETCH_FEED
= 22;
43 const HOOK_QUERY_HEADLINES
= 23;
44 const HOOK_HOUSE_KEEPING
= 24; // *1
45 const HOOK_SEARCH
= 25;
46 const HOOK_FORMAT_ENCLOSURES
= 26;
47 const HOOK_SUBSCRIBE_FEED
= 27;
48 const HOOK_HEADLINES_BEFORE
= 28;
49 const HOOK_RENDER_ENCLOSURE
= 29;
52 const KIND_SYSTEM
= 2;
55 function __construct() {
56 $this->dbh
= Db
::get();
58 $this->storage
= array();
61 private function __clone() {
65 public static function getInstance() {
66 if (self
::$instance == null)
67 self
::$instance = new self();
69 return self
::$instance;
72 private function register_plugin($name, $plugin) {
73 //array_push($this->plugins, $plugin);
74 $this->plugins
[$name] = $plugin;
77 // needed for compatibility with API 1
86 function get_plugin_names() {
89 foreach ($this->plugins
as $p) {
90 array_push($names, get_class($p));
96 function get_plugins() {
97 return $this->plugins
;
100 function get_plugin($name) {
101 return $this->plugins
[$name];
104 function run_hooks($type, $method, $args) {
105 foreach ($this->get_hooks($type) as $hook) {
106 $hook->$method($args);
110 function add_hook($type, $sender) {
111 if (!is_array($this->hooks
[$type])) {
112 $this->hooks
[$type] = array();
115 array_push($this->hooks
[$type], $sender);
118 function del_hook($type, $sender) {
119 if (is_array($this->hooks
[$type])) {
120 $key = array_Search($sender, $this->hooks
[$type]);
121 if ($key !== FALSE) {
122 unset($this->hooks
[$type][$key]);
127 function get_hooks($type) {
128 if (isset($this->hooks
[$type])) {
129 return $this->hooks
[$type];
134 function load_all($kind, $owner_uid = false) {
136 $plugins = array_merge(glob("plugins/*"), glob("plugins.local/*"));
137 $plugins = array_filter($plugins, "is_dir");
138 $plugins = array_map("basename", $plugins);
142 $this->load(join(",", $plugins), $kind, $owner_uid);
145 function load($classlist, $kind, $owner_uid = false) {
146 $plugins = explode(",", $classlist);
148 $this->owner_uid
= (int) $owner_uid;
150 foreach ($plugins as $class) {
151 $class = trim($class);
152 $class_file = strtolower(basename($class));
154 if (!is_dir(__DIR__
."/../plugins/$class_file") &&
155 !is_dir(__DIR__
."/../plugins.local/$class_file")) continue;
157 // try system plugin directory first
158 $file = __DIR__
. "/../plugins/$class_file/init.php";
160 if (!file_exists($file)) {
161 $file = __DIR__
. "/../plugins.local/$class_file/init.php";
164 if (!isset($this->plugins
[$class])) {
165 if (file_exists($file)) require_once $file;
167 if (class_exists($class) && is_subclass_of($class, "Plugin")) {
168 $plugin = new $class($this);
170 $plugin_api = $plugin->api_version();
172 if ($plugin_api < PluginHost
::API_VERSION
) {
173 user_error("Plugin $class is not compatible with current API version (need: " . PluginHost
::API_VERSION
. ", got: $plugin_api)", E_USER_WARNING
);
177 $this->last_registered
= $class;
180 case $this::KIND_SYSTEM
:
181 if ($this->is_system($plugin)) {
182 $plugin->init($this);
183 $this->register_plugin($class, $plugin);
186 case $this::KIND_USER
:
187 if (!$this->is_system($plugin)) {
188 $plugin->init($this);
189 $this->register_plugin($class, $plugin);
192 case $this::KIND_ALL
:
193 $plugin->init($this);
194 $this->register_plugin($class, $plugin);
202 function is_system($plugin) {
203 $about = $plugin->about();
208 // only system plugins are allowed to modify routing
209 function add_handler($handler, $method, $sender) {
210 $handler = str_replace("-", "_", strtolower($handler));
211 $method = strtolower($method);
213 if ($this->is_system($sender)) {
214 if (!is_array($this->handlers
[$handler])) {
215 $this->handlers
[$handler] = array();
218 $this->handlers
[$handler][$method] = $sender;
222 function del_handler($handler, $method, $sender) {
223 $handler = str_replace("-", "_", strtolower($handler));
224 $method = strtolower($method);
226 if ($this->is_system($sender)) {
227 unset($this->handlers
[$handler][$method]);
231 function lookup_handler($handler, $method) {
232 $handler = str_replace("-", "_", strtolower($handler));
233 $method = strtolower($method);
235 if (is_array($this->handlers
[$handler])) {
236 if (isset($this->handlers
[$handler]["*"])) {
237 return $this->handlers
[$handler]["*"];
239 return $this->handlers
[$handler][$method];
246 function add_command($command, $description, $sender, $suffix = "", $arghelp = "") {
247 $command = str_replace("-", "_", strtolower($command));
249 $this->commands
[$command] = array("description" => $description,
251 "arghelp" => $arghelp,
255 function del_command($command) {
256 $command = "-" . strtolower($command);
258 unset($this->commands
[$command]);
261 function lookup_command($command) {
262 $command = "-" . strtolower($command);
264 if (is_array($this->commands
[$command])) {
265 return $this->commands
[$command]["class"];
273 function get_commands() {
274 return $this->commands
;
277 function run_commands($args) {
278 foreach ($this->get_commands() as $command => $data) {
279 if (isset($args[$command])) {
280 $command = str_replace("-", "", $command);
281 $data["class"]->$command($args);
286 function load_data($force = false) {
287 if ($this->owner_uid
) {
288 $result = $this->dbh
->query("SELECT name, content FROM ttrss_plugin_storage
289 WHERE owner_uid = '".$this->owner_uid
."'");
291 while ($line = $this->dbh
->fetch_assoc($result)) {
292 $this->storage
[$line["name"]] = unserialize($line["content"]);
297 private function save_data($plugin) {
298 if ($this->owner_uid
) {
299 $plugin = $this->dbh
->escape_string($plugin);
301 $this->dbh
->query("BEGIN");
303 $result = $this->dbh
->query("SELECT id FROM ttrss_plugin_storage WHERE
304 owner_uid= '".$this->owner_uid
."' AND name = '$plugin'");
306 if (!isset($this->storage
[$plugin]))
307 $this->storage
[$plugin] = array();
309 $content = $this->dbh
->escape_string(serialize($this->storage
[$plugin]),
312 if ($this->dbh
->num_rows($result) != 0) {
313 $this->dbh
->query("UPDATE ttrss_plugin_storage SET content = '$content'
314 WHERE owner_uid= '".$this->owner_uid
."' AND name = '$plugin'");
317 $this->dbh
->query("INSERT INTO ttrss_plugin_storage
318 (name,owner_uid,content) VALUES
319 ('$plugin','".$this->owner_uid
."','$content')");
322 $this->dbh
->query("COMMIT");
326 function set($sender, $name, $value, $sync = true) {
327 $idx = get_class($sender);
329 if (!isset($this->storage
[$idx]))
330 $this->storage
[$idx] = array();
332 $this->storage
[$idx][$name] = $value;
334 if ($sync) $this->save_data(get_class($sender));
337 function get($sender, $name, $default_value = false) {
338 $idx = get_class($sender);
340 if (isset($this->storage
[$idx][$name])) {
341 return $this->storage
[$idx][$name];
343 return $default_value;
347 function get_all($sender) {
348 $idx = get_class($sender);
350 return $this->storage
[$idx];
353 function clear_data($sender) {
354 if ($this->owner_uid
) {
355 $idx = get_class($sender);
357 unset($this->storage
[$idx]);
359 $this->dbh
->query("DELETE FROM ttrss_plugin_storage WHERE name = '$idx'
360 AND owner_uid = " . $this->owner_uid
);
364 function set_debug($debug) {
365 $this->debug
= $debug;
368 function get_debug() {
372 // Plugin feed functions are *EXPERIMENTAL*!
374 // cat_id: only -1 is supported (Special)
375 function add_feed($cat_id, $title, $icon, $sender) {
376 if (!$this->feeds
[$cat_id]) $this->feeds
[$cat_id] = array();
378 $id = count($this->feeds
[$cat_id]);
380 array_push($this->feeds
[$cat_id],
381 array('id' => $id, 'title' => $title, 'sender' => $sender, 'icon' => $icon));
386 function get_feeds($cat_id) {
387 return $this->feeds
[$cat_id];
390 // convert feed_id (e.g. -129) to pfeed_id first
391 function get_feed_handler($pfeed_id) {
392 foreach ($this->feeds
as $cat) {
393 foreach ($cat as $feed) {
394 if ($feed['id'] == $pfeed_id) {
395 return $feed['sender'];
401 static function pfeed_to_feed_id($label) {
402 return PLUGIN_FEED_BASE_INDEX
- 1 - abs($label);
405 static function feed_to_pfeed_id($feed) {
406 return PLUGIN_FEED_BASE_INDEX
- 1 +
abs($feed);
409 function add_api_method($name, $sender) {
410 if ($this->is_system($sender)) {
411 $this->api_methods
[strtolower($name)] = $sender;
415 function get_api_method($name) {
416 return $this->api_methods
[$name];