18 * @copyright (C) 2006-2008 Enano Project |
18 * @copyright (C) 2006-2008 Enano Project |
19 * @license GNU General Public License <http://enanocms.org/Special:GNU_General_Public_License> |
19 * @license GNU General Public License <http://enanocms.org/Special:GNU_General_Public_License> |
20 */ |
20 */ |
21 |
21 |
22 class pluginLoader { |
22 class pluginLoader { |
23 |
23 |
24 /** |
24 /** |
25 * The list of hooks registered. |
25 * The list of hooks registered. |
26 * @var array |
26 * @var array |
27 * @access private |
27 * @access private |
28 */ |
28 */ |
29 |
29 |
30 var $hook_list; |
30 var $hook_list; |
31 |
31 |
32 /** |
32 /** |
33 * The list of plugins that should be loaded. Used only by common.php. |
33 * The list of plugins that should be loaded. Used only by common.php. |
34 * @var array |
34 * @var array |
35 * @access private |
35 * @access private |
36 */ |
36 */ |
37 |
37 |
38 var $load_list; |
38 var $load_list; |
39 |
39 |
40 /** |
40 /** |
41 * The list of plugins that are loaded currently. This is only used by the loaded() method which in turn is |
41 * The list of plugins that are loaded currently. This is only used by the loaded() method which in turn is |
42 * used by template files with the <!-- IFPLUGIN --> special tag. |
42 * used by template files with the <!-- IFPLUGIN --> special tag. |
43 * @var array |
43 * @var array |
44 * @access private |
44 * @access private |
45 */ |
45 */ |
46 |
46 |
47 var $loaded_plugins; |
47 var $loaded_plugins; |
48 |
48 |
49 /** |
49 /** |
50 * The list of plugins that are always loaded because they're part of the Enano core. This cannot be modified |
50 * The list of plugins that are always loaded because they're part of the Enano core. This cannot be modified |
51 * by any external code because user plugins are loaded after the load_list is calculated. Can be useful in |
51 * by any external code because user plugins are loaded after the load_list is calculated. Can be useful in |
52 * alternative administration panel frameworks that need the list of system plugins. |
52 * alternative administration panel frameworks that need the list of system plugins. |
53 * @var array |
53 * @var array |
54 */ |
54 */ |
55 |
55 |
56 var $system_plugins = Array('SpecialUserFuncs.php','SpecialUserPrefs.php','SpecialPageFuncs.php','SpecialAdmin.php','SpecialCSS.php','SpecialUpdownload.php','SpecialSearch.php','PrivateMessages.php','SpecialGroups.php', 'SpecialLog.php', 'DemoMode.php'); |
56 var $system_plugins = Array('SpecialUserFuncs.php','SpecialUserPrefs.php','SpecialPageFuncs.php','SpecialAdmin.php','SpecialCSS.php','SpecialUpdownload.php','SpecialSearch.php','PrivateMessages.php','SpecialGroups.php', 'SpecialLog.php', 'DemoMode.php'); |
57 |
57 |
58 /** |
58 /** |
59 * Name kept for compatibility. Effectively a constructor. Calculates the list of plugins that should be loaded |
59 * Name kept for compatibility. Effectively a constructor. Calculates the list of plugins that should be loaded |
60 * and puts that list in the $load_list property. Plugin developers have absolutely no use for this whatsoever. |
60 * and puts that list in the $load_list property. Plugin developers have absolutely no use for this whatsoever. |
61 */ |
61 */ |
62 |
62 |
63 function loadAll() |
63 function loadAll() |
64 { |
64 { |
65 global $db, $session, $paths, $template, $plugins; // Common objects |
65 global $db, $session, $paths, $template, $plugins; // Common objects |
66 $GLOBALS['plugins_cache'] = array(); |
66 $GLOBALS['plugins_cache'] = array(); |
67 |
67 |
68 // if we're in an upgrade, just skip this step |
68 // if we're in an upgrade, just skip this step |
69 if ( defined('IN_ENANO_UPGRADE') ) |
69 if ( defined('IN_ENANO_UPGRADE') ) |
70 { |
70 { |
71 $this->load_list = array(); |
71 $this->load_list = array(); |
72 return false; |
72 return false; |
73 } |
73 } |
74 |
74 |
75 $dir = ENANO_ROOT.'/plugins/'; |
75 $dir = ENANO_ROOT.'/plugins/'; |
76 |
76 |
77 $plugin_list = $this->get_plugin_list(); |
77 $plugin_list = $this->get_plugin_list(); |
78 $this->load_list = array(); |
78 $this->load_list = array(); |
79 |
79 |
80 foreach ( $plugin_list as $filename => $data ) |
80 foreach ( $plugin_list as $filename => $data ) |
81 { |
81 { |
82 if ( !$data['system plugin'] && ( $data['status'] & PLUGIN_OUTOFDATE || $data['status'] & PLUGIN_DISABLED || !$data['installed'] ) ) |
82 if ( !$data['system plugin'] && ( $data['status'] & PLUGIN_OUTOFDATE || $data['status'] & PLUGIN_DISABLED || !$data['installed'] ) ) |
83 continue; |
83 continue; |
84 |
84 |
85 $this->load_list[] = $filename; |
85 $this->load_list[] = $filename; |
86 $this->loaded_plugins[$filename] = $data; |
86 $this->loaded_plugins[$filename] = $data; |
87 } |
87 } |
88 } |
88 } |
89 |
89 |
90 /** |
90 /** |
91 * Name kept for compatibility. This method is used to add a new hook into the code somewhere. Plugins are encouraged |
91 * Name kept for compatibility. This method is used to add a new hook into the code somewhere. Plugins are encouraged |
92 * to set hooks and hook into other plugins in a fail-safe way, this encourages reuse of code. Returns an array, whose |
92 * to set hooks and hook into other plugins in a fail-safe way, this encourages reuse of code. Returns an array, whose |
93 * values should be eval'ed. |
93 * values should be eval'ed. |
94 * @example <code> |
94 * @example <code> |
95 $code = $plugins->setHook('my_hook_name'); |
95 $code = $plugins->setHook('my_hook_name'); |
96 foreach ( $code as $cmd ) |
96 foreach ( $code as $cmd ) |
97 { |
97 { |
98 eval($cmd); |
98 eval($cmd); |
99 } |
99 } |
100 </code> |
100 </code> |
101 * @param string The name of the hook. |
101 * @param string The name of the hook. |
102 * @param array Deprecated. |
102 * @param array Deprecated. |
103 */ |
103 */ |
104 |
104 |
105 function setHook($name, $dont_split = false) |
105 function setHook($name, $dont_split = false) |
106 { |
106 { |
107 if ( !empty($this->hook_list[$name]) && is_array($this->hook_list[$name]) ) |
107 if ( !empty($this->hook_list[$name]) && is_array($this->hook_list[$name]) ) |
108 { |
108 { |
109 if ( $dont_split ) |
109 if ( $dont_split ) |
110 return $this->hook_list[$name]; |
110 return $this->hook_list[$name]; |
111 |
111 |
112 return array(implode("\n", $this->hook_list[$name])); |
112 return array(implode("\n", $this->hook_list[$name])); |
113 } |
113 } |
114 else |
114 else |
115 { |
115 { |
116 return Array(); |
116 return Array(); |
117 } |
117 } |
118 } |
118 } |
119 |
119 |
120 /** |
120 /** |
121 * Attaches to a hook effectively scheduling some code to be run at that point. You should try to keep hooks clean by |
121 * Attaches to a hook effectively scheduling some code to be run at that point. You should try to keep hooks clean by |
122 * making a function that has variables that need to be modified passed by reference. |
122 * making a function that has variables that need to be modified passed by reference. |
123 * @example Simple example: <code> |
123 * @example Simple example: <code> |
124 $plugins->attachHook('render_wikiformat_pre', '$text = str_replace("Goodbye, Mr. Chips", "Hello, Mr. Carrots", $text);'); |
124 $plugins->attachHook('render_wikiformat_pre', '$text = str_replace("Goodbye, Mr. Chips", "Hello, Mr. Carrots", $text);'); |
125 </code> |
125 </code> |
126 * @example More complicated example: <code> |
126 * @example More complicated example: <code> |
127 $plugins->attachHook('render_wikiformat_pre', 'myplugin_parser_ext($text);'); |
127 $plugins->attachHook('render_wikiformat_pre', 'myplugin_parser_ext($text);'); |
128 |
128 |
129 // Notice that $text is passed by reference. |
129 // Notice that $text is passed by reference. |
130 function myplugin_parser_ext(&$text) |
130 function myplugin_parser_ext(&$text) |
131 { |
131 { |
132 $text = str_replace("Goodbye, Mr. Chips", "Hello, Mr. Carrots", $text); |
132 $text = str_replace("Goodbye, Mr. Chips", "Hello, Mr. Carrots", $text); |
133 } |
133 } |
134 </code> |
134 </code> |
135 */ |
135 */ |
136 |
136 |
137 function attachHook($name, $code) |
137 function attachHook($name, $code) |
138 { |
138 { |
139 if ( !isset($this->hook_list[$name]) ) |
139 if ( !isset($this->hook_list[$name]) ) |
140 { |
140 { |
141 $this->hook_list[$name] = Array(); |
141 $this->hook_list[$name] = Array(); |
142 } |
142 } |
143 $this->hook_list[$name][] = $code; |
143 $this->hook_list[$name][] = $code; |
144 } |
144 } |
145 |
145 |
146 /** |
146 /** |
147 * Tell whether a plugin is loaded or not. |
147 * Tell whether a plugin is loaded or not. |
148 * @param string The filename of the plugin |
148 * @param string The filename of the plugin |
149 * @return bool |
149 * @return bool |
150 */ |
150 */ |
151 |
151 |
152 function loaded($plugid) |
152 function loaded($plugid) |
153 { |
153 { |
154 return isset( $this->loaded_plugins[$plugid] ); |
154 return isset( $this->loaded_plugins[$plugid] ); |
155 } |
155 } |
156 |
156 |
157 /** |
157 /** |
158 * Parses all special comment blocks in a plugin and returns an array in the format: |
158 * Parses all special comment blocks in a plugin and returns an array in the format: |
159 <code> |
159 <code> |
160 array( |
160 array( |
161 0 => array( |
161 0 => array( |
162 'block' => 'upgrade', |
162 'block' => 'upgrade', |
163 // parsed from the block's parameters section |
163 // parsed from the block's parameters section |
164 'release_from' => '1.0b1', |
164 'release_from' => '1.0b1', |
165 'release_to' => '1.0b2', |
165 'release_to' => '1.0b2', |
166 'value' => 'foo' |
166 'value' => 'foo' |
167 ), |
167 ), |
168 1 => array( |
168 1 => array( |
169 ... |
169 ... |
170 ) |
170 ) |
171 ); |
171 ); |
172 </code> |
172 </code> |
173 * @param string Path to plugin file |
173 * @param string Path to plugin file |
174 * @param string Optional. The type of block to fetch. If this is specified, only the block type specified will be read, all others will be discarded. |
174 * @param string Optional. The type of block to fetch. If this is specified, only the block type specified will be read, all others will be discarded. |
175 * @return array |
175 * @return array |
176 */ |
176 */ |
177 |
177 |
178 public static function parse_plugin_blocks($file, $type = false) |
178 public static function parse_plugin_blocks($file, $type = false) |
179 { |
179 { |
180 if ( !file_exists($file) ) |
180 if ( !file_exists($file) ) |
181 { |
181 { |
182 return array(); |
182 return array(); |
183 } |
183 } |
184 $blocks = array(); |
184 $blocks = array(); |
185 $contents = @file_get_contents($file); |
185 $contents = @file_get_contents($file); |
186 if ( empty($contents) ) |
186 if ( empty($contents) ) |
187 { |
187 { |
188 return array(); |
188 return array(); |
189 } |
189 } |
190 |
190 |
191 // The "\r?" in this regular expression is the result of an embarrassing failure during a job related meeting. |
191 // The "\r?" in this regular expression is the result of an embarrassing failure during a job related meeting. |
192 // I was demoing the authoring of an Enano plugin and simply could not figure out why the plugin would not |
192 // I was demoing the authoring of an Enano plugin and simply could not figure out why the plugin would not |
193 // show up in the admin panel. |
193 // show up in the admin panel. |
194 |
194 |
195 $regexp = '#^/\*\*!([a-z0-9_]+)' // block header and type |
195 $regexp = '#^/\*\*!([a-z0-9_]+)' // block header and type |
196 . '(([\s]+[a-z0-9_]+[\s]*=[\s]*".+?"[\s]*;)*)' // parameters |
196 . '(([\s]+[a-z0-9_]+[\s]*=[\s]*".+?"[\s]*;)*)' // parameters |
197 . '[\s]*\*\*' . "\r?\n" // spacing and header close |
197 . '[\s]*\*\*' . "\r?\n" // spacing and header close |
198 . '([\w\W]+?)' . "\r?\n" // value |
198 . '([\w\W]+?)' . "\r?\n" // value |
199 . '\*\*!\*/' // closing comment |
199 . '\*\*!\*/' // closing comment |
200 . '#m'; |
200 . '#m'; |
201 |
201 |
202 // Match out all blocks |
202 // Match out all blocks |
203 $results = preg_match_all($regexp, $contents, $blocks); |
203 $results = preg_match_all($regexp, $contents, $blocks); |
204 |
204 |
205 $return = array(); |
205 $return = array(); |
206 foreach ( $blocks[0] as $i => $_ ) |
206 foreach ( $blocks[0] as $i => $_ ) |
207 { |
207 { |
208 if ( is_string($type) && $blocks[1][$i] !== $type ) |
208 if ( is_string($type) && $blocks[1][$i] !== $type ) |
209 continue; |
209 continue; |
210 |
210 |
211 $value =& $blocks[4][$i]; |
211 $value =& $blocks[4][$i]; |
212 // parse includes |
212 // parse includes |
213 preg_match_all('/^!include [\'"]?(.+?)[\'"]?$/m', $value, $includes); |
213 preg_match_all('/^!include [\'"]?(.+?)[\'"]?$/m', $value, $includes); |
214 foreach ( $includes[0] as $i => $replace ) |
214 foreach ( $includes[0] as $i => $replace ) |
215 { |
215 { |
216 $filename = ENANO_ROOT . '/' . $includes[1][$i]; |
216 $filename = ENANO_ROOT . '/' . $includes[1][$i]; |
217 if ( @file_exists( $filename ) && @is_readable( $filename ) ) |
217 if ( @file_exists( $filename ) && @is_readable( $filename ) ) |
218 { |
218 { |
219 $contents = @file_get_contents($filename); |
219 $contents = @file_get_contents($filename); |
220 $value = str_replace_once($replace, $contents, $value); |
220 $value = str_replace_once($replace, $contents, $value); |
221 } |
221 } |
222 } |
222 } |
223 |
223 |
224 $el = self::parse_vars($blocks[2][$i]); |
224 $el = self::parse_vars($blocks[2][$i]); |
225 $el['block'] = $blocks[1][$i]; |
225 $el['block'] = $blocks[1][$i]; |
226 $el['value'] = $value; |
226 $el['value'] = $value; |
227 $return[] = $el; |
227 $return[] = $el; |
228 } |
228 } |
229 |
229 |
230 return $return; |
230 return $return; |
231 } |
231 } |
232 |
232 |
233 private static function parse_vars($var_block) |
233 private static function parse_vars($var_block) |
234 { |
234 { |
235 preg_match_all('/[\s]+([a-z0-9_]+)[\s]*=[\s]*"(.+?)";/', $var_block, $matches); |
235 preg_match_all('/[\s]+([a-z0-9_]+)[\s]*=[\s]*"(.+?)";/', $var_block, $matches); |
236 $return = array(); |
236 $return = array(); |
237 foreach ( $matches[0] as $i => $_ ) |
237 foreach ( $matches[0] as $i => $_ ) |
238 { |
238 { |
239 $return[ $matches[1][$i] ] = $matches[2][$i]; |
239 $return[ $matches[1][$i] ] = $matches[2][$i]; |
240 } |
240 } |
241 return $return; |
241 return $return; |
242 } |
242 } |
243 |
243 |
244 /** |
244 /** |
245 * Reads all plugins in the filesystem and cross-references them with the database, providing a very complete summary of plugins |
245 * Reads all plugins in the filesystem and cross-references them with the database, providing a very complete summary of plugins |
246 * on the site. |
246 * on the site. |
247 * @param array If specified, will restrict scanned files to this list. Defaults to null, which means all PHP files will be scanned. |
247 * @param array If specified, will restrict scanned files to this list. Defaults to null, which means all PHP files will be scanned. |
248 * @param bool If true, allows using cached information. Defaults to true. |
248 * @param bool If true, allows using cached information. Defaults to true. |
249 * @return array |
249 * @return array |
250 */ |
250 */ |
251 |
251 |
252 function get_plugin_list($restrict = null, $use_cache = true) |
252 function get_plugin_list($restrict = null, $use_cache = true) |
253 { |
253 { |
254 global $db, $session, $paths, $template, $plugins; // Common objects |
254 global $db, $session, $paths, $template, $plugins; // Common objects |
255 |
255 |
256 // Scan all plugins |
256 // Scan all plugins |
257 $plugin_list = array(); |
257 $plugin_list = array(); |
258 $ta = 0; |
258 $ta = 0; |
259 // won't load twice (failsafe automatic skip) |
259 // won't load twice (failsafe automatic skip) |
260 $this->load_plugins_cache(); |
260 $this->load_plugins_cache(); |
261 global $plugins_cache; |
261 global $plugins_cache; |
262 if ( $use_cache && !empty($plugins_cache) ) |
262 if ( $use_cache && !empty($plugins_cache) ) |
263 { |
263 { |
264 return $plugins_cache; |
264 return $plugins_cache; |
265 } |
265 } |
266 else |
266 else |
267 { |
267 { |
268 // blank array - effectively skips importing the cache |
268 // blank array - effectively skips importing the cache |
269 $plugins_cache = array(); |
269 $plugins_cache = array(); |
270 } |
270 } |
271 |
271 |
272 // List all plugins |
272 // List all plugins |
273 if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) ) |
273 if ( $dirh = @opendir( ENANO_ROOT . '/plugins' ) ) |
274 { |
274 { |
275 while ( $dh = @readdir($dirh) ) |
275 while ( $dh = @readdir($dirh) ) |
276 { |
276 { |
277 if ( !preg_match('/\.php$/i', $dh) ) |
277 if ( !preg_match('/\.php$/i', $dh) ) |
278 continue; |
278 continue; |
279 |
279 |
280 if ( is_array($restrict) ) |
280 if ( is_array($restrict) ) |
281 if ( !in_array($dh, $restrict) ) |
281 if ( !in_array($dh, $restrict) ) |
282 continue; |
282 continue; |
283 |
283 |
284 // it's a PHP file, attempt to read metadata |
284 // it's a PHP file, attempt to read metadata |
285 $fullpath = ENANO_ROOT . "/plugins/$dh"; |
285 $fullpath = ENANO_ROOT . "/plugins/$dh"; |
286 $plugin_meta = $this->read_plugin_headers($fullpath, $use_cache); |
286 $plugin_meta = $this->read_plugin_headers($fullpath, $use_cache); |
287 |
287 |
288 if ( is_array($plugin_meta) ) |
288 if ( is_array($plugin_meta) ) |
289 { |
289 { |
290 // all checks passed |
290 // all checks passed |
291 $plugin_list[$dh] = $plugin_meta; |
291 $plugin_list[$dh] = $plugin_meta; |
292 } |
292 } |
293 } |
293 } |
294 } |
294 } |
295 |
295 |
296 // Populate with additional metadata from database |
296 // Populate with additional metadata from database |
297 $q = $db->sql_query('SELECT plugin_id, plugin_filename, plugin_version, plugin_flags FROM ' . table_prefix . 'plugins;'); |
297 $q = $db->sql_query('SELECT plugin_id, plugin_filename, plugin_version, plugin_flags FROM ' . table_prefix . 'plugins;'); |
298 if ( !$q ) |
298 if ( !$q ) |
299 $db->_die(); |
299 $db->_die(); |
300 while ( $row = $db->fetchrow() ) |
300 while ( $row = $db->fetchrow() ) |
301 { |
301 { |
302 if ( !isset($plugin_list[ $row['plugin_filename'] ]) ) |
302 if ( !isset($plugin_list[ $row['plugin_filename'] ]) ) |
303 { |
303 { |
304 // missing plugin file, don't report (for now) |
304 // missing plugin file, don't report (for now) |
305 continue; |
305 continue; |
306 } |
306 } |
307 $filename =& $row['plugin_filename']; |
307 $filename =& $row['plugin_filename']; |
308 $plugin_list[$filename]['installed'] = true; |
308 $plugin_list[$filename]['installed'] = true; |
309 $plugin_list[$filename]['status'] = PLUGIN_INSTALLED; |
309 $plugin_list[$filename]['status'] = PLUGIN_INSTALLED; |
310 $plugin_list[$filename]['plugin id'] = $row['plugin_id']; |
310 $plugin_list[$filename]['plugin id'] = $row['plugin_id']; |
311 if ( $row['plugin_version'] != $plugin_list[$filename]['version'] ) |
311 if ( $row['plugin_version'] != $plugin_list[$filename]['version'] ) |
312 { |
312 { |
313 $plugin_list[$filename]['status'] |= PLUGIN_OUTOFDATE; |
313 $plugin_list[$filename]['status'] |= PLUGIN_OUTOFDATE; |
314 $plugin_list[$filename]['version installed'] = $row['plugin_version']; |
314 $plugin_list[$filename]['version installed'] = $row['plugin_version']; |
315 } |
315 } |
316 if ( $row['plugin_flags'] & PLUGIN_DISABLED ) |
316 if ( $row['plugin_flags'] & PLUGIN_DISABLED ) |
317 { |
317 { |
318 $plugin_list[$filename]['status'] |= PLUGIN_DISABLED; |
318 $plugin_list[$filename]['status'] |= PLUGIN_DISABLED; |
319 } |
319 } |
320 } |
320 } |
321 $db->free_result(); |
321 $db->free_result(); |
322 |
322 |
323 // sort it all out by filename |
323 // sort it all out by filename |
324 ksort($plugin_list); |
324 ksort($plugin_list); |
325 |
325 |
326 // done |
326 // done |
327 return $plugin_list; |
327 return $plugin_list; |
328 } |
328 } |
329 |
329 |
330 /** |
330 /** |
331 * Retrieves the metadata block from a plugin file |
331 * Retrieves the metadata block from a plugin file |
332 * @param string Path to plugin file (full path) |
332 * @param string Path to plugin file (full path) |
333 * @return array |
333 * @return array |
334 */ |
334 */ |
335 |
335 |
336 function read_plugin_headers($fullpath, $use_cache = true) |
336 function read_plugin_headers($fullpath, $use_cache = true) |
337 { |
337 { |
338 global $plugins_cache; |
338 global $plugins_cache; |
339 $dh = basename($fullpath); |
339 $dh = basename($fullpath); |
340 |
340 |
341 // first can we use cached info? |
341 // first can we use cached info? |
342 if ( isset($plugins_cache[$dh]) && $plugins_cache[$dh]['file md5'] === $this->md5_header($fullpath) ) |
342 if ( isset($plugins_cache[$dh]) && $plugins_cache[$dh]['file md5'] === $this->md5_header($fullpath) ) |
343 { |
343 { |
344 $plugin_meta = $plugins_cache[$dh]; |
344 $plugin_meta = $plugins_cache[$dh]; |
345 } |
345 } |
346 else |
346 else |
347 { |
347 { |
348 // the cache is out of date if we reached here -- regenerate |
348 // the cache is out of date if we reached here -- regenerate |
349 if ( $use_cache ) |
349 if ( $use_cache ) |
350 $this->generate_plugins_cache(); |
350 $this->generate_plugins_cache(); |
351 |
351 |
352 // pass 1: try to read a !info block |
352 // pass 1: try to read a !info block |
353 $blockdata = $this->parse_plugin_blocks($fullpath, 'info'); |
353 $blockdata = $this->parse_plugin_blocks($fullpath, 'info'); |
354 if ( empty($blockdata) ) |
354 if ( empty($blockdata) ) |
355 { |
355 { |
356 // no !info block, check for old header |
356 // no !info block, check for old header |
357 $fh = @fopen($fullpath, 'r'); |
357 $fh = @fopen($fullpath, 'r'); |
358 if ( !$fh ) |
358 if ( !$fh ) |
359 // can't read, bail out |
359 // can't read, bail out |
360 return false; |
360 return false; |
361 $plugin_data = array(); |
361 $plugin_data = array(); |
362 for ( $i = 0; $i < 8; $i++ ) |
362 for ( $i = 0; $i < 8; $i++ ) |
363 { |
363 { |
364 $plugin_data[] = @fgets($fh, 8096); |
364 $plugin_data[] = @fgets($fh, 8096); |
365 } |
365 } |
366 // close our file handle |
366 // close our file handle |
367 fclose($fh); |
367 fclose($fh); |
368 // is the header correct? |
368 // is the header correct? |
369 if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' ) |
369 if ( trim($plugin_data[0]) != '<?php' || trim($plugin_data[1]) != '/*' ) |
370 { |
370 { |
371 // nope. get out. |
371 // nope. get out. |
372 return false; |
372 return false; |
373 } |
373 } |
374 // parse all the variables |
374 // parse all the variables |
375 $plugin_meta = array(); |
375 $plugin_meta = array(); |
376 for ( $i = 2; $i <= 7; $i++ ) |
376 for ( $i = 2; $i <= 7; $i++ ) |
377 { |
377 { |
378 if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) ) |
378 if ( !preg_match('/^([A-z0-9 ]+?): (.+?)$/', trim($plugin_data[$i]), $match) ) |
379 return false; |
379 return false; |
380 $plugin_meta[ strtolower($match[1]) ] = $match[2]; |
380 $plugin_meta[ strtolower($match[1]) ] = $match[2]; |
381 } |
381 } |
382 } |
382 } |
383 else |
383 else |
384 { |
384 { |
385 // parse JSON block |
385 // parse JSON block |
386 $plugin_data =& $blockdata[0]['value']; |
386 $plugin_data =& $blockdata[0]['value']; |
387 $plugin_data = enano_clean_json(enano_trim_json($plugin_data)); |
387 $plugin_data = enano_clean_json(enano_trim_json($plugin_data)); |
388 try |
388 try |
389 { |
389 { |
390 $plugin_meta_uc = enano_json_decode($plugin_data); |
390 $plugin_meta_uc = enano_json_decode($plugin_data); |
391 } |
391 } |
392 catch ( Exception $e ) |
392 catch ( Exception $e ) |
393 { |
393 { |
394 return false; |
394 return false; |
395 } |
395 } |
396 // convert all the keys to lowercase |
396 // convert all the keys to lowercase |
397 $plugin_meta = array(); |
397 $plugin_meta = array(); |
398 foreach ( $plugin_meta_uc as $key => $value ) |
398 foreach ( $plugin_meta_uc as $key => $value ) |
399 { |
399 { |
400 $plugin_meta[ strtolower($key) ] = $value; |
400 $plugin_meta[ strtolower($key) ] = $value; |
401 } |
401 } |
402 } |
402 } |
403 } |
403 } |
404 if ( !isset($plugin_meta) || !is_array(@$plugin_meta) ) |
404 if ( !isset($plugin_meta) || !is_array(@$plugin_meta) ) |
405 { |
405 { |
406 // parsing didn't work. |
406 // parsing didn't work. |
407 return false; |
407 return false; |
408 } |
408 } |
409 // check for required keys |
409 // check for required keys |
410 $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri'); |
410 $required_keys = array('plugin name', 'plugin uri', 'description', 'author', 'version', 'author uri'); |
411 foreach ( $required_keys as $key ) |
411 foreach ( $required_keys as $key ) |
412 { |
412 { |
413 if ( !isset($plugin_meta[$key]) ) |
413 if ( !isset($plugin_meta[$key]) ) |
414 // not set, skip this plugin |
414 // not set, skip this plugin |
415 return false; |
415 return false; |
416 } |
416 } |
417 // decide if it's a system plugin |
417 // decide if it's a system plugin |
418 $plugin_meta['system plugin'] = in_array($dh, $this->system_plugins); |
418 $plugin_meta['system plugin'] = in_array($dh, $this->system_plugins); |
419 // reset installed variable |
419 // reset installed variable |
420 $plugin_meta['installed'] = false; |
420 $plugin_meta['installed'] = false; |
421 $plugin_meta['status'] = 0; |
421 $plugin_meta['status'] = 0; |
422 |
422 |
423 return $plugin_meta; |
423 return $plugin_meta; |
424 } |
424 } |
425 |
425 |
426 |
426 |
427 /** |
427 /** |
428 * Attempts to cache plugin information in a file to speed fetching. |
428 * Attempts to cache plugin information in a file to speed fetching. |
429 */ |
429 */ |
430 |
430 |
431 function generate_plugins_cache() |
431 function generate_plugins_cache() |
432 { |
432 { |
433 if ( getConfig('cache_thumbs') != '1' ) |
433 if ( getConfig('cache_thumbs') != '1' ) |
434 return; |
434 return; |
435 |
435 |
436 // fetch the most current info |
436 // fetch the most current info |
437 $plugin_info = $this->get_plugin_list(null, false); |
437 $plugin_info = $this->get_plugin_list(null, false); |
438 foreach ( $plugin_info as $plugin => &$info ) |
438 foreach ( $plugin_info as $plugin => &$info ) |
439 { |
439 { |
440 $info['file md5'] = $this->md5_header(ENANO_ROOT . "/plugins/$plugin"); |
440 $info['file md5'] = $this->md5_header(ENANO_ROOT . "/plugins/$plugin"); |
441 } |
441 } |
442 |
442 |
443 $this->update_plugins_cache($plugin_info); |
443 $this->update_plugins_cache($plugin_info); |
444 $GLOBALS['plugins_cache'] = $plugin_info; |
444 $GLOBALS['plugins_cache'] = $plugin_info; |
445 |
445 |
446 return true; |
446 return true; |
447 } |
447 } |
448 |
448 |
449 /** |
449 /** |
450 * Writes an information array to the cache file. |
450 * Writes an information array to the cache file. |
451 * @param array |
451 * @param array |
452 * @access private |
452 * @access private |
453 */ |
453 */ |
454 |
454 |
455 function update_plugins_cache($plugin_info) |
455 function update_plugins_cache($plugin_info) |
456 { |
456 { |
457 global $cache; |
457 global $cache; |
458 try |
458 try |
459 { |
459 { |
460 $result = $cache->store('plugins', $plugin_info, -1); |
460 $result = $cache->store('plugins', $plugin_info, -1); |
461 } |
461 } |
462 catch ( Exception $e ) |
462 catch ( Exception $e ) |
463 { |
463 { |
464 return false; |
464 return false; |
465 } |
465 } |
466 return $result; |
466 return $result; |
467 } |
467 } |
468 |
468 |
469 /** |
469 /** |
470 * Loads the plugins cache if any. |
470 * Loads the plugins cache if any. |
471 */ |
471 */ |
472 |
472 |
473 function load_plugins_cache() |
473 function load_plugins_cache() |
474 { |
474 { |
475 global $cache; |
475 global $cache; |
476 if ( $data = $cache->fetch('plugins') ) |
476 if ( $data = $cache->fetch('plugins') ) |
477 { |
477 { |
478 $GLOBALS['plugins_cache'] = $data; |
478 $GLOBALS['plugins_cache'] = $data; |
479 } |
479 } |
480 } |
480 } |
481 |
481 |
482 /** |
482 /** |
483 * Calculates the MD5 sum of the first 10 lines of a file. Useful for caching plugin header information. |
483 * Calculates the MD5 sum of the first 10 lines of a file. Useful for caching plugin header information. |
484 * @param string File |
484 * @param string File |
485 * @return string |
485 * @return string |
486 */ |
486 */ |
487 |
487 |
488 function md5_header($file) |
488 function md5_header($file) |
489 { |
489 { |
490 $fh = @fopen($file, 'r'); |
490 $fh = @fopen($file, 'r'); |
491 if ( !$fh ) |
491 if ( !$fh ) |
492 return false; |
492 return false; |
493 $i = 0; |
493 $i = 0; |
494 $h = ''; |
494 $h = ''; |
495 while ( $i < 10 ) |
495 while ( $i < 10 ) |
496 { |
496 { |
497 $line = fgets($fh, 8096); |
497 $line = fgets($fh, 8096); |
498 $h .= $line . "\n"; |
498 $h .= $line . "\n"; |
499 $i++; |
499 $i++; |
500 } |
500 } |
501 fclose($fh); |
501 fclose($fh); |
502 return md5($h); |
502 return md5($h); |
503 } |
503 } |
504 |
504 |
505 /** |
505 /** |
506 * Determines if a file is an authentication extension by looking at the file contents. |
506 * Determines if a file is an authentication extension by looking at the file contents. |
507 * @param string Plugin filename |
507 * @param string Plugin filename |
508 * @return bool |
508 * @return bool |
509 */ |
509 */ |
510 |
510 |
511 function is_file_auth_plugin($filename) |
511 function is_file_auth_plugin($filename) |
512 { |
512 { |
513 $filename = ENANO_ROOT . '/plugins/' . $filename; |
513 $filename = ENANO_ROOT . '/plugins/' . $filename; |
514 if ( !file_exists($filename) ) |
514 if ( !file_exists($filename) ) |
515 return false; |
515 return false; |
516 |
516 |
517 $info = $this->read_plugin_headers($filename); |
517 $info = $this->read_plugin_headers($filename); |
518 if ( isset($info['auth plugin']) ) |
518 if ( isset($info['auth plugin']) ) |
519 return true; |
519 return true; |
520 |
520 |
521 $contents = @file_get_contents($filename); |
521 $contents = @file_get_contents($filename); |
522 if ( strstr($contents, 'login_process_userdata_json') ) |
522 if ( strstr($contents, 'login_process_userdata_json') ) |
523 return true; |
523 return true; |
524 |
524 |
525 return false; |
525 return false; |
526 } |
526 } |
527 |
527 |
528 /** |
528 /** |
529 * Installs a plugin. |
529 * Installs a plugin. |
530 * @param string Filename of plugin. |
530 * @param string Filename of plugin. |
531 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
531 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
532 * @return array JSON-formatted but not encoded response |
532 * @return array JSON-formatted but not encoded response |
533 */ |
533 */ |
534 |
534 |
535 function install_plugin($filename, $plugin_list = null) |
535 function install_plugin($filename, $plugin_list = null) |
536 { |
536 { |
537 global $db, $session, $paths, $template, $plugins; // Common objects |
537 global $db, $session, $paths, $template, $plugins; // Common objects |
538 global $lang; |
538 global $lang; |
539 global $cache; |
539 global $cache; |
540 |
540 |
541 if ( defined('ENANO_DEMO_MODE') ) |
541 if ( defined('ENANO_DEMO_MODE') ) |
542 { |
542 { |
543 return array( |
543 return array( |
544 'mode' => 'error', |
544 'mode' => 'error', |
545 'error' => $lang->get('acppl_err_demo_mode') |
545 'error' => $lang->get('acppl_err_demo_mode') |
546 ); |
546 ); |
547 } |
547 } |
548 |
548 |
549 if ( !$plugin_list ) |
549 if ( !$plugin_list ) |
550 $plugin_list = $this->get_plugin_list(); |
550 $plugin_list = $this->get_plugin_list(); |
551 |
551 |
552 // we're gonna need this |
552 // we're gonna need this |
553 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
553 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
554 |
554 |
555 switch ( true ): case true: |
555 switch ( true ): case true: |
556 |
556 |
557 // is the plugin in the directory and awaiting installation? |
557 // is the plugin in the directory and awaiting installation? |
558 if ( !isset($plugin_list[$filename]) || ( |
558 if ( !isset($plugin_list[$filename]) || ( |
559 isset($plugin_list[$filename]) && $plugin_list[$filename]['installed'] |
559 isset($plugin_list[$filename]) && $plugin_list[$filename]['installed'] |
560 )) |
560 )) |
561 { |
561 { |
562 $return = array( |
562 $return = array( |
563 'mode' => 'error', |
563 'mode' => 'error', |
564 'error' => 'Invalid plugin specified.', |
564 'error' => 'Invalid plugin specified.', |
565 'debug' => $filename |
565 'debug' => $filename |
566 ); |
566 ); |
567 break; |
567 break; |
568 } |
568 } |
569 |
569 |
570 $dataset =& $plugin_list[$filename]; |
570 $dataset =& $plugin_list[$filename]; |
571 |
571 |
572 // load up the installer schema |
572 // load up the installer schema |
573 $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'install' ); |
573 $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'install' ); |
574 |
574 |
575 $sql = array(); |
575 $sql = array(); |
576 global $dbdriver; |
576 global $dbdriver; |
577 if ( !empty($schema) ) |
577 if ( !empty($schema) ) |
578 { |
578 { |
579 // Decide which schema to use |
579 // Decide which schema to use |
580 $use_schema = false; |
580 $use_schema = false; |
581 foreach ( $schema as $current_schema ) |
581 foreach ( $schema as $current_schema ) |
582 { |
582 { |
583 if ( isset($current_schema['dbms']) && $current_schema['dbms'] === $dbdriver ) |
583 if ( isset($current_schema['dbms']) && $current_schema['dbms'] === $dbdriver ) |
584 { |
584 { |
585 $use_schema =& $current_schema['value']; |
585 $use_schema =& $current_schema['value']; |
586 break; |
586 break; |
587 } |
587 } |
588 } |
588 } |
589 if ( !$use_schema ) |
589 if ( !$use_schema ) |
590 { |
590 { |
591 if ( !isset($schema[0]['dbms']) ) |
591 if ( !isset($schema[0]['dbms']) ) |
592 { |
592 { |
593 $use_schema =& $schema[0]['value']; |
593 $use_schema =& $schema[0]['value']; |
594 } |
594 } |
595 else |
595 else |
596 { |
596 { |
597 $return = array( |
597 $return = array( |
598 'mode' => 'error', |
598 'mode' => 'error', |
599 'error' => $lang->get('acppl_err_dmbs_not_supported', array('dbdriver' => $db->dbms_name)) |
599 'error' => $lang->get('acppl_err_dmbs_not_supported', array('dbdriver' => $db->dbms_name)) |
600 ); |
600 ); |
601 break; |
601 break; |
602 } |
602 } |
603 } |
603 } |
604 // parse SQL |
604 // parse SQL |
605 $parser = new SQL_Parser($use_schema, true); |
605 $parser = new SQL_Parser($use_schema, true); |
606 $parser->assign_vars(array( |
606 $parser->assign_vars(array( |
607 'TABLE_PREFIX' => table_prefix |
607 'TABLE_PREFIX' => table_prefix |
608 )); |
608 )); |
609 $sql = $parser->parse(); |
609 $sql = $parser->parse(); |
610 } |
610 } |
611 |
611 |
612 // schema is final, check queries |
612 // schema is final, check queries |
613 foreach ( $sql as $query ) |
613 foreach ( $sql as $query ) |
614 { |
614 { |
615 if ( !$db->check_query($query) ) |
615 if ( !$db->check_query($query) ) |
616 { |
616 { |
617 // aww crap, a query is bad |
617 // aww crap, a query is bad |
618 $return = array( |
618 $return = array( |
619 'mode' => 'error', |
619 'mode' => 'error', |
620 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
620 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
621 ); |
621 ); |
622 break 2; |
622 break 2; |
623 } |
623 } |
624 } |
624 } |
625 |
625 |
626 // this is it, perform installation |
626 // this is it, perform installation |
627 foreach ( $sql as $query ) |
627 foreach ( $sql as $query ) |
628 { |
628 { |
629 if ( substr($query, 0, 1) == '@' ) |
629 if ( substr($query, 0, 1) == '@' ) |
630 { |
630 { |
631 $query = substr($query, 1); |
631 $query = substr($query, 1); |
632 $db->sql_query($query); |
632 $db->sql_query($query); |
633 } |
633 } |
634 else |
634 else |
635 { |
635 { |
636 if ( !$db->sql_query($query) ) |
636 if ( !$db->sql_query($query) ) |
637 { |
637 { |
638 $return = array( |
638 $return = array( |
639 'mode' => 'error', |
639 'mode' => 'error', |
640 'error' => "[SQL] " . $db->sql_error() |
640 'error' => "[SQL] " . $db->sql_error() |
641 ); |
641 ); |
642 break 2; |
642 break 2; |
643 } |
643 } |
644 } |
644 } |
645 } |
645 } |
646 |
646 |
647 // log action |
647 // log action |
648 $time = time(); |
648 $time = time(); |
649 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
649 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
650 $username_db = $db->escape($session->username); |
650 $username_db = $db->escape($session->username); |
651 $file_db = $db->escape($filename); |
651 $file_db = $db->escape($filename); |
652 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
652 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
653 . " ('security', 'plugin_install', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
653 . " ('security', 'plugin_install', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
654 if ( !$q ) |
654 if ( !$q ) |
655 $db->_die(); |
655 $db->_die(); |
656 |
656 |
657 // register plugin |
657 // register plugin |
658 $version_db = $db->escape($dataset['version']); |
658 $version_db = $db->escape($dataset['version']); |
659 $filename_db = $db->escape($filename); |
659 $filename_db = $db->escape($filename); |
660 $flags = PLUGIN_INSTALLED; |
660 $flags = PLUGIN_INSTALLED; |
661 |
661 |
662 $q = $db->sql_query('INSERT INTO ' . table_prefix . "plugins ( plugin_version, plugin_filename, plugin_flags )\n" |
662 $q = $db->sql_query('INSERT INTO ' . table_prefix . "plugins ( plugin_version, plugin_filename, plugin_flags )\n" |
663 . " VALUES ( '$version_db', '$filename_db', $flags );"); |
663 . " VALUES ( '$version_db', '$filename_db', $flags );"); |
664 if ( !$q ) |
664 if ( !$q ) |
665 $db->die_json(); |
665 $db->die_json(); |
666 |
666 |
667 $plugin_list[$filename]['installed'] = true; |
667 $plugin_list[$filename]['installed'] = true; |
668 $this->reimport_plugin_strings($filename, $plugin_list); |
668 $this->reimport_plugin_strings($filename, $plugin_list); |
669 |
669 |
670 $return = array( |
670 $return = array( |
671 'success' => true |
671 'success' => true |
672 ); |
672 ); |
673 |
673 |
674 endswitch; |
674 endswitch; |
675 |
675 |
676 $cache->purge('plugins'); |
676 $cache->purge('plugins'); |
677 $cache->purge('page_meta'); |
677 $cache->purge('page_meta'); |
678 $cache->purge('anon_sidebar'); |
678 $cache->purge('anon_sidebar'); |
679 |
679 |
680 return $return; |
680 return $return; |
681 } |
681 } |
682 |
682 |
683 /** |
683 /** |
684 * Uninstalls a plugin, removing it completely from the database and calling any custom uninstallation code the plugin specifies. |
684 * Uninstalls a plugin, removing it completely from the database and calling any custom uninstallation code the plugin specifies. |
685 * @param string Filename of plugin. |
685 * @param string Filename of plugin. |
686 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
686 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
687 * @return array JSON-formatted but not encoded response |
687 * @return array JSON-formatted but not encoded response |
688 */ |
688 */ |
689 |
689 |
690 function uninstall_plugin($filename, $plugin_list = null) |
690 function uninstall_plugin($filename, $plugin_list = null) |
691 { |
691 { |
692 global $db, $session, $paths, $template, $plugins; // Common objects |
692 global $db, $session, $paths, $template, $plugins; // Common objects |
693 global $lang; |
693 global $lang; |
694 global $cache; |
694 global $cache; |
695 |
695 |
696 if ( defined('ENANO_DEMO_MODE') ) |
696 if ( defined('ENANO_DEMO_MODE') ) |
697 { |
697 { |
698 return array( |
698 return array( |
699 'mode' => 'error', |
699 'mode' => 'error', |
700 'error' => $lang->get('acppl_err_demo_mode') |
700 'error' => $lang->get('acppl_err_demo_mode') |
701 ); |
701 ); |
702 } |
702 } |
703 |
703 |
704 if ( !$plugin_list ) |
704 if ( !$plugin_list ) |
705 $plugin_list = $this->get_plugin_list(); |
705 $plugin_list = $this->get_plugin_list(); |
706 |
706 |
707 // we're gonna need this |
707 // we're gonna need this |
708 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
708 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
709 |
709 |
710 switch ( true ): case true: |
710 switch ( true ): case true: |
711 |
711 |
712 // is the plugin in the directory and already installed? |
712 // is the plugin in the directory and already installed? |
713 if ( !isset($plugin_list[$filename]) || ( |
713 if ( !isset($plugin_list[$filename]) || ( |
714 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
714 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
715 )) |
715 )) |
716 { |
716 { |
717 $return = array( |
717 $return = array( |
718 'mode' => 'error', |
718 'mode' => 'error', |
719 'error' => 'Invalid plugin specified.', |
719 'error' => 'Invalid plugin specified.', |
720 ); |
720 ); |
721 break; |
721 break; |
722 } |
722 } |
723 // get plugin id |
723 // get plugin id |
724 $dataset =& $plugin_list[$filename]; |
724 $dataset =& $plugin_list[$filename]; |
725 if ( empty($dataset['plugin id']) ) |
725 if ( empty($dataset['plugin id']) ) |
726 { |
726 { |
727 $return = array( |
727 $return = array( |
728 'mode' => 'error', |
728 'mode' => 'error', |
729 'error' => 'Couldn\'t retrieve plugin ID.', |
729 'error' => 'Couldn\'t retrieve plugin ID.', |
730 ); |
730 ); |
731 break; |
731 break; |
732 } |
732 } |
733 |
733 |
734 // load up the installer schema |
734 // load up the installer schema |
735 $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'uninstall' ); |
735 $schema = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'uninstall' ); |
736 |
736 |
737 $sql = array(); |
737 $sql = array(); |
738 global $dbdriver; |
738 global $dbdriver; |
739 if ( !empty($schema) ) |
739 if ( !empty($schema) ) |
740 { |
740 { |
741 // Decide which schema to use |
741 // Decide which schema to use |
742 $use_schema = false; |
742 $use_schema = false; |
743 foreach ( $schema as $current_schema ) |
743 foreach ( $schema as $current_schema ) |
744 { |
744 { |
745 if ( isset($current_schema['dbms']) && $current_schema['dbms'] === $dbdriver ) |
745 if ( isset($current_schema['dbms']) && $current_schema['dbms'] === $dbdriver ) |
746 { |
746 { |
747 $use_schema =& $current_schema['value']; |
747 $use_schema =& $current_schema['value']; |
748 break; |
748 break; |
749 } |
749 } |
750 } |
750 } |
751 if ( !$use_schema ) |
751 if ( !$use_schema ) |
752 { |
752 { |
753 if ( !isset($schema[0]['dbms']) ) |
753 if ( !isset($schema[0]['dbms']) ) |
754 { |
754 { |
755 $use_schema =& $schema[0]['value']; |
755 $use_schema =& $schema[0]['value']; |
756 } |
756 } |
757 else |
757 else |
758 { |
758 { |
759 $return = array( |
759 $return = array( |
760 'mode' => 'error', |
760 'mode' => 'error', |
761 'error' => $lang->get('acppl_err_dmbs_not_supported', array('dbdriver' => $db->dbms_name)) |
761 'error' => $lang->get('acppl_err_dmbs_not_supported', array('dbdriver' => $db->dbms_name)) |
762 ); |
762 ); |
763 break; |
763 break; |
764 } |
764 } |
765 } |
765 } |
766 // parse SQL |
766 // parse SQL |
767 $parser = new SQL_Parser($use_schema, true); |
767 $parser = new SQL_Parser($use_schema, true); |
768 $parser->assign_vars(array( |
768 $parser->assign_vars(array( |
769 'TABLE_PREFIX' => table_prefix |
769 'TABLE_PREFIX' => table_prefix |
770 )); |
770 )); |
771 $sql = $parser->parse(); |
771 $sql = $parser->parse(); |
772 } |
772 } |
773 |
773 |
774 // schema is final, check queries |
774 // schema is final, check queries |
775 foreach ( $sql as $query ) |
775 foreach ( $sql as $query ) |
776 { |
776 { |
777 if ( !$db->check_query($query) ) |
777 if ( !$db->check_query($query) ) |
778 { |
778 { |
779 // aww crap, a query is bad |
779 // aww crap, a query is bad |
780 $return = array( |
780 $return = array( |
781 'mode' => 'error', |
781 'mode' => 'error', |
782 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
782 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
783 ); |
783 ); |
784 break 2; |
784 break 2; |
785 } |
785 } |
786 } |
786 } |
787 |
787 |
788 // this is it, perform uninstallation |
788 // this is it, perform uninstallation |
789 foreach ( $sql as $query ) |
789 foreach ( $sql as $query ) |
790 { |
790 { |
791 if ( substr($query, 0, 1) == '@' ) |
791 if ( substr($query, 0, 1) == '@' ) |
792 { |
792 { |
793 $query = substr($query, 1); |
793 $query = substr($query, 1); |
794 $db->sql_query($query); |
794 $db->sql_query($query); |
795 } |
795 } |
796 else |
796 else |
797 { |
797 { |
798 if ( !$db->sql_query($query) ) |
798 if ( !$db->sql_query($query) ) |
799 { |
799 { |
800 $return = array( |
800 $return = array( |
801 'mode' => 'error', |
801 'mode' => 'error', |
802 'error' => "[SQL] " . $db->sql_error() |
802 'error' => "[SQL] " . $db->sql_error() |
803 ); |
803 ); |
804 break 2; |
804 break 2; |
805 } |
805 } |
806 } |
806 } |
807 } |
807 } |
808 |
808 |
809 // log action |
809 // log action |
810 $time = time(); |
810 $time = time(); |
811 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
811 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
812 $username_db = $db->escape($session->username); |
812 $username_db = $db->escape($session->username); |
813 $file_db = $db->escape($filename); |
813 $file_db = $db->escape($filename); |
814 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
814 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
815 . " ('security', 'plugin_uninstall', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
815 . " ('security', 'plugin_uninstall', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
816 if ( !$q ) |
816 if ( !$q ) |
817 $db->_die(); |
817 $db->_die(); |
818 |
818 |
819 // deregister plugin |
819 // deregister plugin |
820 $q = $db->sql_query('DELETE FROM ' . table_prefix . "plugins WHERE plugin_id = {$dataset['plugin id']};"); |
820 $q = $db->sql_query('DELETE FROM ' . table_prefix . "plugins WHERE plugin_id = {$dataset['plugin id']};"); |
821 if ( !$q ) |
821 if ( !$q ) |
822 $db->die_json(); |
822 $db->die_json(); |
823 |
823 |
824 $return = array( |
824 $return = array( |
825 'success' => true |
825 'success' => true |
826 ); |
826 ); |
827 |
827 |
828 endswitch; |
828 endswitch; |
829 |
829 |
830 $cache->purge('plugins'); |
830 $cache->purge('plugins'); |
831 $cache->purge('page_meta'); |
831 $cache->purge('page_meta'); |
832 $cache->purge('anon_sidebar'); |
832 $cache->purge('anon_sidebar'); |
833 |
833 |
834 return $return; |
834 return $return; |
835 } |
835 } |
836 |
836 |
837 /** |
837 /** |
838 * Very intelligently upgrades a plugin to the version specified in the filesystem. |
838 * Very intelligently upgrades a plugin to the version specified in the filesystem. |
839 * @param string Filename of plugin. |
839 * @param string Filename of plugin. |
840 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
840 * @param array The list of plugins as output by pluginLoader::get_plugin_list(). If not passed, the function is called, possibly wasting time. |
841 * @return array JSON-formatted but not encoded response |
841 * @return array JSON-formatted but not encoded response |
842 */ |
842 */ |
843 |
843 |
844 function upgrade_plugin($filename, $plugin_list = null) |
844 function upgrade_plugin($filename, $plugin_list = null) |
845 { |
845 { |
846 global $db, $session, $paths, $template, $plugins; // Common objects |
846 global $db, $session, $paths, $template, $plugins; // Common objects |
847 global $lang, $cache; |
847 global $lang, $cache; |
848 |
848 |
849 if ( !$plugin_list ) |
849 if ( !$plugin_list ) |
850 $plugin_list = $this->get_plugin_list(); |
850 $plugin_list = $this->get_plugin_list(); |
851 |
851 |
852 // we're gonna need this |
852 // we're gonna need this |
853 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
853 require_once ( ENANO_ROOT . '/includes/sql_parse.php' ); |
854 |
854 |
855 switch ( true ): case true: |
855 switch ( true ): case true: |
856 |
856 |
857 // is the plugin in the directory and already installed? |
857 // is the plugin in the directory and already installed? |
858 if ( !isset($plugin_list[$filename]) || ( |
858 if ( !isset($plugin_list[$filename]) || ( |
859 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
859 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
860 )) |
860 )) |
861 { |
861 { |
862 $return = array( |
862 $return = array( |
863 'mode' => 'error', |
863 'mode' => 'error', |
864 'error' => 'Invalid plugin specified.', |
864 'error' => 'Invalid plugin specified.', |
865 ); |
865 ); |
866 break; |
866 break; |
867 } |
867 } |
868 // get plugin id |
868 // get plugin id |
869 $dataset =& $plugin_list[$filename]; |
869 $dataset =& $plugin_list[$filename]; |
870 if ( empty($dataset['plugin id']) ) |
870 if ( empty($dataset['plugin id']) ) |
871 { |
871 { |
872 $return = array( |
872 $return = array( |
873 'mode' => 'error', |
873 'mode' => 'error', |
874 'error' => 'Couldn\'t retrieve plugin ID.', |
874 'error' => 'Couldn\'t retrieve plugin ID.', |
875 ); |
875 ); |
876 break; |
876 break; |
877 } |
877 } |
878 |
878 |
879 // |
879 // |
880 // Here we go with the main upgrade process. This is the same logic that the |
880 // Here we go with the main upgrade process. This is the same logic that the |
881 // Enano official upgrader uses, in fact it's the same SQL parser. We need |
881 // Enano official upgrader uses, in fact it's the same SQL parser. We need |
882 // list of all versions of the plugin to continue, though. |
882 // list of all versions of the plugin to continue, though. |
883 // |
883 // |
884 |
884 |
885 if ( !isset($dataset['version list']) || ( isset($dataset['version list']) && !is_array($dataset['version list']) ) ) |
885 if ( !isset($dataset['version list']) || ( isset($dataset['version list']) && !is_array($dataset['version list']) ) ) |
886 { |
886 { |
887 // no version list - update the version number but leave the rest alone |
887 // no version list - update the version number but leave the rest alone |
888 $version = $db->escape($dataset['version']); |
888 $version = $db->escape($dataset['version']); |
889 $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};"); |
889 $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};"); |
890 if ( !$q ) |
890 if ( !$q ) |
891 $db->die_json(); |
891 $db->die_json(); |
892 |
892 |
893 // send an error and notify the user even though it was technically a success |
893 // send an error and notify the user even though it was technically a success |
894 $return = array( |
894 $return = array( |
895 'mode' => 'error', |
895 'mode' => 'error', |
896 'error' => $lang->get('acppl_err_upgrade_not_supported'), |
896 'error' => $lang->get('acppl_err_upgrade_not_supported'), |
897 ); |
897 ); |
898 break; |
898 break; |
899 } |
899 } |
900 |
900 |
901 // build target list |
901 // build target list |
902 $versions = $dataset['version list']; |
902 $versions = $dataset['version list']; |
903 $indices = array_flip($versions); |
903 $indices = array_flip($versions); |
904 $installed = $dataset['version installed']; |
904 $installed = $dataset['version installed']; |
905 |
905 |
906 // is the current version upgradeable? |
906 // is the current version upgradeable? |
907 if ( !isset($indices[$installed]) ) |
907 if ( !isset($indices[$installed]) ) |
908 { |
908 { |
909 $return = array( |
909 $return = array( |
910 'mode' => 'error', |
910 'mode' => 'error', |
911 'error' => $lang->get('acppl_err_upgrade_bad_version'), |
911 'error' => $lang->get('acppl_err_upgrade_bad_version'), |
912 ); |
912 ); |
913 break; |
913 break; |
914 } |
914 } |
915 |
915 |
916 // does the plugin support upgrading to its own version? |
916 // does the plugin support upgrading to its own version? |
917 if ( !isset($indices[$installed]) ) |
917 if ( !isset($indices[$installed]) ) |
918 { |
918 { |
919 $return = array( |
919 $return = array( |
920 'mode' => 'error', |
920 'mode' => 'error', |
921 'error' => $lang->get('acppl_err_upgrade_bad_target_version'), |
921 'error' => $lang->get('acppl_err_upgrade_bad_target_version'), |
922 ); |
922 ); |
923 break; |
923 break; |
924 } |
924 } |
925 |
925 |
926 // list out which versions to do |
926 // list out which versions to do |
927 $index_start = @$indices[$installed]; |
927 $index_start = @$indices[$installed]; |
928 $index_stop = @$indices[$dataset['version']]; |
928 $index_stop = @$indices[$dataset['version']]; |
929 |
929 |
930 // Are we trying to go backwards? |
930 // Are we trying to go backwards? |
931 if ( $index_stop <= $index_start ) |
931 if ( $index_stop <= $index_start ) |
932 { |
932 { |
933 $return = array( |
933 $return = array( |
934 'mode' => 'error', |
934 'mode' => 'error', |
935 'error' => $lang->get('acppl_err_upgrade_to_older'), |
935 'error' => $lang->get('acppl_err_upgrade_to_older'), |
936 // 'debug' => "going from $installed ($index_start) to {$dataset['version']} ($index_stop)" |
936 // 'debug' => "going from $installed ($index_start) to {$dataset['version']} ($index_stop)" |
937 ); |
937 ); |
938 break; |
938 break; |
939 } |
939 } |
940 |
940 |
941 // build the list of version sets |
941 // build the list of version sets |
942 $ver_previous = $installed; |
942 $ver_previous = $installed; |
943 $targets = array(); |
943 $targets = array(); |
944 for ( $i = $index_start; $i <= $index_stop; $i++ ) |
944 for ( $i = $index_start; $i <= $index_stop; $i++ ) |
945 { |
945 { |
946 $targets[] = array($ver_previous, $versions[$i]); |
946 $targets[] = array($ver_previous, $versions[$i]); |
947 $ver_previous = $versions[$i]; |
947 $ver_previous = $versions[$i]; |
948 } |
948 } |
949 |
949 |
950 // parse out upgrade sections in plugin file |
950 // parse out upgrade sections in plugin file |
951 $plugin_blocks = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'upgrade' ); |
951 $plugin_blocks = $this->parse_plugin_blocks( ENANO_ROOT . '/plugins/' . $filename, 'upgrade' ); |
952 $sql_blocks = array(); |
952 $sql_blocks = array(); |
953 foreach ( $plugin_blocks as $block ) |
953 foreach ( $plugin_blocks as $block ) |
954 { |
954 { |
955 if ( !isset($block['from']) || !isset($block['to']) ) |
955 if ( !isset($block['from']) || !isset($block['to']) ) |
956 { |
956 { |
957 continue; |
957 continue; |
958 } |
958 } |
959 $key = "{$block['from']} TO {$block['to']}"; |
959 $key = "{$block['from']} TO {$block['to']}"; |
960 $sql_blocks[$key] = $block['value']; |
960 $sql_blocks[$key] = $block['value']; |
961 } |
961 } |
962 |
962 |
963 // do version list check |
963 // do version list check |
964 // for now we won't fret if a specific version set isn't found, we'll just |
964 // for now we won't fret if a specific version set isn't found, we'll just |
965 // not do that version and assume there were no DB changes. |
965 // not do that version and assume there were no DB changes. |
966 foreach ( $targets as $i => $target ) |
966 foreach ( $targets as $i => $target ) |
967 { |
967 { |
968 list($from, $to) = $target; |
968 list($from, $to) = $target; |
969 $key = "$from TO $to"; |
969 $key = "$from TO $to"; |
970 if ( !isset($sql_blocks[$key]) ) |
970 if ( !isset($sql_blocks[$key]) ) |
971 { |
971 { |
972 unset($targets[$i]); |
972 unset($targets[$i]); |
973 } |
973 } |
974 } |
974 } |
975 $targets = array_values($targets); |
975 $targets = array_values($targets); |
976 |
976 |
977 // parse and finalize schema |
977 // parse and finalize schema |
978 $schema = array(); |
978 $schema = array(); |
979 foreach ( $targets as $i => $target ) |
979 foreach ( $targets as $i => $target ) |
980 { |
980 { |
981 list($from, $to) = $target; |
981 list($from, $to) = $target; |
982 $key = "$from TO $to"; |
982 $key = "$from TO $to"; |
983 try |
983 try |
984 { |
984 { |
985 $parser = new SQL_Parser($sql_blocks[$key], true); |
985 $parser = new SQL_Parser($sql_blocks[$key], true); |
986 } |
986 } |
987 catch ( Exception $e ) |
987 catch ( Exception $e ) |
988 { |
988 { |
989 $return = array( |
989 $return = array( |
990 'mode' => 'error', |
990 'mode' => 'error', |
991 'error' => 'SQL parser init exception', |
991 'error' => 'SQL parser init exception', |
992 'debug' => "$e" |
992 'debug' => "$e" |
993 ); |
993 ); |
994 break 2; |
994 break 2; |
995 } |
995 } |
996 $parser->assign_vars(array( |
996 $parser->assign_vars(array( |
997 'TABLE_PREFIX' => table_prefix |
997 'TABLE_PREFIX' => table_prefix |
998 )); |
998 )); |
999 $parsed = $parser->parse(); |
999 $parsed = $parser->parse(); |
1000 foreach ( $parsed as $query ) |
1000 foreach ( $parsed as $query ) |
1001 { |
1001 { |
1002 $schema[] = $query; |
1002 $schema[] = $query; |
1003 } |
1003 } |
1004 } |
1004 } |
1005 |
1005 |
1006 // schema is final, check queries |
1006 // schema is final, check queries |
1007 foreach ( $schema as $query ) |
1007 foreach ( $schema as $query ) |
1008 { |
1008 { |
1009 if ( !$db->check_query($query) ) |
1009 if ( !$db->check_query($query) ) |
1010 { |
1010 { |
1011 // aww crap, a query is bad |
1011 // aww crap, a query is bad |
1012 $return = array( |
1012 $return = array( |
1013 'mode' => 'error', |
1013 'mode' => 'error', |
1014 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
1014 'error' => $lang->get('acppl_err_upgrade_bad_query'), |
1015 ); |
1015 ); |
1016 break 2; |
1016 break 2; |
1017 } |
1017 } |
1018 } |
1018 } |
1019 |
1019 |
1020 // this is it, perform upgrade |
1020 // this is it, perform upgrade |
1021 foreach ( $schema as $query ) |
1021 foreach ( $schema as $query ) |
1022 { |
1022 { |
1023 if ( substr($query, 0, 1) == '@' ) |
1023 if ( substr($query, 0, 1) == '@' ) |
1024 { |
1024 { |
1025 $query = substr($query, 1); |
1025 $query = substr($query, 1); |
1026 $db->sql_query($query); |
1026 $db->sql_query($query); |
1027 } |
1027 } |
1028 else |
1028 else |
1029 { |
1029 { |
1030 if ( !$db->sql_query($query) ) |
1030 if ( !$db->sql_query($query) ) |
1031 { |
1031 { |
1032 $return = array( |
1032 $return = array( |
1033 'mode' => 'error', |
1033 'mode' => 'error', |
1034 'error' => "[SQL] " . $db->sql_error() |
1034 'error' => "[SQL] " . $db->sql_error() |
1035 ); |
1035 ); |
1036 break 2; |
1036 break 2; |
1037 } |
1037 } |
1038 } |
1038 } |
1039 } |
1039 } |
1040 |
1040 |
1041 // log action |
1041 // log action |
1042 $time = time(); |
1042 $time = time(); |
1043 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
1043 $ip_db = $db->escape($_SERVER['REMOTE_ADDR']); |
1044 $username_db = $db->escape($session->username); |
1044 $username_db = $db->escape($session->username); |
1045 $file_db = $db->escape($filename); |
1045 $file_db = $db->escape($filename); |
1046 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
1046 $q = $db->sql_query('INSERT INTO '.table_prefix."logs(log_type, action, time_id, edit_summary, author, author_uid, page_text) VALUES\n" |
1047 . " ('security', 'plugin_upgrade', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
1047 . " ('security', 'plugin_upgrade', $time, '$ip_db', '$username_db', $session->user_id, '$file_db');"); |
1048 if ( !$q ) |
1048 if ( !$q ) |
1049 $db->_die(); |
1049 $db->_die(); |
1050 |
1050 |
1051 // update version number |
1051 // update version number |
1052 $version = $db->escape($dataset['version']); |
1052 $version = $db->escape($dataset['version']); |
1053 $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};"); |
1053 $q = $db->sql_query('UPDATE ' . table_prefix . "plugins SET plugin_version = '$version' WHERE plugin_id = {$dataset['plugin id']};"); |
1054 if ( !$q ) |
1054 if ( !$q ) |
1055 $db->die_json(); |
1055 $db->die_json(); |
1056 |
1056 |
1057 // all done :-) |
1057 // all done :-) |
1058 $return = array( |
1058 $return = array( |
1059 'success' => true |
1059 'success' => true |
1060 ); |
1060 ); |
1061 |
1061 |
1062 endswitch; |
1062 endswitch; |
1063 |
1063 |
1064 $cache->purge('plugins'); |
1064 $cache->purge('plugins'); |
1065 $cache->purge('page_meta'); |
1065 $cache->purge('page_meta'); |
1066 $cache->purge('anon_sidebar'); |
1066 $cache->purge('anon_sidebar'); |
1067 |
1067 |
1068 return $return; |
1068 return $return; |
1069 } |
1069 } |
1070 |
1070 |
1071 /** |
1071 /** |
1072 * Re-imports the language strings from a plugin. |
1072 * Re-imports the language strings from a plugin. |
1073 * @param string File name |
1073 * @param string File name |
1074 * @return array Enano JSON response protocol |
1074 * @return array Enano JSON response protocol |
1075 */ |
1075 */ |
1076 |
1076 |
1077 function reimport_plugin_strings($filename, $plugin_list = null) |
1077 function reimport_plugin_strings($filename, $plugin_list = null) |
1078 { |
1078 { |
1079 global $db, $session, $paths, $template, $plugins; // Common objects |
1079 global $db, $session, $paths, $template, $plugins; // Common objects |
1080 global $lang; |
1080 global $lang; |
1081 |
1081 |
1082 if ( !$plugin_list ) |
1082 if ( !$plugin_list ) |
1083 $plugin_list = $this->get_plugin_list(); |
1083 $plugin_list = $this->get_plugin_list(); |
1084 |
1084 |
1085 switch ( true ): case true: |
1085 switch ( true ): case true: |
1086 |
1086 |
1087 // is the plugin in the directory and already installed? |
1087 // is the plugin in the directory and already installed? |
1088 if ( !isset($plugin_list[$filename]) || ( |
1088 if ( !isset($plugin_list[$filename]) || ( |
1089 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
1089 isset($plugin_list[$filename]) && !$plugin_list[$filename]['installed'] |
1090 )) |
1090 )) |
1091 { |
1091 { |
1092 $return = array( |
1092 $return = array( |
1093 'mode' => 'error', |
1093 'mode' => 'error', |
1094 'error' => 'Invalid plugin specified.', |
1094 'error' => 'Invalid plugin specified.', |
1095 ); |
1095 ); |
1096 break; |
1096 break; |
1097 } |
1097 } |
1098 // get plugin data |
1098 // get plugin data |
1099 $dataset =& $plugin_list[$filename]; |
1099 $dataset =& $plugin_list[$filename]; |
1100 |
1100 |
1101 // check for a language block |
1101 // check for a language block |
1102 $blocks = self::parse_plugin_blocks(ENANO_ROOT . '/plugins/' . $filename, 'language'); |
1102 $blocks = self::parse_plugin_blocks(ENANO_ROOT . '/plugins/' . $filename, 'language'); |
1103 if ( count($blocks) < 1 ) |
1103 if ( count($blocks) < 1 ) |
1104 { |
1104 { |
1105 return array( |
1105 return array( |
1106 'mode' => 'error', |
1106 'mode' => 'error', |
1107 'error' => $lang->get('acppl_err_import_no_strings') |
1107 'error' => $lang->get('acppl_err_import_no_strings') |
1108 ); |
1108 ); |
1109 } |
1109 } |
1110 |
1110 |
1111 $result = $lang->import_plugin(ENANO_ROOT . '/plugins/' . $filename); |
1111 $result = $lang->import_plugin(ENANO_ROOT . '/plugins/' . $filename); |
1112 if ( $result ) |
1112 if ( $result ) |
1113 { |
1113 { |
1114 return array( |
1114 return array( |
1115 'success' => true |
1115 'success' => true |
1116 ); |
1116 ); |
1117 } |
1117 } |
1118 else |
1118 else |
1119 { |
1119 { |
1120 return array( |
1120 return array( |
1121 'mode' => 'error', |
1121 'mode' => 'error', |
1122 'error' => 'Language API returned error' |
1122 'error' => 'Language API returned error' |
1123 ); |
1123 ); |
1124 } |
1124 } |
1125 |
1125 |
1126 endswitch; |
1126 endswitch; |
1127 } |
1127 } |
1128 } |
1128 } |
1129 |
1129 |
1130 ?> |
1130 ?> |