1 | <?php |
||
2 | |||
3 | namespace Elgg\Database; |
||
4 | |||
5 | use Closure; |
||
6 | use DatabaseException; |
||
7 | use Elgg\Application; |
||
8 | use Elgg\Cacheable; |
||
9 | use Elgg\Config; |
||
10 | use Elgg\Context; |
||
11 | use Elgg\Database; |
||
12 | use Elgg\I18n\Translator; |
||
13 | use Elgg\Includer; |
||
14 | use Elgg\EventsService; |
||
15 | use Elgg\Loggable; |
||
16 | use Elgg\Profilable; |
||
17 | use Elgg\Project\Paths; |
||
18 | use Elgg\SystemMessagesService; |
||
19 | use Elgg\ViewsService; |
||
20 | use ElggCache; |
||
21 | use ElggPlugin; |
||
22 | use ElggSession; |
||
23 | use ElggUser; |
||
24 | use Exception; |
||
25 | use Psr\Log\LogLevel; |
||
26 | use Elgg\Cache\PrivateSettingsCache; |
||
27 | |||
28 | /** |
||
29 | * Persistent, installation-wide key-value storage. |
||
30 | * |
||
31 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
32 | * |
||
33 | * @internal |
||
34 | * @since 1.10.0 |
||
35 | */ |
||
36 | class Plugins { |
||
37 | |||
38 | use Profilable; |
||
39 | use Cacheable; |
||
40 | use Loggable; |
||
41 | |||
42 | const BUNDLED_PLUGINS = [ |
||
43 | 'activity', |
||
44 | 'blog', |
||
45 | 'bookmarks', |
||
46 | 'ckeditor', |
||
47 | 'custom_index', |
||
48 | 'dashboard', |
||
49 | 'developers', |
||
50 | 'diagnostics', |
||
51 | 'discussions', |
||
52 | 'embed', |
||
53 | 'externalpages', |
||
54 | 'file', |
||
55 | 'friends', |
||
56 | 'friends_collections', |
||
57 | 'garbagecollector', |
||
58 | 'groups', |
||
59 | 'invitefriends', |
||
60 | 'likes', |
||
61 | 'login_as', |
||
62 | 'members', |
||
63 | 'messageboard', |
||
64 | 'messages', |
||
65 | 'notifications', |
||
66 | 'pages', |
||
67 | 'profile', |
||
68 | 'reportedcontent', |
||
69 | 'search', |
||
70 | 'site_notifications', |
||
71 | 'system_log', |
||
72 | 'tagcloud', |
||
73 | 'thewire', |
||
74 | 'uservalidationbyemail', |
||
75 | 'web_services', |
||
76 | ]; |
||
77 | |||
78 | /** |
||
79 | * @var ElggPlugin[] |
||
80 | */ |
||
81 | protected $boot_plugins; |
||
82 | |||
83 | /** |
||
84 | * @var array|null |
||
85 | */ |
||
86 | protected $provides_cache; |
||
87 | |||
88 | /** |
||
89 | * @var Database |
||
90 | */ |
||
91 | protected $db; |
||
92 | |||
93 | /** |
||
94 | * @var ElggSession |
||
95 | */ |
||
96 | protected $session; |
||
97 | |||
98 | /** |
||
99 | * @var EventsService |
||
100 | */ |
||
101 | protected $events; |
||
102 | |||
103 | /** |
||
104 | * @var Translator |
||
105 | */ |
||
106 | protected $translator; |
||
107 | |||
108 | /** |
||
109 | * @var ViewsService |
||
110 | */ |
||
111 | protected $views; |
||
112 | |||
113 | /** |
||
114 | * @var PrivateSettingsCache |
||
115 | */ |
||
116 | protected $private_settings_cache; |
||
117 | |||
118 | /** |
||
119 | * @var Config |
||
120 | */ |
||
121 | protected $config; |
||
122 | |||
123 | /** |
||
124 | * @var SystemMessagesService |
||
125 | */ |
||
126 | protected $system_messages; |
||
127 | |||
128 | /** |
||
129 | * @var Context |
||
130 | */ |
||
131 | protected $context; |
||
132 | |||
133 | |||
134 | /** |
||
135 | * Constructor |
||
136 | * |
||
137 | * @param ElggCache $cache Cache for referencing plugins by ID |
||
138 | * @param Database $db Database |
||
139 | * @param ElggSession $session Session |
||
140 | * @param EventsService $events Events |
||
141 | * @param Translator $translator Translator |
||
142 | * @param ViewsService $views Views service |
||
143 | * @param PrivateSettingsCache $private_settings_cache Settings cache |
||
144 | * @param Config $config Config |
||
145 | * @param SystemMessagesService $system_messages System messages |
||
146 | * @param Context $context Context |
||
147 | */ |
||
148 | public function __construct( |
||
149 | ElggCache $cache, |
||
150 | Database $db, |
||
151 | ElggSession $session, |
||
152 | EventsService $events, |
||
153 | Translator $translator, |
||
154 | ViewsService $views, |
||
155 | PrivateSettingsCache $private_settings_cache, |
||
156 | Config $config, |
||
157 | SystemMessagesService $system_messages, |
||
158 | Context $context |
||
159 | ) { |
||
160 | $this->cache = $cache; |
||
161 | $this->db = $db; |
||
162 | $this->session = $session; |
||
163 | $this->events = $events; |
||
164 | $this->translator = $translator; |
||
165 | $this->views = $views; |
||
166 | $this->private_settings_cache = $private_settings_cache; |
||
167 | $this->config = $config; |
||
168 | $this->system_messages = $system_messages; |
||
169 | $this->context = $context; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * Get the plugin path for this installation, ending with slash. |
||
174 | * |
||
175 | * @return string |
||
176 | */ |
||
177 | public function getPath() { |
||
178 | $path = $this->config->plugins_path; |
||
179 | if (!$path) { |
||
180 | $path = Paths::project() . 'mod/'; |
||
181 | } |
||
182 | return $path; |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Set the list of active plugins according to the boot data cache |
||
187 | * |
||
188 | * @param ElggPlugin[]|null $plugins Set of active plugins |
||
189 | * @param bool $order_plugins Make sure plugins are saved in the correct order (set to false if provided plugins are already sorted) |
||
190 | * |
||
191 | * @return void |
||
192 | */ |
||
193 | public function setBootPlugins($plugins, $order_plugins = true) { |
||
194 | if (!is_array($plugins)) { |
||
195 | unset($this->boot_plugins); |
||
196 | return; |
||
197 | } |
||
198 | |||
199 | // Always (re)set the boot_plugins. This makes sure that even if you have no plugins active this is known to the system. |
||
200 | $this->boot_plugins = []; |
||
201 | |||
202 | if ($order_plugins) { |
||
203 | $plugins = $this->orderPluginsByPriority($plugins); |
||
204 | } |
||
205 | |||
206 | foreach ($plugins as $plugin) { |
||
207 | if (!$plugin instanceof ElggPlugin) { |
||
208 | continue; |
||
209 | } |
||
210 | |||
211 | $plugin_id = $plugin->getID(); |
||
212 | if (!$plugin_id) { |
||
213 | continue; |
||
214 | } |
||
215 | |||
216 | $plugin->registerLanguages(); |
||
217 | |||
218 | $this->boot_plugins[$plugin_id] = $plugin; |
||
219 | $this->cache->save($plugin_id, $plugin); |
||
220 | } |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Clear plugin caches |
||
225 | * @return void |
||
226 | */ |
||
227 | public function clear() { |
||
228 | $this->cache->clear(); |
||
229 | $this->invalidateProvidesCache(); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Invalidate plugin cache |
||
234 | * |
||
235 | * @return void |
||
236 | */ |
||
237 | public function invalidate() { |
||
238 | $this->cache->invalidate(); |
||
239 | $this->invalidateProvidesCache(); |
||
240 | } |
||
241 | |||
242 | /** |
||
243 | * Returns a list of plugin directory names from a base directory. |
||
244 | * |
||
245 | * @param string $dir A dir to scan for plugins. Defaults to config's plugins_path. |
||
246 | * Must have a trailing slash. |
||
247 | * |
||
248 | * @return array Array of directory names (not full paths) |
||
249 | */ |
||
250 | public function getDirsInDir($dir = null) { |
||
251 | if (!$dir) { |
||
252 | $dir = $this->getPath(); |
||
253 | } |
||
254 | |||
255 | if (!is_dir($dir)) { |
||
256 | return []; |
||
257 | } |
||
258 | |||
259 | $handle = opendir($dir); |
||
260 | if ($handle === false) { |
||
261 | return []; |
||
262 | } |
||
263 | |||
264 | $plugin_dirs = []; |
||
265 | while (($plugin_dir = readdir($handle)) !== false) { |
||
266 | // must be directory and not begin with a . |
||
267 | if (substr($plugin_dir, 0, 1) !== '.' && is_dir($dir . $plugin_dir)) { |
||
268 | $plugin_dirs[] = $plugin_dir; |
||
269 | } |
||
270 | } |
||
271 | |||
272 | sort($plugin_dirs); |
||
273 | |||
274 | return $plugin_dirs; |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Discovers plugins in the plugins_path setting and creates \ElggPlugin |
||
279 | * entities for them if they don't exist. If there are plugins with entities |
||
280 | * but not actual files, will disable the \ElggPlugin entities and mark as inactive. |
||
281 | * The \ElggPlugin object holds config data, so don't delete. |
||
282 | * |
||
283 | * @return bool |
||
284 | * @throws DatabaseException |
||
285 | * @throws \PluginException |
||
286 | */ |
||
287 | public function generateEntities() { |
||
288 | |||
289 | $mod_dir = $this->getPath(); |
||
290 | |||
291 | // ignore access in case this is called with no admin logged in - needed for creating plugins perhaps? |
||
292 | $old_ia = $this->session->setIgnoreAccess(true); |
||
293 | |||
294 | // show hidden entities so that we can enable them if appropriate |
||
295 | $old_access = $this->session->setDisabledEntityVisibility(true); |
||
296 | |||
297 | $known_plugins = $this->find('all'); |
||
298 | if (empty($known_plugins)) { |
||
299 | $known_plugins = []; |
||
300 | } |
||
301 | |||
302 | // map paths to indexes |
||
303 | $id_map = []; |
||
304 | foreach ($known_plugins as $i => $plugin) { |
||
305 | // if the ID is wrong, delete the plugin because we can never load it. |
||
306 | $id = $plugin->getID(); |
||
307 | if (!$id) { |
||
308 | $plugin->delete(); |
||
309 | unset($known_plugins[$i]); |
||
310 | continue; |
||
311 | } |
||
312 | $id_map[$plugin->getID()] = $i; |
||
313 | $plugin->cache(); |
||
314 | } |
||
315 | |||
316 | $physical_plugins = $this->getDirsInDir($mod_dir); |
||
317 | if (empty($physical_plugins)) { |
||
318 | $this->session->setIgnoreAccess($old_ia); |
||
319 | $this->session->setDisabledEntityVisibility($old_access); |
||
320 | |||
321 | return false; |
||
322 | } |
||
323 | |||
324 | // check real plugins against known ones |
||
325 | foreach ($physical_plugins as $plugin_id) { |
||
326 | // is this already in the db? |
||
327 | if (array_key_exists($plugin_id, $id_map)) { |
||
328 | $index = $id_map[$plugin_id]; |
||
329 | $plugin = $known_plugins[$index]; |
||
330 | // was this plugin deleted and its entity disabled? |
||
331 | if (!$plugin->isEnabled()) { |
||
332 | $plugin->enable(); |
||
333 | $plugin->deactivate(); |
||
334 | $plugin->setPriority('new'); |
||
335 | } |
||
336 | |||
337 | // remove from the list of plugins to disable |
||
338 | unset($known_plugins[$index]); |
||
339 | } else { |
||
340 | // create new plugin |
||
341 | // priority is forced to last in save() if not set. |
||
342 | $plugin = ElggPlugin::fromId($plugin_id); |
||
343 | $plugin->cache(); |
||
344 | } |
||
345 | } |
||
346 | |||
347 | // everything remaining in $known_plugins needs to be disabled |
||
348 | // because they are entities, but their dirs were removed. |
||
349 | // don't delete the entities because they hold settings. |
||
350 | $reindex = false; |
||
351 | foreach ($known_plugins as $plugin) { |
||
352 | if (!$plugin->isEnabled()) { |
||
353 | continue; |
||
354 | } |
||
355 | |||
356 | $reindex = true; |
||
357 | |||
358 | if ($plugin->isActive()) { |
||
359 | $plugin->deactivate(); |
||
360 | } |
||
361 | // remove the priority. |
||
362 | $name = $this->namespacePrivateSetting('internal', 'priority'); |
||
363 | $plugin->removePrivateSetting($name); |
||
364 | |||
365 | $plugin->disable(); |
||
366 | } |
||
367 | |||
368 | if ($reindex) { |
||
369 | $this->reindexPriorities(); |
||
370 | } |
||
371 | |||
372 | $this->session->setIgnoreAccess($old_ia); |
||
373 | $this->session->setDisabledEntityVisibility($old_access); |
||
374 | |||
375 | return true; |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * Cache a reference to this plugin by its ID |
||
380 | * |
||
381 | * @param ElggPlugin $plugin the plugin to cache |
||
382 | * |
||
383 | * @return void |
||
384 | */ |
||
385 | public function cache(ElggPlugin $plugin) { |
||
386 | if (!$plugin->getID()) { |
||
387 | return; |
||
388 | } |
||
389 | $this->cache->save($plugin->getID(), $plugin); |
||
390 | } |
||
391 | |||
392 | /** |
||
393 | * Remove plugin from cache |
||
394 | * |
||
395 | * @param string $plugin_id Plugin ID |
||
396 | * |
||
397 | * @return void |
||
398 | */ |
||
399 | public function invalidateCache($plugin_id) { |
||
400 | try { |
||
401 | $this->cache->delete($plugin_id); |
||
402 | $this->invalidateProvidesCache(); |
||
403 | } catch (\InvalidArgumentException $ex) { |
||
404 | // A plugin must have been deactivated due to missing folder |
||
405 | // without proper cleanup |
||
406 | elgg_flush_caches(); |
||
1 ignored issue
–
show
|
|||
407 | } |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Returns an \ElggPlugin object with the path $path. |
||
412 | * |
||
413 | * @param string $plugin_id The id (dir name) of the plugin. NOT the guid. |
||
414 | * |
||
415 | * @return ElggPlugin|null |
||
416 | */ |
||
417 | public function get($plugin_id) { |
||
418 | if (!$plugin_id) { |
||
419 | return null; |
||
420 | } |
||
421 | |||
422 | $fallback = function () use ($plugin_id) { |
||
423 | $plugins = elgg_get_entities([ |
||
424 | 'type' => 'object', |
||
425 | 'subtype' => 'plugin', |
||
426 | 'metadata_name_value_pairs' => [ |
||
427 | 'name' => 'title', |
||
428 | 'value' => $plugin_id, |
||
429 | ], |
||
430 | 'limit' => 1, |
||
431 | 'distinct' => false, |
||
432 | ]); |
||
433 | |||
434 | if ($plugins) { |
||
435 | return $plugins[0]; |
||
436 | } |
||
437 | |||
438 | return null; |
||
439 | }; |
||
440 | |||
441 | $plugin = $this->cache->load($plugin_id); |
||
442 | if (!isset($plugin)) { |
||
443 | $plugin = $fallback(); |
||
444 | if ($plugin instanceof ElggPlugin) { |
||
445 | $plugin->cache(); |
||
446 | } |
||
447 | } |
||
448 | |||
449 | return $plugin; |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Returns if a plugin exists in the system. |
||
454 | * |
||
455 | * @warning This checks only plugins that are registered in the system! |
||
456 | * If the plugin cache is outdated, be sure to regenerate it with |
||
457 | * {@link _elgg_generate_plugin_objects()} first. |
||
458 | * |
||
459 | * @param string $id The plugin ID. |
||
460 | * |
||
461 | * @return bool |
||
462 | */ |
||
463 | public function exists($id) { |
||
464 | return $this->get($id) instanceof ElggPlugin; |
||
465 | } |
||
466 | |||
467 | /** |
||
468 | * Returns the highest priority of the plugins |
||
469 | * |
||
470 | * @return int |
||
471 | * @throws DatabaseException |
||
472 | */ |
||
473 | public function getMaxPriority() { |
||
474 | $priority = $this->namespacePrivateSetting('internal', 'priority'); |
||
475 | |||
476 | $qb = Select::fromTable('entities', 'e'); |
||
477 | $qb->select('MAX(CAST(ps.value AS unsigned)) as max') |
||
478 | ->join('e', 'private_settings', 'ps', 'e.guid = ps.entity_guid') |
||
479 | ->where($qb->compare('ps.name', '=', $priority, ELGG_VALUE_STRING)) |
||
480 | ->andWhere($qb->compare('e.type', '=', 'object', ELGG_VALUE_STRING)) |
||
481 | ->andWhere($qb->compare('e.subtype', '=', 'plugin', ELGG_VALUE_STRING)); |
||
482 | |||
483 | $data = $this->db->getDataRow($qb); |
||
484 | if (empty($data)) { |
||
485 | return 1; |
||
486 | } |
||
487 | |||
488 | return max(1, (int) $data->max); |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Returns if a plugin is active for a current site. |
||
493 | * |
||
494 | * @param string $plugin_id The plugin ID |
||
495 | * |
||
496 | * @return bool |
||
497 | */ |
||
498 | public function isActive($plugin_id) { |
||
499 | if (isset($this->boot_plugins) && is_array($this->boot_plugins)) { |
||
500 | return array_key_exists($plugin_id, $this->boot_plugins); |
||
501 | } |
||
502 | |||
503 | $plugin = $this->get($plugin_id); |
||
504 | if (!$plugin) { |
||
505 | return false; |
||
506 | } |
||
507 | |||
508 | return check_entity_relationship($plugin->guid, 'active_plugin', 1) instanceof \ElggRelationship; |
||
509 | } |
||
510 | |||
511 | /** |
||
512 | * Registers lifecycle hooks for all active plugins sorted by their priority |
||
513 | * |
||
514 | * @note This is called on every page load. If a plugin is active and problematic, it |
||
515 | * will be disabled and a visible error emitted. This does not check the deps system because |
||
516 | * that was too slow. |
||
517 | * |
||
518 | * @return bool |
||
519 | */ |
||
520 | public function build() { |
||
521 | |||
522 | $plugins_path = $this->getPath(); |
||
523 | |||
524 | // temporary disable all plugins if there is a file called 'disabled' in the plugin dir |
||
525 | if (file_exists("$plugins_path/disabled")) { |
||
526 | if ($this->session->isAdminLoggedIn() && $this->context->contains('admin')) { |
||
527 | $this->system_messages->addSuccessMessage($this->translator->translate('plugins:disabled')); |
||
528 | } |
||
529 | |||
530 | return false; |
||
531 | } |
||
532 | |||
533 | $this->events->registerHandler('plugins_load', 'system', [$this, 'register']); |
||
534 | $this->events->registerHandler('plugins_boot:before', 'system', [$this, 'boot']); |
||
535 | $this->events->registerHandler('init', 'system', [$this, 'init']); |
||
536 | $this->events->registerHandler('ready', 'system', [$this, 'ready']); |
||
537 | $this->events->registerHandler('upgrade', 'system', [$this, 'upgrade']); |
||
538 | $this->events->registerHandler('shutdown', 'system', [$this, 'shutdown']); |
||
539 | |||
540 | return true; |
||
541 | } |
||
542 | |||
543 | /** |
||
544 | * Autoload plugin classes and files |
||
545 | * Register views, translations and custom entity types |
||
546 | * |
||
547 | * @elgg_event plugins_load system |
||
548 | * @return void |
||
549 | */ |
||
550 | public function register() { |
||
551 | $plugins = $this->find('active'); |
||
552 | if (empty($plugins)) { |
||
553 | return; |
||
554 | } |
||
555 | |||
556 | if ($this->timer) { |
||
557 | $this->timer->begin([__METHOD__]); |
||
558 | } |
||
559 | |||
560 | foreach ($plugins as $plugin) { |
||
561 | try { |
||
562 | $plugin->register(); |
||
563 | } catch (Exception $ex) { |
||
564 | $this->disable($plugin, $ex); |
||
565 | } |
||
566 | } |
||
567 | |||
568 | $this->registerRoot(); |
||
569 | |||
570 | if ($this->timer) { |
||
571 | $this->timer->end([__METHOD__]); |
||
572 | } |
||
573 | } |
||
574 | |||
575 | /** |
||
576 | * Boot the plugins |
||
577 | * |
||
578 | * @elgg_event plugins_boot:before system |
||
579 | * @return void |
||
580 | */ |
||
581 | public function boot() { |
||
582 | $plugins = $this->find('active'); |
||
583 | if (empty($plugins)) { |
||
584 | return; |
||
585 | } |
||
586 | |||
587 | if ($this->timer) { |
||
588 | $this->timer->begin([__METHOD__]); |
||
589 | } |
||
590 | |||
591 | foreach ($plugins as $plugin) { |
||
592 | try { |
||
593 | $setup = $plugin->boot(); |
||
594 | if ($setup instanceof Closure) { |
||
595 | $setup(); |
||
596 | } |
||
597 | } catch (Exception $ex) { |
||
598 | $this->disable($plugin, $ex); |
||
599 | } |
||
600 | } |
||
601 | |||
602 | $this->bootRoot(); |
||
603 | |||
604 | if ($this->timer) { |
||
605 | $this->timer->end([__METHOD__]); |
||
606 | } |
||
607 | } |
||
608 | |||
609 | /** |
||
610 | * Register root level plugin views and translations |
||
611 | * @return void |
||
612 | */ |
||
613 | protected function registerRoot() { |
||
614 | if (Paths::project() === Paths::elgg()) { |
||
615 | return; |
||
616 | } |
||
617 | |||
618 | // Elgg is installed as a composer dep, so try to treat the root directory |
||
619 | // as a custom plugin that is always loaded last and can't be disabled... |
||
620 | if (!$this->config->system_cache_loaded) { |
||
621 | // configure view locations for the custom plugin (not Elgg core) |
||
622 | $viewsFile = Paths::project() . 'views.php'; |
||
623 | if (is_file($viewsFile)) { |
||
624 | $viewsSpec = Includer::includeFile($viewsFile); |
||
625 | if (is_array($viewsSpec)) { |
||
626 | $this->views->mergeViewsSpec($viewsSpec); |
||
627 | } |
||
628 | } |
||
629 | |||
630 | // find views for the custom plugin (not Elgg core) |
||
631 | $this->views->registerPluginViews(Paths::project()); |
||
632 | } |
||
633 | |||
634 | if (!$this->config->i18n_loaded_from_cache) { |
||
635 | $this->translator->registerTranslations(Paths::project() . 'languages'); |
||
636 | } |
||
637 | } |
||
638 | /** |
||
639 | * Boot root level custom plugin for starter-project installation |
||
640 | * @return void |
||
641 | */ |
||
642 | protected function bootRoot() { |
||
643 | if (Paths::project() === Paths::elgg()) { |
||
644 | return; |
||
645 | } |
||
646 | |||
647 | // This is root directory start.php |
||
648 | $root_start = Paths::project() . "start.php"; |
||
649 | if (is_file($root_start)) { |
||
650 | $setup = Application::requireSetupFileOnce($root_start); |
||
651 | if ($setup instanceof \Closure) { |
||
652 | $setup(); |
||
653 | } |
||
654 | } |
||
655 | } |
||
656 | |||
657 | /** |
||
658 | * Initialize plugins |
||
659 | * |
||
660 | * @elgg_event init system |
||
661 | * @return void |
||
662 | */ |
||
663 | public function init() { |
||
664 | $plugins = $this->find('active'); |
||
665 | if (empty($plugins)) { |
||
666 | return; |
||
667 | } |
||
668 | |||
669 | if ($this->timer) { |
||
670 | $this->timer->begin([__METHOD__]); |
||
671 | } |
||
672 | |||
673 | foreach ($plugins as $plugin) { |
||
674 | try { |
||
675 | $plugin->init(); |
||
676 | } catch (Exception $ex) { |
||
677 | $this->disable($plugin, $ex); |
||
678 | } |
||
679 | } |
||
680 | |||
681 | if ($this->timer) { |
||
682 | $this->timer->end([__METHOD__]); |
||
683 | } |
||
684 | } |
||
685 | |||
686 | /** |
||
687 | * Run plugin ready handlers |
||
688 | * |
||
689 | * @elgg_event ready system |
||
690 | * @return void |
||
691 | */ |
||
692 | public function ready() { |
||
693 | $plugins = $this->find('active'); |
||
694 | if (empty($plugins)) { |
||
695 | return; |
||
696 | } |
||
697 | |||
698 | if ($this->timer) { |
||
699 | $this->timer->begin([__METHOD__]); |
||
700 | } |
||
701 | |||
702 | foreach ($plugins as $plugin) { |
||
703 | try { |
||
704 | $plugin->getBootstrap()->ready(); |
||
705 | } catch (Exception $ex) { |
||
706 | $this->disable($plugin, $ex); |
||
707 | } |
||
708 | } |
||
709 | |||
710 | if ($this->timer) { |
||
711 | $this->timer->end([__METHOD__]); |
||
712 | } |
||
713 | } |
||
714 | |||
715 | /** |
||
716 | * Run plugin upgrade handlers |
||
717 | * |
||
718 | * @elgg_event upgrade system |
||
719 | * @return void |
||
720 | */ |
||
721 | public function upgrade() { |
||
722 | $plugins = $this->find('active'); |
||
723 | if (empty($plugins)) { |
||
724 | return; |
||
725 | } |
||
726 | |||
727 | if ($this->timer) { |
||
728 | $this->timer->begin([__METHOD__]); |
||
729 | } |
||
730 | |||
731 | foreach ($plugins as $plugin) { |
||
732 | try { |
||
733 | $plugin->getBootstrap()->upgrade(); |
||
734 | } catch (Exception $ex) { |
||
735 | $this->disable($plugin, $ex); |
||
736 | } |
||
737 | } |
||
738 | |||
739 | if ($this->timer) { |
||
740 | $this->timer->end([__METHOD__]); |
||
741 | } |
||
742 | } |
||
743 | |||
744 | /** |
||
745 | * Run plugin shutdown handlers |
||
746 | * |
||
747 | * @elgg_event shutdown system |
||
748 | * @return void |
||
749 | */ |
||
750 | public function shutdown() { |
||
751 | $plugins = $this->find('active'); |
||
752 | if (empty($plugins)) { |
||
753 | return; |
||
754 | } |
||
755 | |||
756 | if ($this->timer) { |
||
757 | $this->timer->begin([__METHOD__]); |
||
758 | } |
||
759 | |||
760 | foreach ($plugins as $plugin) { |
||
761 | try { |
||
762 | $plugin->getBootstrap()->shutdown(); |
||
763 | } catch (Exception $ex) { |
||
764 | $this->disable($plugin, $ex); |
||
765 | } |
||
766 | } |
||
767 | |||
768 | if ($this->timer) { |
||
769 | $this->timer->end([__METHOD__]); |
||
770 | } |
||
771 | } |
||
772 | |||
773 | /** |
||
774 | * Disable a plugin upon exception |
||
775 | * |
||
776 | * @param ElggPlugin $plugin Plugin entity to disable |
||
777 | * @param Exception $previous Exception thrown |
||
778 | * |
||
779 | * @return void |
||
780 | */ |
||
781 | protected function disable(ElggPlugin $plugin, Exception $previous) { |
||
782 | $this->getLogger()->log(LogLevel::ERROR, $previous, [ |
||
783 | 'context' => [ |
||
784 | 'plugin' => $plugin, |
||
785 | ], |
||
786 | ]); |
||
787 | |||
788 | $disable_plugins = $this->config->auto_disable_plugins; |
||
789 | if ($disable_plugins === null) { |
||
790 | $disable_plugins = true; |
||
791 | } |
||
792 | |||
793 | if (!$disable_plugins) { |
||
794 | return; |
||
795 | } |
||
796 | |||
797 | try { |
||
798 | $id = $plugin->getID(); |
||
799 | $plugin->deactivate(); |
||
800 | |||
801 | $msg = $this->translator->translate( |
||
802 | 'PluginException:CannotStart', |
||
803 | [$id, $plugin->guid, $previous->getMessage()] |
||
804 | ); |
||
805 | |||
806 | elgg_add_admin_notice("cannot_start $id", $msg); |
||
807 | } catch (\PluginException $ex) { |
||
808 | $this->getLogger()->log(LogLevel::ERROR, $ex, [ |
||
809 | 'context' => [ |
||
810 | 'plugin' => $plugin, |
||
811 | ], |
||
812 | ]); |
||
813 | } |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * Returns an ordered list of plugins |
||
818 | * |
||
819 | * @param string $status The status of the plugins. active, inactive, or all. |
||
820 | * |
||
821 | * @return ElggPlugin[] |
||
822 | */ |
||
823 | public function find($status = 'active') { |
||
824 | if (!$this->db || !$this->config->installed) { |
||
825 | return []; |
||
826 | } |
||
827 | |||
828 | if ($status === 'active' && isset($this->boot_plugins)) { |
||
829 | // boot_plugins is an already ordered list of plugins |
||
830 | return array_values($this->boot_plugins); |
||
831 | } |
||
832 | |||
833 | $volatile_data_name = null; |
||
834 | $site_guid = 1; |
||
835 | |||
836 | // grab plugins |
||
837 | $options = [ |
||
838 | 'type' => 'object', |
||
839 | 'subtype' => 'plugin', |
||
840 | 'limit' => false, |
||
841 | // ORDER BY CAST(ps.value) is super slow. We custom sorting below. |
||
842 | 'order_by' => false, |
||
843 | // preload private settings because private settings will probably be used, at least priority |
||
844 | 'preload_private_settings' => true, |
||
845 | ]; |
||
846 | |||
847 | switch ($status) { |
||
848 | case 'active': |
||
849 | $options['relationship'] = 'active_plugin'; |
||
850 | $options['relationship_guid'] = $site_guid; |
||
851 | $options['inverse_relationship'] = true; |
||
852 | |||
853 | // shorten callstack |
||
854 | $volatile_data_name = 'select:value'; |
||
855 | $options['select'] = ['ps.value']; |
||
856 | $options['private_setting_names'] = [ |
||
857 | $this->namespacePrivateSetting('internal', 'priority'), |
||
858 | ]; |
||
859 | break; |
||
860 | |||
861 | case 'inactive': |
||
862 | $options['wheres'][] = function (QueryBuilder $qb, $main_alias) use ($site_guid) { |
||
863 | $subquery = $qb->subquery('entity_relationships', 'active_er'); |
||
864 | $subquery->select('active_er.guid_one') |
||
865 | ->where($qb->compare('active_er.relationship', '=', 'active_plugin', ELGG_VALUE_STRING)) |
||
866 | ->andWhere($qb->compare('active_er.guid_two', '=', $site_guid, ELGG_VALUE_GUID)); |
||
867 | |||
868 | return $qb->compare("{$main_alias}.guid", 'NOT IN', $subquery->getSQL()); |
||
869 | }; |
||
870 | break; |
||
871 | |||
872 | case 'all': |
||
873 | default: |
||
874 | break; |
||
875 | } |
||
876 | |||
877 | $old_ia = $this->session->setIgnoreAccess(true); |
||
878 | $plugins = elgg_get_entities($options) ? : []; |
||
879 | $this->session->setIgnoreAccess($old_ia); |
||
880 | |||
881 | $result = $this->orderPluginsByPriority($plugins, $volatile_data_name); |
||
882 | |||
883 | if ($status === 'active' && !isset($this->boot_plugins)) { |
||
884 | // populate local cache if for some reason this is not set yet |
||
885 | $this->setBootPlugins($result, false); |
||
886 | } |
||
887 | |||
888 | return $result; |
||
889 | } |
||
890 | |||
891 | /** |
||
892 | * Sorts plugins by priority |
||
893 | * |
||
894 | * @param \ElggPlugin[] $plugins Array of plugins |
||
895 | * @param string $volatile_data_name Use an optional volatile data name to retrieve priority |
||
896 | * |
||
897 | * @return ElggPlugin[] |
||
898 | */ |
||
899 | protected function orderPluginsByPriority($plugins = [], $volatile_data_name = null) { |
||
900 | $priorities = []; |
||
901 | $sorted_plugins = []; |
||
902 | |||
903 | foreach ($plugins as $plugin) { |
||
904 | $priority = null; |
||
905 | if (!empty($volatile_data_name)) { |
||
906 | $priority = $plugin->getVolatileData($volatile_data_name); |
||
907 | } |
||
908 | |||
909 | if (!isset($priority)) { |
||
910 | $priority = $plugin->getPriority(); |
||
911 | } |
||
912 | |||
913 | $priorities[$plugin->guid] = (int) $priority; |
||
914 | $sorted_plugins[$plugin->guid] = $plugin; |
||
915 | } |
||
916 | |||
917 | asort($priorities); |
||
918 | |||
919 | return array_values(array_replace($priorities, $sorted_plugins)); |
||
920 | } |
||
921 | |||
922 | /** |
||
923 | * Reorder plugins to an order specified by the array. |
||
924 | * Plugins not included in this array will be appended to the end. |
||
925 | * |
||
926 | * @note This doesn't use the \ElggPlugin->setPriority() method because |
||
927 | * all plugins are being changed and we don't want it to automatically |
||
928 | * reorder plugins. |
||
929 | * @todo Can this be done in a single sql command? |
||
930 | * |
||
931 | * @param array $order An array of plugin ids in the order to set them |
||
932 | * |
||
933 | * @return bool |
||
934 | */ |
||
935 | public function setPriorities(array $order) { |
||
936 | $name = $this->namespacePrivateSetting('internal', 'priority'); |
||
937 | |||
938 | $plugins = $this->find('any'); |
||
939 | if (empty($plugins)) { |
||
940 | return false; |
||
941 | } |
||
942 | |||
943 | // reindex to get standard counting. no need to increment by 10. |
||
944 | // though we do start with 1 |
||
945 | $order = array_values($order); |
||
946 | |||
947 | $missing_plugins = []; |
||
948 | /* @var ElggPlugin[] $missing_plugins */ |
||
949 | |||
950 | $priority = 0; |
||
951 | foreach ($plugins as $plugin) { |
||
952 | $plugin_id = $plugin->getID(); |
||
953 | |||
954 | if (!in_array($plugin_id, $order)) { |
||
955 | $missing_plugins[] = $plugin; |
||
956 | continue; |
||
957 | } |
||
958 | |||
959 | $priority = array_search($plugin_id, $order) + 1; |
||
960 | |||
961 | if (!$plugin->setPrivateSetting($name, $priority)) { |
||
962 | return false; |
||
963 | } |
||
964 | } |
||
965 | |||
966 | // set the missing plugins' priorities |
||
967 | if (empty($missing_plugins)) { |
||
968 | return true; |
||
969 | } |
||
970 | |||
971 | foreach ($missing_plugins as $plugin) { |
||
972 | $priority++; |
||
973 | if (!$plugin->setPrivateSetting($name, $priority)) { |
||
974 | return false; |
||
975 | } |
||
976 | } |
||
977 | |||
978 | return true; |
||
979 | } |
||
980 | |||
981 | /** |
||
982 | * Reindexes all plugin priorities starting at 1. |
||
983 | * |
||
984 | * @return bool |
||
985 | */ |
||
986 | public function reindexPriorities() { |
||
987 | return $this->setPriorities([]); |
||
988 | } |
||
989 | |||
990 | /** |
||
991 | * Namespaces a string to be used as a private setting name for a plugin. |
||
992 | * |
||
993 | * For user_settings, two namespaces are added: a user setting namespace and the |
||
994 | * plugin id. |
||
995 | * |
||
996 | * For internal (plugin priority), there is a single internal namespace added. |
||
997 | * |
||
998 | * @param string $type The type of setting: user_setting or internal. |
||
999 | * @param string $name The name to namespace. |
||
1000 | * @param string $id The plugin's ID to namespace with. Required for user_setting. |
||
1001 | * |
||
1002 | * @return string |
||
1003 | */ |
||
1004 | public function namespacePrivateSetting($type, $name, $id = null) { |
||
1005 | switch ($type) { |
||
1006 | case 'user_setting': |
||
1007 | if (!$id) { |
||
1008 | throw new \InvalidArgumentException("You must pass the plugin id for user settings"); |
||
1009 | } |
||
1010 | $name = ELGG_PLUGIN_USER_SETTING_PREFIX . "$id:$name"; |
||
1011 | break; |
||
1012 | |||
1013 | case 'internal': |
||
1014 | $name = ELGG_PLUGIN_INTERNAL_PREFIX . $name; |
||
1015 | break; |
||
1016 | } |
||
1017 | |||
1018 | return $name; |
||
1019 | } |
||
1020 | |||
1021 | |||
1022 | /** |
||
1023 | * Returns an array of all provides from all active plugins. |
||
1024 | * |
||
1025 | * Array in the form array( |
||
1026 | * 'provide_type' => array( |
||
1027 | * 'provided_name' => array( |
||
1028 | * 'version' => '1.8', |
||
1029 | * 'provided_by' => 'provider_plugin_id' |
||
1030 | * ) |
||
1031 | * ) |
||
1032 | * ) |
||
1033 | * |
||
1034 | * @param string $type The type of provides to return |
||
1035 | * @param string $name A specific provided name to return. Requires $provide_type. |
||
1036 | * |
||
1037 | * @return array|false |
||
1038 | */ |
||
1039 | public function getProvides($type = null, $name = null) { |
||
1040 | if ($this->provides_cache === null) { |
||
1041 | $active_plugins = $this->find('active'); |
||
1042 | |||
1043 | $provides = []; |
||
1044 | |||
1045 | foreach ($active_plugins as $plugin) { |
||
1046 | $plugin_provides = []; |
||
1047 | $manifest = $plugin->getManifest(); |
||
1048 | if ($manifest instanceof \ElggPluginManifest) { |
||
1049 | $plugin_provides = $plugin->getManifest()->getProvides(); |
||
1050 | } |
||
1051 | if ($plugin_provides) { |
||
1052 | foreach ($plugin_provides as $provided) { |
||
1053 | $provides[$provided['type']][$provided['name']] = [ |
||
1054 | 'version' => $provided['version'], |
||
1055 | 'provided_by' => $plugin->getID() |
||
1056 | ]; |
||
1057 | } |
||
1058 | } |
||
1059 | } |
||
1060 | |||
1061 | $this->provides_cache = $provides; |
||
1062 | } |
||
1063 | |||
1064 | if ($type && $name) { |
||
1065 | if (isset($this->provides_cache[$type][$name])) { |
||
1066 | return $this->provides_cache[$type][$name]; |
||
1067 | } else { |
||
1068 | return false; |
||
1069 | } |
||
1070 | } else if ($type) { |
||
1071 | if (isset($this->provides_cache[$type])) { |
||
1072 | return $this->provides_cache[$type]; |
||
1073 | } else { |
||
1074 | return false; |
||
1075 | } |
||
1076 | } |
||
1077 | |||
1078 | return $this->provides_cache; |
||
1079 | } |
||
1080 | |||
1081 | /** |
||
1082 | * Deletes all cached data on plugins being provided. |
||
1083 | * |
||
1084 | * @return boolean |
||
1085 | */ |
||
1086 | public function invalidateProvidesCache() { |
||
1087 | $this->provides_cache = null; |
||
1088 | |||
1089 | return true; |
||
1090 | } |
||
1091 | |||
1092 | /** |
||
1093 | * Checks if a plugin is currently providing $type and $name, and optionally |
||
1094 | * checking a version. |
||
1095 | * |
||
1096 | * @param string $type The type of the provide |
||
1097 | * @param string $name The name of the provide |
||
1098 | * @param string $version A version to check against |
||
1099 | * @param string $comparison The comparison operator to use in version_compare() |
||
1100 | * |
||
1101 | * @return array An array in the form array( |
||
1102 | * 'status' => bool Does the provide exist?, |
||
1103 | * 'value' => string The version provided |
||
1104 | * ) |
||
1105 | */ |
||
1106 | public function checkProvides($type, $name, $version = null, $comparison = 'ge') { |
||
1107 | $provided = $this->getProvides($type, $name); |
||
1108 | if (!$provided) { |
||
1109 | return [ |
||
1110 | 'status' => false, |
||
1111 | 'value' => '' |
||
1112 | ]; |
||
1113 | } |
||
1114 | |||
1115 | if ($version) { |
||
1116 | $status = version_compare($provided['version'], $version, $comparison); |
||
1117 | } else { |
||
1118 | $status = true; |
||
1119 | } |
||
1120 | |||
1121 | return [ |
||
1122 | 'status' => $status, |
||
1123 | 'value' => $provided['version'] |
||
1124 | ]; |
||
1125 | } |
||
1126 | |||
1127 | /** |
||
1128 | * Returns an array of parsed strings for a dependency in the |
||
1129 | * format: array( |
||
1130 | * 'type' => requires, conflicts, or provides. |
||
1131 | * 'name' => The name of the requirement / conflict |
||
1132 | * 'value' => A string representing the expected value: <1, >=3, !=enabled |
||
1133 | * 'local_value' => The current value, ("Not installed") |
||
1134 | * 'comment' => Free form text to help resovle the problem ("Enable / Search for plugin <link>") |
||
1135 | * ) |
||
1136 | * |
||
1137 | * @param array $dep An \ElggPluginPackage dependency array |
||
1138 | * |
||
1139 | * @return false|array |
||
1140 | */ |
||
1141 | public function getDependencyStrings($dep) { |
||
1142 | $translator = $this->translator; |
||
1143 | $dep_system = elgg_extract('type', $dep); |
||
1144 | $info = elgg_extract('dep', $dep); |
||
1145 | $type = elgg_extract('type', $info); |
||
1146 | |||
1147 | if (!$dep_system || !$info || !$type) { |
||
1148 | return false; |
||
1149 | } |
||
1150 | |||
1151 | // rewrite some of these to be more readable |
||
1152 | $comparison = elgg_extract('comparison', $info); |
||
1153 | switch ($comparison) { |
||
1154 | case 'lt': |
||
1155 | $comparison = '<'; |
||
1156 | break; |
||
1157 | case 'gt': |
||
1158 | $comparison = '>'; |
||
1159 | break; |
||
1160 | case 'ge': |
||
1161 | $comparison = '>='; |
||
1162 | break; |
||
1163 | case 'le': |
||
1164 | $comparison = '<='; |
||
1165 | break; |
||
1166 | default: |
||
1167 | //keep $comparison value intact |
||
1168 | break; |
||
1169 | } |
||
1170 | |||
1171 | /* |
||
1172 | 'requires' 'plugin oauth_lib' <1.3 1.3 'downgrade' |
||
1173 | 'requires' 'php setting bob' >3 3 'change it' |
||
1174 | 'conflicts' 'php setting' >3 4 'change it' |
||
1175 | 'conflicted''plugin profile' any 1.8 'disable profile' |
||
1176 | 'provides' 'plugin oauth_lib' 1.3 -- -- |
||
1177 | 'priority' 'before blog' -- after 'move it' |
||
1178 | */ |
||
1179 | $strings = []; |
||
1180 | $strings['type'] = $translator->translate('ElggPlugin:Dependencies:' . ucwords($dep_system)); |
||
1181 | |||
1182 | switch ($type) { |
||
1183 | case 'elgg_release': |
||
1184 | // 'Elgg Version' |
||
1185 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Elgg'); |
||
1186 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
1187 | $strings['local_value'] = $dep['value']; |
||
1188 | $strings['comment'] = ''; |
||
1189 | break; |
||
1190 | |||
1191 | case 'php_version': |
||
1192 | // 'PHP version' |
||
1193 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpVersion'); |
||
1194 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
1195 | $strings['local_value'] = $dep['value']; |
||
1196 | $strings['comment'] = ''; |
||
1197 | break; |
||
1198 | |||
1199 | case 'php_extension': |
||
1200 | // PHP Extension %s [version] |
||
1201 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpExtension', [$info['name']]); |
||
1202 | if ($info['version']) { |
||
1203 | $strings['expected_value'] = "$comparison {$info['version']}"; |
||
1204 | $strings['local_value'] = $dep['value']; |
||
1205 | } else { |
||
1206 | $strings['expected_value'] = ''; |
||
1207 | $strings['local_value'] = ''; |
||
1208 | } |
||
1209 | $strings['comment'] = ''; |
||
1210 | break; |
||
1211 | |||
1212 | case 'php_ini': |
||
1213 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:PhpIni', [$info['name']]); |
||
1214 | $strings['expected_value'] = "$comparison {$info['value']}"; |
||
1215 | $strings['local_value'] = $dep['value']; |
||
1216 | $strings['comment'] = ''; |
||
1217 | break; |
||
1218 | |||
1219 | case 'plugin': |
||
1220 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Plugin', [$info['name']]); |
||
1221 | $expected = $info['version'] ? "$comparison {$info['version']}" : $translator->translate('any'); |
||
1222 | $strings['expected_value'] = $expected; |
||
1223 | $strings['local_value'] = $dep['value'] ? $dep['value'] : '--'; |
||
1224 | $strings['comment'] = ''; |
||
1225 | break; |
||
1226 | |||
1227 | case 'priority': |
||
1228 | $expected_priority = ucwords($info['priority']); |
||
1229 | $real_priority = ucwords($dep['value']); |
||
1230 | $strings['name'] = $translator->translate('ElggPlugin:Dependencies:Priority'); |
||
1231 | $strings['expected_value'] = $translator->translate("ElggPlugin:Dependencies:Priority:$expected_priority", [$info['plugin']]); |
||
1232 | $strings['local_value'] = $translator->translate("ElggPlugin:Dependencies:Priority:$real_priority", [$info['plugin']]); |
||
1233 | $strings['comment'] = ''; |
||
1234 | break; |
||
1235 | } |
||
1236 | |||
1237 | if ($dep['type'] == 'suggests') { |
||
1238 | if ($dep['status']) { |
||
1239 | $strings['comment'] = $translator->translate('ok'); |
||
1240 | } else { |
||
1241 | $strings['comment'] = $translator->translate('ElggPlugin:Dependencies:Suggests:Unsatisfied'); |
||
1242 | } |
||
1243 | } else { |
||
1244 | if ($dep['status']) { |
||
1245 | $strings['comment'] = $translator->translate('ok'); |
||
1246 | } else { |
||
1247 | $strings['comment'] = $translator->translate('error'); |
||
1248 | } |
||
1249 | } |
||
1250 | |||
1251 | return $strings; |
||
1252 | } |
||
1253 | |||
1254 | /** |
||
1255 | * Get all settings (excluding user settings) for a plugin |
||
1256 | * |
||
1257 | * @param \ElggPlugin $plugin Plugin |
||
1258 | * |
||
1259 | * @return string[] |
||
1260 | * @throws DatabaseException |
||
1261 | */ |
||
1262 | public function getAllSettings(ElggPlugin $plugin) { |
||
1263 | if (!$plugin->guid) { |
||
1264 | return []; |
||
1265 | } |
||
1266 | |||
1267 | $values = $this->private_settings_cache->load($plugin->guid); |
||
1268 | if (isset($values)) { |
||
1269 | return $values; |
||
1270 | } |
||
1271 | |||
1272 | $us_prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID()); |
||
1273 | |||
1274 | // Get private settings for user |
||
1275 | $qb = Select::fromTable('private_settings'); |
||
1276 | $qb->select('name') |
||
1277 | ->addSelect('value') |
||
1278 | ->where($qb->compare('name', 'not like', "$us_prefix%", ELGG_VALUE_STRING)) |
||
1279 | ->andWhere($qb->compare('entity_guid', '=', $plugin->guid, ELGG_VALUE_GUID)); |
||
1280 | |||
1281 | $rows = $this->db->getData($qb); |
||
1282 | |||
1283 | $settings = []; |
||
1284 | |||
1285 | if (!empty($rows)) { |
||
1286 | foreach ($rows as $row) { |
||
1287 | $settings[$row->name] = $row->value; |
||
1288 | } |
||
1289 | } |
||
1290 | |||
1291 | $this->private_settings_cache->save($plugin->guid, $settings); |
||
1292 | |||
1293 | return $settings; |
||
1294 | } |
||
1295 | |||
1296 | /** |
||
1297 | * Returns an array of all plugin user settings for a user |
||
1298 | * |
||
1299 | * @param ElggPlugin $plugin Plugin |
||
1300 | * @param ElggUser $user User |
||
1301 | * |
||
1302 | * @return array |
||
1303 | * @see ElggPlugin::getAllUserSettings() |
||
1304 | * @throws DatabaseException |
||
1305 | */ |
||
1306 | public function getAllUserSettings(ElggPlugin $plugin, ElggUser $user = null) { |
||
1307 | |||
1308 | // send an empty name so we just get the first part of the namespace |
||
1309 | $prefix = $this->namespacePrivateSetting('user_setting', '', $plugin->getID()); |
||
1310 | |||
1311 | $qb = Select::fromTable('private_settings'); |
||
1312 | $qb->select('name') |
||
1313 | ->addSelect('value') |
||
1314 | ->where($qb->compare('name', 'like', "{$prefix}%")); |
||
1315 | |||
1316 | if ($user) { |
||
1317 | $qb->andWhere($qb->compare('entity_guid', '=', $user->guid, ELGG_VALUE_INTEGER)); |
||
1318 | } |
||
1319 | |||
1320 | $rows = $this->db->getData($qb); |
||
1321 | |||
1322 | $settings = []; |
||
1323 | |||
1324 | if (!empty($rows)) { |
||
1325 | foreach ($rows as $rows) { |
||
1326 | $name = substr($rows->name, strlen($prefix)); |
||
1327 | $value = $rows->value; |
||
1328 | |||
1329 | $settings[$name] = $value; |
||
1330 | } |
||
1331 | } |
||
1332 | |||
1333 | return $settings; |
||
1334 | } |
||
1335 | |||
1336 | /** |
||
1337 | * Returns entities based upon plugin user settings. |
||
1338 | * Takes all the options for {@link elgg_get_entities_from_private_settings()} |
||
1339 | * in addition to the ones below. |
||
1340 | * |
||
1341 | * @param array $options Array in the format: |
||
1342 | * |
||
1343 | * plugin_id => STR The plugin id. Required. |
||
1344 | * |
||
1345 | * plugin_user_setting_names => null|ARR private setting names |
||
1346 | * |
||
1347 | * plugin_user_setting_values => null|ARR metadata values |
||
1348 | * |
||
1349 | * plugin_user_setting_name_value_pairs => null|ARR ( |
||
1350 | * name => 'name', |
||
1351 | * value => 'value', |
||
1352 | * 'operand' => '=', |
||
1353 | * ) |
||
1354 | * Currently if multiple values are sent via |
||
1355 | * an array (value => array('value1', 'value2') |
||
1356 | * the pair's operand will be forced to "IN". |
||
1357 | * |
||
1358 | * plugin_user_setting_name_value_pairs_operator => null|STR The operator to use for combining |
||
1359 | * (name = value) OPERATOR (name = value); default AND |
||
1360 | * |
||
1361 | * @return mixed int If count, int. If not count, array. false on errors. |
||
1362 | */ |
||
1363 | public function getEntitiesFromUserSettings(array $options = []) { |
||
1364 | $singulars = [ |
||
1365 | 'plugin_user_setting_name', |
||
1366 | 'plugin_user_setting_value', |
||
1367 | 'plugin_user_setting_name_value_pair' |
||
1368 | ]; |
||
1369 | |||
1370 | $options = LegacyQueryOptionsAdapter::normalizePluralOptions($options, $singulars); |
||
1371 | |||
1372 | // rewrite plugin_user_setting_name_* to the right PS ones. |
||
1373 | $map = [ |
||
1374 | 'plugin_user_setting_names' => 'private_setting_names', |
||
1375 | 'plugin_user_setting_values' => 'private_setting_values', |
||
1376 | 'plugin_user_setting_name_value_pairs' => 'private_setting_name_value_pairs', |
||
1377 | 'plugin_user_setting_name_value_pairs_operator' => 'private_setting_name_value_pairs_operator', |
||
1378 | ]; |
||
1379 | |||
1380 | foreach ($map as $plugin => $private) { |
||
1381 | if (!isset($options[$plugin])) { |
||
1382 | continue; |
||
1383 | } |
||
1384 | |||
1385 | if (isset($options[$private])) { |
||
1386 | if (!is_array($options[$private])) { |
||
1387 | $options[$private] = [$options[$private]]; |
||
1388 | } |
||
1389 | |||
1390 | $options[$private] = array_merge($options[$private], $options[$plugin]); |
||
1391 | } else { |
||
1392 | $options[$private] = $options[$plugin]; |
||
1393 | } |
||
1394 | } |
||
1395 | |||
1396 | $prefix = $this->namespacePrivateSetting('user_setting', '', $options['plugin_id']); |
||
1397 | $options['private_setting_name_prefix'] = $prefix; |
||
1398 | |||
1399 | return elgg_get_entities($options); |
||
1400 | } |
||
1401 | |||
1402 | /** |
||
1403 | * Set plugin priority and adjust the priorities of other plugins |
||
1404 | * |
||
1405 | * @param ElggPlugin $plugin Plugin |
||
1406 | * @param int $priority New priority |
||
1407 | * |
||
1408 | * @return int|false |
||
1409 | * @throws DatabaseException |
||
1410 | */ |
||
1411 | public function setPriority(ElggPlugin $plugin, $priority) { |
||
1412 | |||
1413 | $old_priority = $plugin->getPriority() ? : 1; |
||
1414 | |||
1415 | $name = $this->namespacePrivateSetting('internal', 'priority'); |
||
1416 | |||
1417 | if (!$plugin->setPrivateSetting($name, $priority)) { |
||
1418 | return false; |
||
1419 | } |
||
1420 | |||
1421 | if (!$plugin->guid) { |
||
1422 | return false; |
||
1423 | } |
||
1424 | |||
1425 | $qb = Update::table('private_settings'); |
||
1426 | $qb->where($qb->compare('name', '=', $name, ELGG_VALUE_STRING)) |
||
1427 | ->andWhere($qb->compare('entity_guid', '!=', $plugin->guid, ELGG_VALUE_INTEGER)); |
||
1428 | |||
1429 | if ($priority > $old_priority) { |
||
1430 | $qb->set('value', "CAST(value AS UNSIGNED) - 1"); |
||
1431 | $qb->andWhere($qb->between('CAST(value AS UNSIGNED)', $old_priority, $priority, ELGG_VALUE_INTEGER)); |
||
1432 | } else { |
||
1433 | $qb->set('value', "CAST(value AS UNSIGNED) + 1"); |
||
1434 | $qb->andWhere($qb->between('CAST(value AS UNSIGNED)', $priority, $old_priority, ELGG_VALUE_INTEGER)); |
||
1435 | } |
||
1436 | |||
1437 | if (!$this->db->updateData($qb)) { |
||
1438 | return false; |
||
1439 | } |
||
1440 | |||
1441 | return $priority; |
||
1442 | } |
||
1443 | } |
||
1444 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.