aboutsummaryrefslogtreecommitdiffhomepage
path: root/application
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2015-07-15 11:42:15 +0200
committerArthurHoaro <arthur@hoa.ro>2015-11-07 15:27:17 +0100
commit6fc14d530369740d27d6bd641369d4f5f5f04080 (patch)
tree2da553378e8f0ff367dcb677d6f519d1fb3e803c /application
parent38bedfbbcdd2a40e9f04f5753e0fd6f4fd513c21 (diff)
downloadShaarli-6fc14d530369740d27d6bd641369d4f5f5f04080.tar.gz
Shaarli-6fc14d530369740d27d6bd641369d4f5f5f04080.tar.zst
Shaarli-6fc14d530369740d27d6bd641369d4f5f5f04080.zip
Plugin system - CORE
see shaarli/Shaarli#275
Diffstat (limited to 'application')
-rw-r--r--[-rwxr-xr-x]application/Config.php263
-rw-r--r--application/PluginManager.php184
-rw-r--r--application/Router.php105
3 files changed, 423 insertions, 129 deletions
diff --git a/application/Config.php b/application/Config.php
index ec799d7f..c71ef68c 100755..100644
--- a/application/Config.php
+++ b/application/Config.php
@@ -1,129 +1,134 @@
1<?php 1<?php
2/** 2/**
3 * Functions related to configuration management. 3 * Functions related to configuration management.
4 */ 4 */
5 5
6/** 6/**
7 * Re-write configuration file according to given array. 7 * Re-write configuration file according to given array.
8 * Requires mandatory fields listed in $MANDATORY_FIELDS. 8 * Requires mandatory fields listed in $MANDATORY_FIELDS.
9 * 9 *
10 * @param array $config contains all configuration fields. 10 * @param array $config contains all configuration fields.
11 * @param bool $isLoggedIn true if user is logged in. 11 * @param bool $isLoggedIn true if user is logged in.
12 * 12 *
13 * @return void 13 * @return void
14 * 14 *
15 * @throws MissingFieldConfigException: a mandatory field has not been provided in $config. 15 * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
16 * @throws UnauthorizedConfigException: user is not authorize to change configuration. 16 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
17 * @throws Exception: an error occured while writing the new config file. 17 * @throws Exception: an error occured while writing the new config file.
18 */ 18 */
19function writeConfig($config, $isLoggedIn) 19function writeConfig($config, $isLoggedIn)
20{ 20{
21 // These fields are required in configuration. 21 // These fields are required in configuration.
22 $MANDATORY_FIELDS = array( 22 $MANDATORY_FIELDS = array(
23 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink', 23 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
24 'redirector', 'disablesessionprotection', 'privateLinkByDefault' 24 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
25 ); 25 );
26 26
27 if (!isset($config['config']['CONFIG_FILE'])) { 27 if (!isset($config['config']['CONFIG_FILE'])) {
28 throw new MissingFieldConfigException('CONFIG_FILE'); 28 throw new MissingFieldConfigException('CONFIG_FILE');
29 } 29 }
30 30
31 // Only logged in user can alter config. 31 // Only logged in user can alter config.
32 if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) { 32 if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
33 throw new UnauthorizedConfigException(); 33 throw new UnauthorizedConfigException();
34 } 34 }
35 35
36 // Check that all mandatory fields are provided in $config. 36 // Check that all mandatory fields are provided in $config.
37 foreach ($MANDATORY_FIELDS as $field) { 37 foreach ($MANDATORY_FIELDS as $field) {
38 if (!isset($config[$field])) { 38 if (!isset($config[$field])) {
39 throw new MissingFieldConfigException($field); 39 throw new MissingFieldConfigException($field);
40 } 40 }
41 } 41 }
42 42
43 $configStr = '<?php '. PHP_EOL; 43 $configStr = '<?php '. PHP_EOL;
44 $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL; 44 $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
45 $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL; 45 $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
46 $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL; 46 $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
47 $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL; 47 $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
48 $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL; 48 $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
49 $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL; 49 $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
50 $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL; 50 $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
51 $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL; 51 $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
52 $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL; 52 $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
53 $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL; 53 $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
54 54
55 // Store all $config['config'] 55 // Store all $config['config']
56 foreach ($config['config'] as $key => $value) { 56 foreach ($config['config'] as $key => $value) {
57 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL; 57 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
58 } 58 }
59 $configStr .= '?>'; 59
60 60 if (isset($config['plugins'])) {
61 if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr) 61 foreach ($config['plugins'] as $key => $value) {
62 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0 62 $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
63 ) { 63 }
64 throw new Exception( 64 }
65 'Shaarli could not create the config file. 65
66 Please make sure Shaarli has the right to write in the folder is it installed in.' 66 if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
67 ); 67 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
68 } 68 ) {
69} 69 throw new Exception(
70 70 'Shaarli could not create the config file.
71/** 71 Please make sure Shaarli has the right to write in the folder is it installed in.'
72 * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore. 72 );
73 * ==> if user is loggedIn, merge its content with config.php, then delete options.php. 73 }
74 * 74}
75 * @param array $config contains all configuration fields. 75
76 * @param bool $isLoggedIn true if user is logged in. 76/**
77 * 77 * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
78 * @return void 78 * ==> if user is loggedIn, merge its content with config.php, then delete options.php.
79 */ 79 *
80function mergeDeprecatedConfig($config, $isLoggedIn) 80 * @param array $config contains all configuration fields.
81{ 81 * @param bool $isLoggedIn true if user is logged in.
82 $config_file = $config['config']['CONFIG_FILE']; 82 *
83 83 * @return void
84 if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) { 84 */
85 include $config['config']['DATADIR'].'/options.php'; 85function mergeDeprecatedConfig($config, $isLoggedIn)
86 86{
87 // Load GLOBALS into config 87 $config_file = $config['config']['CONFIG_FILE'];
88 foreach ($GLOBALS as $key => $value) { 88
89 $config[$key] = $value; 89 if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {
90 } 90 include $config['config']['DATADIR'].'/options.php';
91 $config['config']['CONFIG_FILE'] = $config_file; 91
92 writeConfig($config, $isLoggedIn); 92 // Load GLOBALS into config
93 93 foreach ($GLOBALS as $key => $value) {
94 unlink($config['config']['DATADIR'].'/options.php'); 94 $config[$key] = $value;
95 } 95 }
96} 96 $config['config']['CONFIG_FILE'] = $config_file;
97 97 writeConfig($config, $isLoggedIn);
98/** 98
99 * Exception used if a mandatory field is missing in given configuration. 99 unlink($config['config']['DATADIR'].'/options.php');
100 */ 100 }
101class MissingFieldConfigException extends Exception 101}
102{ 102
103 public $field; 103/**
104 104 * Exception used if a mandatory field is missing in given configuration.
105 /** 105 */
106 * Construct exception. 106class MissingFieldConfigException extends Exception
107 * 107{
108 * @param string $field field name missing. 108 public $field;
109 */ 109
110 public function __construct($field) 110 /**
111 { 111 * Construct exception.
112 $this->field = $field; 112 *
113 $this->message = 'Configuration value is required for '. $this->field; 113 * @param string $field field name missing.
114 } 114 */
115} 115 public function __construct($field)
116 116 {
117/** 117 $this->field = $field;
118 * Exception used if an unauthorized attempt to edit configuration has been made. 118 $this->message = 'Configuration value is required for '. $this->field;
119 */ 119 }
120class UnauthorizedConfigException extends Exception 120}
121{ 121
122 /** 122/**
123 * Construct exception. 123 * Exception used if an unauthorized attempt to edit configuration has been made.
124 */ 124 */
125 public function __construct() 125class UnauthorizedConfigException extends Exception
126 { 126{
127 $this->message = 'You are not authorized to alter config.'; 127 /**
128 } 128 * Construct exception.
129} 129 */
130 public function __construct()
131 {
132 $this->message = 'You are not authorized to alter config.';
133 }
134}
diff --git a/application/PluginManager.php b/application/PluginManager.php
new file mode 100644
index 00000000..e572ff7c
--- /dev/null
+++ b/application/PluginManager.php
@@ -0,0 +1,184 @@
1<?php
2
3/**
4 * Class PluginManager
5 *
6 * Use to manage, load and execute plugins.
7 *
8 * Using Singleton design pattern.
9 */
10class PluginManager
11{
12 /**
13 * PluginManager singleton instance.
14 * @var PluginManager $instance
15 */
16 private static $instance;
17
18 /**
19 * List of authorized plugins from configuration file.
20 * @var array $authorizedPlugins
21 */
22 private $authorizedPlugins;
23
24 /**
25 * List of loaded plugins.
26 * @var array $loadedPlugins
27 */
28 private $loadedPlugins = array();
29
30 /**
31 * Plugins subdirectory.
32 * @var string $PLUGINS_PATH
33 */
34 public static $PLUGINS_PATH = 'plugins';
35
36 /**
37 * Private constructor: new instances not allowed.
38 */
39 private function __construct()
40 {
41 }
42
43 /**
44 * Cloning isn't allowed either.
45 *
46 * @return void
47 */
48 private function __clone()
49 {
50 }
51
52 /**
53 * Return existing instance of PluginManager, or create it.
54 *
55 * @return PluginManager instance.
56 */
57 public static function getInstance()
58 {
59 if (!(self::$instance instanceof self)) {
60 self::$instance = new self();
61 }
62
63 return self::$instance;
64 }
65
66 /**
67 * Load plugins listed in $authorizedPlugins.
68 *
69 * @param array $authorizedPlugins Names of plugin authorized to be loaded.
70 *
71 * @return void
72 */
73 public function load($authorizedPlugins)
74 {
75 $this->authorizedPlugins = $authorizedPlugins;
76
77 $dirs = glob(self::$PLUGINS_PATH . '/*', GLOB_ONLYDIR);
78 $dirnames = array_map('basename', $dirs);
79 foreach ($this->authorizedPlugins as $plugin) {
80 $index = array_search($plugin, $dirnames);
81
82 // plugin authorized, but its folder isn't listed
83 if ($index === false) {
84 continue;
85 }
86
87 try {
88 $this->loadPlugin($dirs[$index], $plugin);
89 }
90 catch (PluginFileNotFoundException $e) {
91 error_log($e->getMessage());
92 }
93 }
94 }
95
96 /**
97 * Execute all plugins registered hook.
98 *
99 * @param string $hook name of the hook to trigger.
100 * @param array $data list of data to manipulate passed by reference.
101 * @param array $params additional parameters such as page target.
102 *
103 * @return void
104 */
105 public function executeHooks($hook, &$data, $params = array())
106 {
107 if (!empty($params['target'])) {
108 $data['_PAGE_'] = $params['target'];
109 }
110
111 if (isset($params['loggedin'])) {
112 $data['_LOGGEDIN_'] = $params['loggedin'];
113 }
114
115 foreach ($this->loadedPlugins as $plugin) {
116 $hookFunction = $this->buildHookName($hook, $plugin);
117
118 if (function_exists($hookFunction)) {
119 $data = call_user_func($hookFunction, $data);
120 }
121 }
122 }
123
124 /**
125 * Load a single plugin from its files.
126 * Add them in $loadedPlugins if successful.
127 *
128 * @param string $dir plugin's directory.
129 * @param string $pluginName plugin's name.
130 *
131 * @return void
132 * @throws PluginFileNotFoundException - plugin files not found.
133 */
134 private function loadPlugin($dir, $pluginName)
135 {
136 if (!is_dir($dir)) {
137 throw new PluginFileNotFoundException($pluginName);
138 }
139
140 $pluginFilePath = $dir . '/' . $pluginName . '.php';
141 if (!is_file($pluginFilePath)) {
142 throw new PluginFileNotFoundException($pluginName);
143 }
144
145 include_once $pluginFilePath;
146
147 $this->loadedPlugins[] = $pluginName;
148 }
149
150 /**
151 * Construct normalize hook name for a specific plugin.
152 *
153 * Format:
154 * hook_<plugin_name>_<hook_name>
155 *
156 * @param string $hook hook name.
157 * @param string $pluginName plugin name.
158 *
159 * @return string - plugin's hook name.
160 */
161 public function buildHookName($hook, $pluginName)
162 {
163 return 'hook_' . $pluginName . '_' . $hook;
164 }
165}
166
167/**
168 * Class PluginFileNotFoundException
169 *
170 * Raise when plugin files can't be found.
171 */
172class PluginFileNotFoundException extends Exception
173{
174 /**
175 * Construct exception with plugin name.
176 * Generate message.
177 *
178 * @param string $pluginName name of the plugin not found
179 */
180 public function __construct($pluginName)
181 {
182 $this->message = 'Plugin "'. $pluginName .'" files not found.';
183 }
184} \ No newline at end of file
diff --git a/application/Router.php b/application/Router.php
new file mode 100644
index 00000000..82b2b858
--- /dev/null
+++ b/application/Router.php
@@ -0,0 +1,105 @@
1<?php
2
3/**
4 * Class Router
5 *
6 * (only displayable pages here)
7 */
8class Router
9{
10 public static $PAGE_LOGIN = 'login';
11
12 public static $PAGE_PICWALL = 'picwall';
13
14 public static $PAGE_TAGCLOUD = 'tagcloud';
15
16 public static $PAGE_TOOLS = 'tools';
17
18 public static $PAGE_CHANGEPASSWORD = 'changepasswd';
19
20 public static $PAGE_CONFIGURE = 'configure';
21
22 public static $PAGE_CHANGETAG = 'changetag';
23
24 public static $PAGE_ADDLINK = 'addlink';
25
26 public static $PAGE_EDITLINK = 'edit_link';
27
28 public static $PAGE_EXPORT = 'export';
29
30 public static $PAGE_IMPORT = 'import';
31
32 public static $PAGE_LINKLIST = 'linklist';
33
34 /**
35 * Reproducing renderPage() if hell, to avoid regression.
36 *
37 * This highlights how bad this needs to be rewrite,
38 * but let's focus on plugins for now.
39 *
40 * @param string $query $_SERVER['QUERY_STRING'].
41 * @param array $get $_SERVER['GET'].
42 * @param bool $loggedIn true if authenticated user.
43 *
44 * @return self::page found.
45 */
46 public static function findPage($query, $get, $loggedIn)
47 {
48 $loggedIn = ($loggedIn === true) ? true : false;
49
50 if (empty($query) && !isset($get['edit_link']) && !isset($get['post'])) {
51 return self::$PAGE_LINKLIST;
52 }
53
54 if (startswith($query, 'do='. self::$PAGE_LOGIN) && $loggedIn === false) {
55 return self::$PAGE_LOGIN;
56 }
57
58 if (startswith($query, 'do='. self::$PAGE_PICWALL)) {
59 return self::$PAGE_PICWALL;
60 }
61
62 if (startswith($query, 'do='. self::$PAGE_TAGCLOUD)) {
63 return self::$PAGE_TAGCLOUD;
64 }
65
66 // At this point, only loggedin pages.
67 if (!$loggedIn) {
68 return self::$PAGE_LINKLIST;
69 }
70
71 if (startswith($query, 'do='. self::$PAGE_TOOLS)) {
72 return self::$PAGE_TOOLS;
73 }
74
75 if (startswith($query, 'do='. self::$PAGE_CHANGEPASSWORD)) {
76 return self::$PAGE_CHANGEPASSWORD;
77 }
78
79 if (startswith($query, 'do='. self::$PAGE_CONFIGURE)) {
80 return self::$PAGE_CONFIGURE;
81 }
82
83 if (startswith($query, 'do='. self::$PAGE_CHANGETAG)) {
84 return self::$PAGE_CHANGETAG;
85 }
86
87 if (startswith($query, 'do='. self::$PAGE_ADDLINK)) {
88 return self::$PAGE_ADDLINK;
89 }
90
91 if (isset($get['edit_link']) || isset($get['post'])) {
92 return self::$PAGE_EDITLINK;
93 }
94
95 if (startswith($query, 'do='. self::$PAGE_EXPORT)) {
96 return self::$PAGE_EXPORT;
97 }
98
99 if (startswith($query, 'do='. self::$PAGE_IMPORT)) {
100 return self::$PAGE_IMPORT;
101 }
102
103 return self::$PAGE_LINKLIST;
104 }
105} \ No newline at end of file