1 | <?php |
||
2 | |||
3 | namespace Elgg\Database; |
||
4 | |||
5 | use DatabaseException; |
||
6 | use ElggCache; |
||
7 | use Elgg\Database; |
||
8 | use Elgg\Profilable; |
||
9 | use ElggPlugin; |
||
10 | use ElggUser; |
||
11 | use Exception; |
||
12 | |||
13 | /** |
||
14 | * Persistent, installation-wide key-value storage. |
||
15 | * |
||
16 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
17 | * |
||
18 | * @access private |
||
19 | * |
||
20 | * @since 1.10.0 |
||
21 | */ |
||
22 | class Plugins { |
||
23 | |||
24 | use Profilable; |
||
25 | |||
26 | /** |
||
27 | * @var ElggPlugin[] |
||
28 | */ |
||
29 | protected $boot_plugins; |
||
30 | |||
31 | /** |
||
32 | * @var array|null |
||
33 | */ |
||
34 | protected $provides_cache; |
||
35 | |||
36 | /** |
||
37 | * @var string[] Active plugins, with plugin ID => GUID. Missing keys imply inactive plugins. |
||
38 | */ |
||
39 | protected $active_guids = []; |
||
40 | |||
41 | /** |
||
42 | * @var bool Has $active_guids been populated? |
||
43 | */ |
||
44 | protected $active_guids_known = false; |
||
45 | |||
46 | /** |
||
47 | * @var ElggCache |
||
48 | */ |
||
49 | protected $cache; |
||
50 | |||
51 | /** |
||
52 | * @var Database |
||
53 | */ |
||
54 | protected $db; |
||
55 | |||
56 | /** |
||
57 | * Constructor |
||
58 | * |
||
59 | * @param ElggCache $cache Cache for referencing plugins by ID |
||
60 | * @param Database $db Database |
||
61 | */ |
||
62 | 4417 | public function __construct(ElggCache $cache, Database $db) { |
|
63 | 4417 | $this->cache = $cache; |
|
64 | 4417 | $this->db = $db; |
|
65 | 4417 | } |
|
66 | |||
67 | /** |
||
68 | * Set the list of active plugins according to the boot data cache |
||
69 | * |
||
70 | * @param ElggPlugin[]|null $plugins Set of active plugins |
||
71 | * |
||
72 | * @return void |
||
73 | */ |
||
74 | 4779 | public function setBootPlugins($plugins) { |
|
75 | 4779 | $this->boot_plugins = $plugins; |
|
76 | 4779 | if (is_array($plugins)) { |
|
77 | 4777 | foreach ($plugins as $plugin) { |
|
78 | 13 | if (!$plugin instanceof ElggPlugin || !$plugin->getID()) { |
|
79 | continue; |
||
80 | } |
||
81 | 13 | $this->cache->save($plugin->getID(), $plugin); |
|
82 | } |
||
83 | } |
||
84 | 4779 | } |
|
85 | |||
86 | /** |
||
87 | * Clear plugin caches |
||
88 | * @return void |
||
89 | */ |
||
90 | 1 | public function clear() { |
|
91 | 1 | $this->cache->clear(); |
|
92 | 1 | $this->invalidateProvidesCache(); |
|
93 | 1 | $this->invalidateIsActiveCache(); |
|
94 | 1 | } |
|
95 | |||
96 | /** |
||
97 | * Returns a list of plugin directory names from a base directory. |
||
98 | * |
||
99 | * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path. |
||
100 | * Must have a trailing slash. |
||
101 | * |
||
102 | * @return array Array of directory names (not full paths) |
||
103 | * @access private |
||
104 | */ |
||
105 | 2 | public function getDirsInDir($dir = null) { |
|
106 | 2 | if (!$dir) { |
|
107 | $dir = elgg_get_plugins_path(); |
||
108 | } |
||
109 | |||
110 | 2 | $plugin_dirs = []; |
|
111 | 2 | $handle = opendir($dir); |
|
112 | |||
113 | 2 | if ($handle) { |
|
114 | 2 | while ($plugin_dir = readdir($handle)) { |
|
115 | // must be directory and not begin with a . |
||
116 | 2 | if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) { |
|
117 | 2 | $plugin_dirs[] = $plugin_dir; |
|
118 | } |
||
119 | } |
||
120 | } |
||
121 | |||
122 | 2 | sort($plugin_dirs); |
|
123 | |||
124 | 2 | return $plugin_dirs; |
|
125 | } |
||
126 | |||
127 | /** |
||
128 | * Discovers plugins in the plugins_path setting and creates \ElggPlugin |
||
129 | * entities for them if they don't exist. If there are plugins with entities |
||
130 | * but not actual files, will disable the \ElggPlugin entities and mark as inactive. |
||
131 | * The \ElggPlugin object holds config data, so don't delete. |
||
132 | * |
||
133 | * @return bool |
||
134 | * @throws DatabaseException |
||
135 | * @throws \PluginException |
||
136 | * @access private |
||
137 | */ |
||
138 | 2 | public function generateEntities() { |
|
139 | |||
140 | 2 | $mod_dir = elgg_get_plugins_path(); |
|
141 | |||
142 | // ignore access in case this is called with no admin logged in - needed for creating plugins perhaps? |
||
143 | 2 | $old_ia = elgg_set_ignore_access(true); |
|
144 | |||
145 | // show hidden entities so that we can enable them if appropriate |
||
146 | 2 | $old_access = access_show_hidden_entities(true); |
|
147 | |||
148 | 2 | $known_plugins = $this->find('all'); |
|
149 | /* @var \ElggPlugin[] $known_plugins */ |
||
150 | |||
151 | 2 | if (!$known_plugins) { |
|
152 | 2 | $known_plugins = []; |
|
153 | } |
||
154 | |||
155 | // map paths to indexes |
||
156 | 2 | $id_map = []; |
|
157 | 2 | foreach ($known_plugins as $i => $plugin) { |
|
158 | // if the ID is wrong, delete the plugin because we can never load it. |
||
159 | $id = $plugin->getID(); |
||
160 | if (!$id) { |
||
161 | $plugin->delete(); |
||
162 | unset($known_plugins[$i]); |
||
163 | continue; |
||
164 | } |
||
165 | $id_map[$plugin->getID()] = $i; |
||
166 | $plugin->cache(); |
||
167 | } |
||
168 | |||
169 | 2 | $physical_plugins = $this->getDirsInDir($mod_dir); |
|
170 | 2 | if (!$physical_plugins) { |
|
171 | elgg_set_ignore_access($old_ia); |
||
172 | |||
173 | return false; |
||
174 | } |
||
175 | |||
176 | // check real plugins against known ones |
||
177 | 2 | foreach ($physical_plugins as $plugin_id) { |
|
178 | // is this already in the db? |
||
179 | 2 | if (array_key_exists($plugin_id, $id_map)) { |
|
180 | $index = $id_map[$plugin_id]; |
||
181 | $plugin = $known_plugins[$index]; |
||
182 | // was this plugin deleted and its entity disabled? |
||
183 | if (!$plugin->isEnabled()) { |
||
184 | $plugin->enable(); |
||
185 | $plugin->deactivate(); |
||
186 | $plugin->setPriority('last'); |
||
187 | } |
||
188 | |||
189 | // remove from the list of plugins to disable |
||
190 | unset($known_plugins[$index]); |
||
191 | } else { |
||
192 | // create new plugin |
||
193 | // priority is forced to last in save() if not set. |
||
194 | 2 | $plugin = ElggPlugin::fromId($plugin_id); |
|
195 | 2 | $plugin->cache(); |
|
196 | } |
||
197 | } |
||
198 | |||
199 | // everything remaining in $known_plugins needs to be disabled |
||
200 | // because they are entities, but their dirs were removed. |
||
201 | // don't delete the entities because they hold settings. |
||
202 | 2 | foreach ($known_plugins as $plugin) { |
|
203 | if ($plugin->isActive()) { |
||
204 | $plugin->deactivate(); |
||
205 | } |
||
206 | // remove the priority. |
||
207 | $name = $this->namespacePrivateSetting('internal', 'priority'); |
||
208 | remove_private_setting($plugin->guid, $name); |
||
209 | if ($plugin->isEnabled()) { |
||
210 | $plugin->disable(); |
||
211 | } |
||
212 | } |
||
213 | |||
214 | 2 | access_show_hidden_entities($old_access); |
|
215 | 2 | elgg_set_ignore_access($old_ia); |
|
216 | |||
217 | 2 | return true; |
|
218 | } |
||
219 | |||
220 | /** |
||
221 | * Cache a reference to this plugin by its ID |
||
222 | * |
||
223 | * @param ElggPlugin $plugin the plugin to cache |
||
224 | * |
||
225 | * @return void |
||
226 | * |
||
227 | * @access private |
||
228 | */ |
||
229 | 308 | public function cache(ElggPlugin $plugin) { |
|
230 | 308 | if (!$plugin->getID()) { |
|
231 | 288 | return; |
|
232 | } |
||
233 | 32 | $this->cache->save($plugin->getID(), $plugin); |
|
234 | 32 | } |
|
235 | |||
236 | /** |
||
237 | * Remove plugin from cache |
||
238 | * |
||
239 | * @param string $plugin_id Plugin ID |
||
240 | * |
||
241 | * @return void |
||
242 | */ |
||
243 | 289 | public function invalidateCache($plugin_id) { |
|
244 | try { |
||
245 | 289 | $this->cache->delete($plugin_id); |
|
246 | 289 | $this->invalidateProvidesCache(); |
|
247 | } catch (\InvalidArgumentException $ex) { |
||
248 | // A plugin must have been deactivated due to missing folder |
||
249 | // without proper cleanup |
||
250 | elgg_flush_caches(); |
||
251 | } |
||
252 | 289 | } |
|
253 | |||
254 | /** |
||
255 | * Returns an \ElggPlugin object with the path $path. |
||
256 | * |
||
257 | * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. |
||
258 | * |
||
259 | * @return ElggPlugin|null |
||
260 | */ |
||
261 | 347 | public function get($plugin_id) { |
|
262 | 347 | if (!$plugin_id) { |
|
263 | return; |
||
264 | } |
||
265 | |||
266 | 347 | $fallback = function () use ($plugin_id) { |
|
267 | 301 | $plugins = elgg_get_entities([ |
|
268 | 301 | 'type' => 'object', |
|
269 | 301 | 'subtype' => 'plugin', |
|
270 | 'metadata_name_value_pairs' => [ |
||
271 | 301 | 'name' => 'title', |
|
272 | 301 | 'value' => $plugin_id, |
|
273 | ], |
||
274 | 301 | 'limit' => 1, |
|
275 | 'distinct' => false, |
||
276 | ]); |
||
277 | |||
278 | 301 | if ($plugins) { |
|
279 | 16 | return $plugins[0]; |
|
280 | } |
||
281 | |||
282 | 288 | return null; |
|
283 | 347 | }; |
|
284 | |||
285 | 347 | $plugin = $this->cache->load($plugin_id); |
|
286 | 347 | if (!isset($plugin)) { |
|
287 | 301 | $plugin = $fallback(); |
|
288 | 301 | if ($plugin instanceof ElggPlugin) { |
|
289 | 16 | $plugin->cache(); |
|
290 | } |
||
291 | } |
||
292 | |||
293 | 347 | return $plugin; |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * Returns if a plugin exists in the system. |
||
298 | * |
||
299 | * @warning This checks only plugins that are registered in the system! |
||
300 | * If the plugin cache is outdated, be sure to regenerate it with |
||
301 | * {@link _elgg_generate_plugin_objects()} first. |
||
302 | * |
||
303 | * @param string $id The plugin ID. |
||
304 | * |
||
305 | * @return bool |
||
306 | */ |
||
307 | function exists($id) { |
||
308 | return (bool) $this->get($id); |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Returns the highest priority of the plugins |
||
313 | * |
||
314 | * @return int |
||
315 | * @access private |
||
316 | * @throws DatabaseException |
||
317 | */ |
||
318 | 7 | public function getMaxPriority() { |
|
319 | 7 | $priority = $this->namespacePrivateSetting('internal', 'priority'); |
|
320 | |||
321 | 7 | $qb = Select::fromTable('entities', 'e'); |
|
322 | 7 | $qb->select('MAX(CAST(ps.value AS unsigned)) as max') |
|
323 | 7 | ->join('e', 'private_settings', 'ps', 'e.guid = ps.entity_guid') |
|
324 | 7 | ->where($qb->compare('ps.name', '=', $priority, ELGG_VALUE_STRING)) |
|
325 | 7 | ->andWhere($qb->compare('e.type', '=', 'object', ELGG_VALUE_STRING)) |
|
326 | 7 | ->andWhere($qb->compare('e.subtype', '=', 'plugin', ELGG_VALUE_STRING)); |
|
327 | |||
328 | 7 | $data = _elgg_services()->db->getDataRow($qb); |
|
329 | |||
330 | 7 | $max = 1; |
|
331 | 7 | if ($data) { |
|
332 | 7 | $max = (int) $data->max; |
|
333 | } |
||
334 | |||
335 | 7 | return max(1, $max); |
|
336 | } |
||
337 | |||
338 | /** |
||
339 | * Returns if a plugin is active for a current site. |
||
340 | * |
||
341 | * @param string $plugin_id The plugin ID |
||
342 | * |
||
343 | * @return bool |
||
344 | */ |
||
345 | 5 | function isActive($plugin_id) { |
|
346 | 5 | if ($this->active_guids_known) { |
|
347 | 1 | return isset($this->active_guids[$plugin_id]); |
|
348 | } |
||
349 | |||
350 | 4 | $site = elgg_get_site_entity(); |
|
351 | |||
352 | 4 | if (!($site instanceof \ElggSite)) { |
|
353 | return false; |
||
354 | } |
||
355 | |||
356 | 4 | $plugin = $this->get($plugin_id); |
|
357 | |||
358 | 4 | if (!$plugin) { |
|
359 | return false; |
||
360 | } |
||
361 | |||
362 | 4 | return $plugin->isActive(); |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * Loads all active plugins in the order specified in the tool admin panel. |
||
367 | * |
||
368 | * @note This is called on every page load. If a plugin is active and problematic, it |
||
369 | * will be disabled and a visible error emitted. This does not check the deps system because |
||
370 | * that was too slow. |
||
371 | * |
||
372 | * @return bool |
||
373 | * @access private |
||
374 | * @throws \PluginException |
||
375 | */ |
||
376 | 18 | function load() { |
|
377 | 18 | if ($this->timer) { |
|
378 | $this->timer->begin([__METHOD__]); |
||
379 | } |
||
380 | |||
381 | 18 | $plugins_path = elgg_get_plugins_path(); |
|
382 | 18 | $start_flags = ELGG_PLUGIN_INCLUDE_START | |
|
383 | 18 | ELGG_PLUGIN_REGISTER_VIEWS | |
|
384 | 18 | ELGG_PLUGIN_REGISTER_ACTIONS | |
|
385 | 18 | ELGG_PLUGIN_REGISTER_ROUTES | |
|
386 | 18 | ELGG_PLUGIN_REGISTER_LANGUAGES | |
|
387 | 18 | ELGG_PLUGIN_REGISTER_WIDGETS | |
|
388 | 18 | ELGG_PLUGIN_REGISTER_CLASSES; |
|
389 | |||
390 | 18 | if (!$plugins_path) { |
|
391 | return false; |
||
392 | } |
||
393 | |||
394 | // temporary disable all plugins if there is a file called 'disabled' in the plugin dir |
||
395 | 18 | if (file_exists("$plugins_path/disabled")) { |
|
396 | if (elgg_is_admin_logged_in() && elgg_in_context('admin')) { |
||
397 | system_message(_elgg_services()->translator->translate('plugins:disabled')); |
||
398 | } |
||
399 | |||
400 | return false; |
||
401 | } |
||
402 | |||
403 | 18 | $config = _elgg_config(); |
|
404 | |||
405 | 18 | if ($config->system_cache_loaded) { |
|
406 | 12 | $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_VIEWS; |
|
407 | } |
||
408 | |||
409 | 18 | if (_elgg_services()->translator->wasLoadedFromCache()) { |
|
410 | 12 | $start_flags = $start_flags & ~ELGG_PLUGIN_REGISTER_LANGUAGES; |
|
411 | } |
||
412 | |||
413 | 18 | $plugins = $this->boot_plugins; |
|
414 | 18 | if (!$plugins) { |
|
0 ignored issues
–
show
|
|||
415 | 5 | $this->active_guids_known = true; |
|
416 | |||
417 | 5 | return true; |
|
418 | } |
||
419 | |||
420 | 13 | $return = true; |
|
421 | 13 | foreach ($plugins as $plugin) { |
|
422 | 13 | $id = $plugin->getID(); |
|
423 | try { |
||
424 | 13 | $plugin->start($start_flags); |
|
425 | 13 | $this->active_guids[$id] = $plugin->guid; |
|
426 | } catch (Exception $e) { |
||
427 | $disable_plugins = _elgg_config()->auto_disable_plugins; |
||
428 | if ($disable_plugins === null) { |
||
429 | $disable_plugins = true; |
||
430 | } |
||
431 | if ($disable_plugins) { |
||
432 | $plugin->deactivate(); |
||
433 | |||
434 | $msg = _elgg_services()->translator->translate('PluginException:CannotStart', |
||
435 | [$id, $plugin->guid, $e->getMessage()]); |
||
436 | elgg_add_admin_notice("cannot_start $id", $msg); |
||
437 | 13 | $return = false; |
|
438 | } |
||
439 | } |
||
440 | } |
||
441 | |||
442 | 13 | $this->active_guids_known = true; |
|
443 | |||
444 | 13 | if ($this->timer) { |
|
445 | $this->timer->end([__METHOD__]); |
||
446 | } |
||
447 | |||
448 | 13 | return $return; |
|
449 | } |
||
450 | |||
451 | /** |
||
452 | * Returns an ordered list of plugins |
||
453 | * |
||
454 | * @param string $status The status of the plugins. active, inactive, or all. |
||
455 | * |
||
456 | * @return ElggPlugin[] |
||
457 | */ |
||
458 | 15 | public function find($status = 'active') { |
|
459 | 15 | if (!_elgg_services()->db) { |
|
460 | return []; |
||
461 | } |
||
462 | |||
463 | 15 | if ($status === 'active' && isset($this->boot_plugins)) { |
|
464 | 3 | $plugins = $this->boot_plugins; |
|
465 | } else { |
||
466 | 14 | $priority = $this->namespacePrivateSetting('internal', 'priority'); |
|
467 | 14 | $site_guid = 1; |
|
468 | |||
469 | // grab plugins |
||
470 | $options = [ |
||
471 | 14 | 'type' => 'object', |
|
472 | 14 | 'subtype' => 'plugin', |
|
473 | 14 | 'limit' => 0, |
|
474 | 'selects' => ['ps.value'], |
||
475 | 14 | 'private_setting_names' => [$priority], |
|
476 | // ORDER BY CAST(ps.value) is super slow. We usort() below. |
||
477 | 'order_by' => false, |
||
478 | ]; |
||
479 | |||
480 | 14 | switch ($status) { |
|
481 | case 'active': |
||
482 | 14 | $options['relationship'] = 'active_plugin'; |
|
483 | 14 | $options['relationship_guid'] = $site_guid; |
|
484 | 14 | $options['inverse_relationship'] = true; |
|
485 | 14 | break; |
|
486 | |||
487 | case 'inactive': |
||
488 | $options['wheres'][] = function (QueryBuilder $qb) use ($site_guid) { |
||
489 | $subquery = $qb->subquery('entity_relationships', 'active_er'); |
||
490 | $subquery->select('*') |
||
491 | ->where($qb->compare('active_er.guid_one', '=', 'e.guid')) |
||
492 | ->andWhere($qb->compare('active_er.relationship', '=', 'active_plugin', ELGG_VALUE_STRING)) |
||
493 | ->andWhere($qb->compare('active_er.guid_two', '=', 1)); |
||
494 | |||
495 | return "NOT EXISTS ({$subquery->getSQL()})"; |
||
496 | }; |
||
497 | break; |
||
498 | |||
499 | case 'all': |
||
500 | default: |
||
501 | break; |
||
502 | } |
||
503 | |||
504 | 14 | $old_ia = elgg_set_ignore_access(true); |
|
505 | 14 | $plugins = elgg_get_entities($options) ? : []; |
|
506 | 14 | elgg_set_ignore_access($old_ia); |
|
507 | } |
||
508 | |||
509 | 15 | usort($plugins, function (ElggPlugin $a, ElggPlugin $b) { |
|
510 | 15 | $a_value = $a->getVolatileData('select:value'); |
|
511 | 15 | $b_value = $b->getVolatileData('select:value'); |
|
512 | |||
513 | 15 | if ($b_value !== $a_value) { |
|
514 | 15 | return $a_value - $b_value; |
|
515 | } else { |
||
516 | 15 | return $a->guid - $b->guid; |
|
517 | } |
||
518 | 15 | }); |
|
519 | |||
520 | 15 | return $plugins; |
|
521 | } |
||
522 | |||
523 | /** |
||
524 | * Reorder plugins to an order specified by the array. |
||
525 | * Plugins not included in this array will be appended to the end. |
||
526 | * |
||
527 | * @note This doesn't use the \ElggPlugin->setPriority() method because |
||
528 | * all plugins are being changed and we don't want it to automatically |
||
529 | * reorder plugins. |
||
530 | * @todo Can this be done in a single sql command? |
||
531 | * |
||
532 | * @param array $order An array of plugin ids in the order to set them |
||
533 | * |
||
534 | * @return bool |
||
535 | * @access private |
||
536 | */ |
||
537 | function setPriorities(array $order) { |
||
538 | $name = $this->namespacePrivateSetting('internal', 'priority'); |
||
539 | |||
540 | $plugins = $this->find('any'); |
||
541 | if (!$plugins) { |
||
542 | return false; |
||
543 | } |
||
544 | |||
545 | // reindex to get standard counting. no need to increment by 10. |
||
546 | // though we do start with 1 |
||
547 | $order = array_values($order); |
||
548 | |||
549 | $missing_plugins = []; |
||
550 | /* @var ElggPlugin[] $missing_plugins */ |
||
551 | |||
552 | $priority = 0; |
||
553 | foreach ($plugins as $plugin) { |
||
554 | $plugin_id = $plugin->getID(); |
||
555 | |||
556 | if (!in_array($plugin_id, $order)) { |
||
557 | $missing_plugins[] = $plugin; |
||
558 | continue; |
||
559 | } |
||
560 | |||
561 | $priority = array_search($plugin_id, $order) + 1; |
||
562 | |||
563 | if (!$plugin->setPrivateSetting($name, $priority)) { |
||
564 | return false; |
||
565 | } |
||
566 | } |
||
567 | |||
568 | // set the missing plugins' priorities |
||
569 | if (empty($missing_plugins)) { |
||
570 | return true; |
||
571 | } |
||
572 | |||
573 | foreach ($missing_plugins as $plugin) { |
||
574 | $priority++; |
||
575 | if (!$plugin->setPrivateSetting($name, $priority)) { |
||
576 | return false; |
||
577 | } |
||
578 | } |
||
579 | |||
580 | return true; |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * Reindexes all plugin priorities starting at 1. |
||
585 | * |
||
586 | * @return bool |
||
587 | * @access private |
||
588 | */ |
||
589 | function reindexPriorities() { |
||
590 | return $this->setPriorities([]); |
||
591 | } |
||
592 | |||
593 | /** |
||
594 | * Namespaces a string to be used as a private setting name for a plugin. |
||
595 | * |
||
596 | * For user_settings, two namespaces are added: a user setting namespace and the |
||
597 | * plugin id. |
||
598 | * |
||
599 | * For internal (plugin priority), there is a single internal namespace added. |
||
600 | * |
||
601 | * @param string $type The type of setting: user_setting or internal. |
||
602 | * @param string $name The name to namespace. |
||
603 | * @param string $id The plugin's ID to namespace with. Required for user_setting. |
||
604 | * |
||
605 | * @return string |
||
606 | * @access private |
||
607 | */ |
||
608 | 39 | function namespacePrivateSetting($type, $name, $id = null) { |
|
609 | 39 | switch ($type) { |
|
610 | case 'user_setting': |
||
611 | 39 | if (!$id) { |
|
612 | throw new \InvalidArgumentException("You must pass the plugin id for user settings"); |
||
613 | } |
||
614 | 39 | $name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name"; |
|
615 | 39 | break; |
|
616 | |||
617 | case 'internal': |
||
618 | 20 | $name = ELGG_PLUGIN_INTERNAL_PREFIX . $name; |
|
619 | 20 | break; |
|
620 | } |
||
621 | |||
622 | 39 | return $name; |
|
623 | } |
||
624 | |||
625 | |||
626 | /** |
||
627 | * Returns an array of all provides from all active plugins. |
||
628 | * |
||
629 | * Array in the form array( |
||
630 | * 'provide_type' => array( |
||
631 | * 'provided_name' => array( |
||
632 | * 'version' => '1.8', |
||
633 | * 'provided_by' => 'provider_plugin_id' |
||
634 | * ) |
||
635 | * ) |
||
636 | * ) |
||
637 | * |
||
638 | * @param string $type The type of provides to return |
||
639 | * @param string $name A specific provided name to return. Requires $provide_type. |
||
640 | * |
||
641 | * @return array|false |
||
642 | * @access private |
||
643 | */ |
||
644 | function getProvides($type = null, $name = null) { |
||
645 | if ($this->provides_cache === null) { |
||
646 | $active_plugins = $this->find('active'); |
||
647 | |||
648 | $provides = []; |
||
649 | |||
650 | foreach ($active_plugins as $plugin) { |
||
651 | $plugin_provides = []; |
||
652 | $manifest = $plugin->getManifest(); |
||
653 | if ($manifest instanceof \ElggPluginManifest) { |
||
654 | $plugin_provides = $plugin->getManifest()->getProvides(); |
||
655 | } |
||
656 | if ($plugin_provides) { |
||
657 | foreach ($plugin_provides as $provided) { |
||
658 | $provides[$provided['type']][$provided['name']] = [ |
||
659 | 'version' => $provided['version'], |
||
660 | 'provided_by' => $plugin->getID() |
||
661 | ]; |
||
662 | } |
||
663 | } |
||
664 | } |
||
665 | |||
666 | $this->provides_cache = $provides; |
||
667 | } |
||
668 | |||
669 | if ($type && $name) { |
||
670 | if (isset($this->provides_cache[$type][$name])) { |
||
671 | return $this->provides_cache[$type][$name]; |
||
672 | } else { |
||
673 | return false; |
||
674 | } |
||
675 | } else if ($type) { |
||
676 | if (isset($this->provides_cache[$type])) { |
||
677 | return $this->provides_cache[$type]; |
||
678 | } else { |
||
679 | return false; |
||
680 | } |
||
681 | } |
||
682 | |||
683 | return $this->provides_cache; |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * Deletes all cached data on plugins being provided. |
||
688 | * |
||
689 | * @return boolean |
||
690 | * @access private |
||
691 | */ |
||
692 | 289 | function invalidateProvidesCache() { |
|
693 | 289 | $this->provides_cache = null; |
|
694 | |||
695 | 289 | return true; |
|
696 | } |
||
697 | |||
698 | /** |
||
699 | * Delete the cache holding whether plugins are active or not |
||
700 | * |
||
701 | * @return void |
||
702 | * @access private |
||
703 | */ |
||
704 | 3 | public function invalidateIsActiveCache() { |
|
705 | 3 | $this->active_guids = []; |
|
706 | 3 | $this->active_guids_known = false; |
|
707 | 3 | } |
|
708 | |||
709 | /** |
||
710 | * Checks if a plugin is currently providing $type and $name, and optionally |
||
711 | * checking a version. |
||
712 | * |
||
713 | * @param string $type The type of the provide |
||
714 | * @param string $name The name of the provide |
||
715 | * @param string $version A version to check against |
||
716 | * @param string $comparison The comparison operator to use in version_compare() |
||
717 | * |
||
718 | * @return array An array in the form array( |
||
719 | * 'status' => bool Does the provide exist?, |
||
720 | * 'value' => string The version provided |
||
721 | * ) |
||
722 | * @access private |
||
723 | */ |
||
724 | function checkProvides($type, $name, $version = null, $comparison = 'ge') { |
||
725 | $provided = $this->getProvides($type, $name); |
||
726 | if (!$provided) { |
||
727 | return [ |
||
728 | 'status' => false, |
||
729 | 'value' => '' |
||
730 | ]; |
||
731 | } |
||
732 | |||
733 | if ($version) { |
||
734 | $status = version_compare($provided['version'], $version, $comparison); |
||
735 | } else { |
||
736 | $status = true; |
||
737 | } |
||
738 | |||
739 | return [ |
||
740 | 'status' => $status, |
||
741 | 'value' => $provided['version'] |
||
742 | ]; |
||
743 | } |
||
744 | |||
745 | /** |
||
746 | * Returns an array of parsed strings for a dependency in the |
||
747 | * format: array( |
||
748 | * 'type' => requires, conflicts, or provides. |
||
749 | * 'name' => The name of the requirement / conflict |
||
750 | * 'value' => A string representing the expected value: <1, >=3, !=enabled |
||
751 | * 'local_value' => The current value, ("Not installed") |
||
752 | * 'comment' => Free form text to help resovle the problem ("Enable / Search for plugin <link>") |
||
753 | * ) |
||
754 | * |
||
755 | * @param array $dep An \ElggPluginPackage dependency array |
||
756 | * |
||
757 | * @return array |
||
758 | * @access private |
||
759 | */ |
||
760 | function getDependencyStrings($dep) { |
||
761 | $translator = _elgg_services()->translator; |
||
762 | $dep_system = elgg_extract('type', $dep); |
||
763 | $info = elgg_extract('dep', $dep); |
||
764 | $type = elgg_extract('type', $info); |
||
765 | |||
766 | if (!$dep_system || !$info || !$type) { |
||
767 | return false; |
||
768 | } |
||
769 | |||
770 | // rewrite some of these to be more readable |
||
771 | $comparison = elgg_extract('comparison', $info); |
||
772 | switch ($comparison) { |
||
773 | case 'lt': |
||
774 | $comparison = '<'; |
||
775 | break; |
||
776 | case 'gt': |
||
777 | $comparison = '>'; |
||
778 | break; |
||
779 | case 'ge': |
||
780 | $comparison = '>='; |
||
781 | break; |
||
782 | case 'le': |
||
783 | $comparison = '<='; |
||
784 | break; |
||
785 | default: |
||
786 | //keep $comparison value intact |
||
787 | break; |
||
788 | } |
||
789 | |||
790 | /* |
||
791 | 'requires' 'plugin oauth_lib' <1.3 1.3 'downgrade' |
||
792 | 'requires' 'php setting bob' >3 3 'change it' |
||
793 | 'conflicts' 'php setting' >3 4 'change it' |
||
794 | 'conflicted''plugin profile' any 1.8 'disable profile' |
||
795 | 'provides' 'plugin oauth_lib' 1.3 -- -- |
||
796 | 'priority' 'before blog' -- after 'move it' |
||
797 | */ |
||
798 | $strings = []; |
||
799 | $strings['type'] = $translator->translate('ElggPlugin:Dependencies:' . ucwords($dep_system)); |
||
800 | |||
801 | switch ($type) { |
||
802 | case 'elgg_release': |
||
803 | // 'Elgg Version' |
||
804 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Elgg'); |
||
805 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
806 | $strings['local_value'] = $dep['value']; |
||
807 | $strings['comment'] = ''; |
||
808 | break; |
||
809 | |||
810 | case 'php_version': |
||
811 | // 'PHP version' |
||
812 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpVersion'); |
||
813 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
814 | $strings['local_value'] = $dep['value']; |
||
815 | $strings['comment'] = ''; |
||
816 | break; |
||
817 | |||
818 | case 'php_extension': |
||
819 | // PHP Extension %s [version] |
||
820 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpExtension', [$info['name']]); |
||
821 | if ($info['version']) { |
||
822 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
823 | $strings['local_value'] = $dep['value']; |
||
824 | } else { |
||
825 | $strings['expected_value'] = ''; |
||
826 | $strings['local_value'] = ''; |
||
827 | } |
||
828 | $strings['comment'] = ''; |
||
829 | break; |
||
830 | |||
831 | case 'php_ini': |
||
832 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpIni', [$info['name']]); |
||
833 | $strings['expected_value'] = "$comparison {$info['value']}"; |
||
834 | $strings['local_value'] = $dep['value']; |
||
835 | $strings['comment'] = ''; |
||
836 | break; |
||
837 | |||
838 | case 'plugin': |
||
839 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Plugin', [$info['name']]); |
||
840 | $expected = $info['version'] ? "$comparison {$info['version']}" : $translator->translate('any'); |
||
841 | $strings['expected_value'] = $expected; |
||
842 | $strings['local_value'] = $dep['value'] ? $dep['value'] : '--'; |
||
843 | $strings['comment'] = ''; |
||
844 | break; |
||
845 | |||
846 | case 'priority': |
||
847 | $expected_priority = ucwords($info['priority']); |
||
848 | $real_priority = ucwords($dep['value']); |
||
849 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Priority'); |
||
850 | $strings['expected_value'] = $translator->translate("ElggPlugin:Dependencies:Priority:$expected_priority", [$info['plugin']]); |
||
851 | $strings['local_value'] = $translator->translate("ElggPlugin:Dependencies:Priority:$real_priority", [$info['plugin']]); |
||
852 | $strings['comment'] = ''; |
||
853 | break; |
||
854 | } |
||
855 | |||
856 | if ($dep['type'] == 'suggests') { |
||
857 | if ($dep['status']) { |
||
858 | $strings['comment'] = $translator->translate('ok'); |
||
859 | } else { |
||
860 | $strings['comment'] = $translator->translate('ElggPlugin:Dependencies:Suggests:Unsatisfied'); |
||
861 | } |
||
862 | } else { |
||
863 | if ($dep['status']) { |
||
864 | $strings['comment'] = $translator->translate('ok'); |
||
865 | } else { |
||
866 | $strings['comment'] = $translator->translate('error'); |
||
867 | } |
||
868 | } |
||
869 | |||
870 | return $strings; |
||
871 | } |
||
872 | |||
873 | /** |
||
874 | * Get all settings (excluding user settings) for a plugin |
||
875 | * |
||
876 | * @param \ElggPlugin $plugin Plugin |
||
877 | * |
||
878 | * @return string[] |
||
879 | * @throws DatabaseException |
||
880 | */ |
||
881 | 39 | public function getAllSettings(ElggPlugin $plugin) { |
|
882 | 39 | if (!$plugin->guid) { |
|
883 | return []; |
||
884 | } |
||
885 | |||
886 | 39 | $values = _elgg_services()->privateSettingsCache->load($plugin->guid); |
|
887 | 39 | if (isset($values)) { |
|
888 | 21 | return $values; |
|
889 | } |
||
890 | |||
891 | 39 | $us_prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID()); |
|
892 | |||
893 | // Get private settings for user |
||
894 | 39 | $qb = Select::fromTable('private_settings'); |
|
895 | 39 | $qb->select('name') |
|
896 | 39 | ->addSelect('value') |
|
897 | 39 | ->where($qb->compare('name', 'not like', "$us_prefix%", ELGG_VALUE_STRING)) |
|
898 | 39 | ->andWhere($qb->compare('entity_guid', '=', $plugin->guid, ELGG_VALUE_GUID)); |
|
899 | |||
900 | 39 | $rows = $this->db->getData($qb); |
|
901 | |||
902 | 39 | $settings = []; |
|
903 | |||
904 | 39 | if (!empty($rows)) { |
|
905 | 18 | foreach ($rows as $row) { |
|
906 | 18 | $settings[$row->name] = $row->value; |
|
907 | } |
||
908 | } |
||
909 | |||
910 | 39 | _elgg_services()->privateSettingsCache->save($plugin->guid, $settings); |
|
911 | |||
912 | 39 | return $settings; |
|
913 | } |
||
914 | |||
915 | /** |
||
916 | * Returns an array of all plugin user settings for a user |
||
917 | * |
||
918 | * @param ElggPlugin $plugin Plugin |
||
919 | * @param ElggUser $user User |
||
920 | * |
||
921 | * @return array |
||
922 | * @see ElggPlugin::getAllUserSettings() |
||
923 | * @throws DatabaseException |
||
924 | */ |
||
925 | 1 | public function getAllUserSettings(ElggPlugin $plugin, ElggUser $user = null) { |
|
926 | |||
927 | // send an empty name so we just get the first part of the namespace |
||
928 | 1 | $prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID()); |
|
929 | |||
930 | 1 | $qb = Select::fromTable('private_settings'); |
|
931 | 1 | $qb->select('name') |
|
932 | 1 | ->addSelect('value') |
|
933 | 1 | ->where($qb->compare('name', 'like', "{$prefix}%")); |
|
934 | |||
935 | 1 | if ($user) { |
|
936 | 1 | $qb->andWhere($qb->compare('entity_guid', '=', $user->guid, ELGG_VALUE_INTEGER)); |
|
937 | } |
||
938 | |||
939 | 1 | $rows = $this->db->getData($qb); |
|
940 | |||
941 | 1 | $settings = []; |
|
942 | |||
943 | 1 | if (!empty($rows)) { |
|
944 | 1 | foreach ($rows as $rows) { |
|
945 | 1 | $name = substr($rows->name, strlen($prefix)); |
|
946 | 1 | $value = $rows->value; |
|
947 | |||
948 | 1 | $settings[$name] = $value; |
|
949 | } |
||
950 | } |
||
951 | |||
952 | 1 | return $settings; |
|
953 | } |
||
954 | |||
955 | /** |
||
956 | * Set a user specific setting for a plugin. |
||
957 | * |
||
958 | * @param string $name The name. Note: cannot be "title". |
||
959 | * @param mixed $value The value. |
||
960 | * @param int $user_guid The user GUID or 0 for the currently logged in user. |
||
961 | * @param string $plugin_id The plugin ID (Required) |
||
962 | * |
||
963 | * @return bool |
||
964 | * @see \ElggPlugin::setUserSetting() |
||
965 | */ |
||
966 | function setUserSetting($name, $value, $user_guid = 0, $plugin_id = null) { |
||
0 ignored issues
–
show
|
|||
967 | $plugin = $this->get($plugin_id); |
||
968 | if (!$plugin) { |
||
969 | return false; |
||
970 | } |
||
971 | |||
972 | return $plugin->setUserSetting($name, $value, (int) $user_guid); |
||
973 | } |
||
974 | |||
975 | /** |
||
976 | * Unsets a user-specific plugin setting |
||
977 | * |
||
978 | * @param string $name Name of the setting |
||
979 | * @param int $user_guid The user GUID or 0 for the currently logged in user. |
||
980 | * @param string $plugin_id The plugin ID (Required) |
||
981 | * |
||
982 | * @return bool |
||
983 | * @see \ElggPlugin::unsetUserSetting() |
||
984 | */ |
||
985 | function unsetUserSetting($name, $user_guid = 0, $plugin_id = null) { |
||
0 ignored issues
–
show
|
|||
986 | $plugin = $this->get($plugin_id); |
||
987 | if (!$plugin) { |
||
988 | return false; |
||
989 | } |
||
990 | |||
991 | return $plugin->unsetUserSetting($name, (int) $user_guid); |
||
992 | } |
||
993 | |||
994 | /** |
||
995 | * Get a user specific setting for a plugin. |
||
996 | * |
||
997 | * @param string $name The name of the setting. |
||
998 | * @param int $user_guid The user GUID or 0 for the currently logged in user. |
||
999 | * @param string $plugin_id The plugin ID (Required) |
||
1000 | * @param mixed $default The default value to return if none is set |
||
1001 | * |
||
1002 | * @return mixed |
||
1003 | * @see \ElggPlugin::getUserSetting() |
||
1004 | */ |
||
1005 | function getUserSetting($name, $user_guid = 0, $plugin_id = null, $default = null) { |
||
0 ignored issues
–
show
|
|||
1006 | $plugin = $this->get($plugin_id); |
||
1007 | if (!$plugin) { |
||
1008 | return false; |
||
1009 | } |
||
1010 | |||
1011 | return $plugin->getUserSetting($name, (int) $user_guid, $default); |
||
1012 | } |
||
1013 | |||
1014 | /** |
||
1015 | * Set a setting for a plugin. |
||
1016 | * |
||
1017 | * @param string $name The name of the setting - note, can't be "title". |
||
1018 | * @param mixed $value The value. |
||
1019 | * @param string $plugin_id The plugin ID (Required) |
||
1020 | * |
||
1021 | * @return bool |
||
1022 | * @see \ElggPlugin::setSetting() |
||
1023 | */ |
||
1024 | function setSetting($name, $value, $plugin_id) { |
||
0 ignored issues
–
show
|
|||
1025 | $plugin = $this->get($plugin_id); |
||
1026 | if (!$plugin) { |
||
1027 | return false; |
||
1028 | } |
||
1029 | |||
1030 | return $plugin->setSetting($name, $value); |
||
1031 | } |
||
1032 | |||
1033 | /** |
||
1034 | * Get setting for a plugin. |
||
1035 | * |
||
1036 | * @param string $name The name of the setting. |
||
1037 | * @param string $plugin_id The plugin ID (Required) |
||
1038 | * @param mixed $default The default value to return if none is set |
||
1039 | * |
||
1040 | * @return mixed |
||
1041 | * @see \ElggPlugin::getSetting() |
||
1042 | */ |
||
1043 | 31 | function getSetting($name, $plugin_id, $default = null) { |
|
0 ignored issues
–
show
|
|||
1044 | 31 | $plugin = $this->get($plugin_id); |
|
1045 | 31 | if (!$plugin) { |
|
1046 | return false; |
||
1047 | } |
||
1048 | |||
1049 | 31 | return $plugin->getSetting($name, $default); |
|
1050 | } |
||
1051 | |||
1052 | /** |
||
1053 | * Unsets a plugin setting. |
||
1054 | * |
||
1055 | * @param string $name The name of the setting. |
||
1056 | * @param string $plugin_id The plugin ID (Required) |
||
1057 | * |
||
1058 | * @return bool |
||
1059 | * @see \ElggPlugin::unsetSetting() |
||
1060 | */ |
||
1061 | function unsetSetting($name, $plugin_id) { |
||
0 ignored issues
–
show
|
|||
1062 | $plugin = $this->get($plugin_id); |
||
1063 | if (!$plugin) { |
||
1064 | return false; |
||
1065 | } |
||
1066 | |||
1067 | return $plugin->unsetSetting($name); |
||
1068 | } |
||
1069 | |||
1070 | /** |
||
1071 | * Unsets all plugin settings for a plugin. |
||
1072 | * |
||
1073 | * @param string $plugin_id The plugin ID (Required) |
||
1074 | * |
||
1075 | * @return bool |
||
1076 | * @see \ElggPlugin::unsetAllSettings() |
||
1077 | */ |
||
1078 | function unsetAllSettings($plugin_id) { |
||
0 ignored issues
–
show
|
|||
1079 | $plugin = $this->get($plugin_id); |
||
1080 | if (!$plugin) { |
||
1081 | return false; |
||
1082 | } |
||
1083 | |||
1084 | return $plugin->unsetAllSettings(); |
||
1085 | } |
||
1086 | |||
1087 | /** |
||
1088 | * Returns entities based upon plugin user settings. |
||
1089 | * Takes all the options for {@link elgg_get_entities_from_private_settings()} |
||
1090 | * in addition to the ones below. |
||
1091 | * |
||
1092 | * @param array $options Array in the format: |
||
1093 | * |
||
1094 | * plugin_id => STR The plugin id. Required. |
||
1095 | * |
||
1096 | * plugin_user_setting_names => null|ARR private setting names |
||
1097 | * |
||
1098 | * plugin_user_setting_values => null|ARR metadata values |
||
1099 | * |
||
1100 | * plugin_user_setting_name_value_pairs => null|ARR ( |
||
1101 | * name => 'name', |
||
1102 | * value => 'value', |
||
1103 | * 'operand' => '=', |
||
1104 | * ) |
||
1105 | * Currently if multiple values are sent via |
||
1106 | * an array (value => array('value1', 'value2') |
||
1107 | * the pair's operand will be forced to "IN". |
||
1108 | * |
||
1109 | * plugin_user_setting_name_value_pairs_operator =&g |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.