Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Cache display administration helper.
19
 *
20
 * This file is part of Moodle's cache API, affectionately called MUC.
21
 * It contains the components that are requried in order to use caching.
22
 *
1441 ariadna 23
 * @package    core_cache
1 efrain 24
 * @category   cache
25
 * @author     Peter Burnett <peterburnett@catalyst-au.net>
26
 * @copyright  2020 Catalyst IT
27
 * @copyright  2012 Sam Hemelryk
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 */
30
 
31
namespace core_cache\local;
32
 
1441 ariadna 33
use core_cache\cache;
34
use core_cache\config;
35
use core_cache\config_writer;
36
use core_cache\configurable_cache_interface;
37
use core_cache\exception\cache_exception;
38
use core_cache\factory;
39
use core_cache\form\cache_lock_form;
40
use core_cache\form\cache_mode_mappings_form;
41
use core_cache\form\cache_definition_sharing_form;
42
use core_cache\form\cache_definition_mappings_form;
43
use core_cache\form\cachestore_addinstance_form;
44
use core_cache\helper as cache_helper;
45
use core_cache\lockable_cache_interface;
46
use core_cache\store;
47
use core_component;
48
use core\context;
49
use core\context\system as context_system;
50
use core\exception\coding_exception;
51
use core\exception\moodle_exception;
1 efrain 52
use core\output\notification;
1441 ariadna 53
use core\output\single_button;
54
use core\url;
55
use html_writer;
56
use stdClass;
1 efrain 57
 
58
/**
59
 * A cache helper for administration tasks
60
 *
61
 * @package    core
62
 * @category   cache
63
 * @copyright  2020 Peter Burnett <peterburnett@catalyst-au.net>
64
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
65
 */
66
class administration_display_helper extends \core_cache\administration_helper {
67
    /**
1441 ariadna 68
     * Please do not call constructor directly. Use factory::get_administration_display_helper() instead.
1 efrain 69
     */
70
    public function __construct() {
71
        // Nothing to do here.
72
    }
73
 
74
    /**
75
     * Returns all of the actions that can be performed on a definition.
76
     *
77
     * @param context $context the system context.
78
     * @param array $definitionsummary information about this cache, from the array returned by
79
     *      core_cache\administration_helper::get_definition_summaries(). Currently only 'sharingoptions'
80
     *      element is used.
81
     * @return array of actions. Each action is an action_url.
82
     */
1441 ariadna 83
    public function get_definition_actions(context $context, array $definitionsummary): array {
1 efrain 84
        global $OUTPUT;
85
        if (has_capability('moodle/site:config', $context)) {
1441 ariadna 86
            $actions = [];
1 efrain 87
            // Edit mappings.
88
            $actions[] = $OUTPUT->action_link(
1441 ariadna 89
                new url('/cache/admin.php', ['action' => 'editdefinitionmapping',
90
                    'definition' => $definitionsummary['id'], ]),
1 efrain 91
                get_string('editmappings', 'cache')
92
            );
93
            // Edit sharing.
94
            if (count($definitionsummary['sharingoptions']) > 1) {
95
                $actions[] = $OUTPUT->action_link(
1441 ariadna 96
                    new url('/cache/admin.php', ['action' => 'editdefinitionsharing',
97
                        'definition' => $definitionsummary['id'], ]),
1 efrain 98
                    get_string('editsharing', 'cache')
99
                );
100
            }
101
            // Purge.
102
            $actions[] = $OUTPUT->action_link(
1441 ariadna 103
                new url('/cache/admin.php', ['action' => 'purgedefinition',
104
                    'definition' => $definitionsummary['id'], 'sesskey' => sesskey(), ]),
1 efrain 105
                get_string('purge', 'cache')
106
            );
107
            return $actions;
108
        }
1441 ariadna 109
        return [];
1 efrain 110
    }
111
 
112
    /**
113
     * Returns all of the actions that can be performed on a store.
114
     *
115
     * @param string $name The name of the store
116
     * @param array $storedetails information about this store, from the array returned by
117
     *      core_cache\administration_helper::get_store_instance_summaries().
118
     * @return array of actions. Each action is an action_url.
119
     */
120
    public function get_store_instance_actions(string $name, array $storedetails): array {
121
        global $OUTPUT;
1441 ariadna 122
        $actions = [];
123
        if (has_capability('moodle/site:config', context_system::instance())) {
124
            $baseurl = new url('/cache/admin.php', ['store' => $name]);
1 efrain 125
            if (empty($storedetails['default'])) {
126
                // Edit store.
127
                $actions[] = $OUTPUT->action_link(
1441 ariadna 128
                    new url($baseurl, ['action' => 'editstore', 'plugin' => $storedetails['plugin']]),
1 efrain 129
                    get_string('editstore', 'cache')
130
                );
131
                // Delete store.
132
                $actions[] = $OUTPUT->action_link(
1441 ariadna 133
                    new url($baseurl, ['action' => 'deletestore']),
1 efrain 134
                    get_string('deletestore', 'cache')
135
                );
136
            }
137
            // Purge store.
138
            $actions[] = $OUTPUT->action_link(
1441 ariadna 139
                new url($baseurl, ['action' => 'purgestore', 'sesskey' => sesskey()]),
1 efrain 140
                get_string('purge', 'cache')
141
            );
142
        }
143
        return $actions;
144
    }
145
 
146
    /**
147
     * Returns all of the actions that can be performed on a plugin.
148
     *
149
     * @param string $name The name of the plugin
150
     * @param array $plugindetails information about this store, from the array returned by
151
     *      core_cache\administration_helper::get_store_plugin_summaries().
152
     * @return array of actions. Each action is an action_url.
153
     */
154
    public function get_store_plugin_actions(string $name, array $plugindetails): array {
155
        global $OUTPUT;
1441 ariadna 156
        $actions = [];
157
        if (has_capability('moodle/site:config', context_system::instance())) {
1 efrain 158
            if (!empty($plugindetails['canaddinstance'])) {
1441 ariadna 159
                $url = new url(
160
                    '/cache/admin.php',
161
                    ['action' => 'addstore', 'plugin' => $name]
162
                );
1 efrain 163
                $actions[] = $OUTPUT->action_link(
164
                    $url,
165
                    get_string('addinstance', 'cache')
166
                );
167
            }
168
        }
169
        return $actions;
170
    }
171
 
172
    /**
173
     * Returns a form that can be used to add a store instance.
174
     *
175
     * @param string $plugin The plugin to add an instance of
176
     * @return cachestore_addinstance_form
177
     * @throws coding_exception
178
     */
1441 ariadna 179
    public function get_add_store_form(string $plugin): cachestore_addinstance_form {
1 efrain 180
        global $CFG; // Needed for includes.
1441 ariadna 181
        $plugins = core_component::get_plugin_list('cachestore');
1 efrain 182
        if (!array_key_exists($plugin, $plugins)) {
1441 ariadna 183
            throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
1 efrain 184
        }
185
        $plugindir = $plugins[$plugin];
186
        $class = 'cachestore_addinstance_form';
1441 ariadna 187
        if (file_exists($plugindir . '/addinstanceform.php')) {
188
            require_once($plugindir . '/addinstanceform.php');
189
            if (class_exists('cachestore_' . $plugin . '_addinstance_form')) {
190
                $class = 'cachestore_' . $plugin . '_addinstance_form';
191
                if (!array_key_exists(cachestore_addinstance_form::class, class_parents($class))) {
192
                    throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
1 efrain 193
                }
194
            }
195
        }
196
 
197
        $locks = $this->get_possible_locks_for_stores($plugindir, $plugin);
198
 
1441 ariadna 199
        $url = new url('/cache/admin.php', ['action' => 'addstore']);
200
        return new $class($url, ['plugin' => $plugin, 'store' => null, 'locks' => $locks]);
1 efrain 201
    }
202
 
203
    /**
204
     * Returns a form that can be used to edit a store instance.
205
     *
206
     * @param string $plugin
207
     * @param string $store
208
     * @return cachestore_addinstance_form
209
     * @throws coding_exception
210
     */
1441 ariadna 211
    public function get_edit_store_form(string $plugin, string $store): cachestore_addinstance_form {
1 efrain 212
        global $CFG; // Needed for includes.
1441 ariadna 213
        $plugins = core_component::get_plugin_list('cachestore');
1 efrain 214
        if (!array_key_exists($plugin, $plugins)) {
1441 ariadna 215
            throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
1 efrain 216
        }
1441 ariadna 217
        $factory = factory::instance();
1 efrain 218
        $config = $factory->create_config_instance();
219
        $stores = $config->get_all_stores();
220
        if (!array_key_exists($store, $stores)) {
1441 ariadna 221
            throw new coding_exception('Invalid store name given when trying to create an edit form.');
1 efrain 222
        }
223
        $plugindir = $plugins[$plugin];
224
        $class = 'cachestore_addinstance_form';
1441 ariadna 225
        if (file_exists($plugindir . '/addinstanceform.php')) {
226
            require_once($plugindir . '/addinstanceform.php');
227
            if (class_exists('cachestore_' . $plugin . '_addinstance_form')) {
228
                $class = 'cachestore_' . $plugin . '_addinstance_form';
229
                if (!array_key_exists(cachestore_addinstance_form::class, class_parents($class))) {
230
                    throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
1 efrain 231
                }
232
            }
233
        }
234
 
235
        $locks = $this->get_possible_locks_for_stores($plugindir, $plugin);
236
 
1441 ariadna 237
        $url = new url('/cache/admin.php', ['action' => 'editstore', 'plugin' => $plugin, 'store' => $store]);
238
        $editform = new $class($url, ['plugin' => $plugin, 'store' => $store, 'locks' => $locks]);
1 efrain 239
        if (isset($stores[$store]['lock'])) {
1441 ariadna 240
            $editform->set_data(['lock' => $stores[$store]['lock']]);
1 efrain 241
        }
242
        // See if the cachestore is going to want to load data for the form.
243
        // If it has a customised add instance form then it is going to want to.
1441 ariadna 244
        $storeclass = 'cachestore_' . $plugin;
1 efrain 245
        $storedata = $stores[$store];
1441 ariadna 246
        if (
247
            array_key_exists('configuration', $storedata) &&
248
            array_key_exists(configurable_cache_interface::class, class_implements($storeclass))
249
        ) {
1 efrain 250
            $storeclass::config_set_edit_form_data($editform, $storedata['configuration']);
251
        }
252
        return $editform;
253
    }
254
 
255
    /**
256
     * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself.
257
     *
258
     * @param string $plugindir
259
     * @param string $plugin
260
     * @return array|false
261
     */
262
    protected function get_possible_locks_for_stores(string $plugindir, string $plugin) {
263
        global $CFG; // Needed for includes.
264
        $supportsnativelocking = false;
1441 ariadna 265
        if (file_exists($plugindir . '/lib.php')) {
266
            require_once($plugindir . '/lib.php');
267
            $pluginclass = 'cachestore_' . $plugin;
1 efrain 268
            if (class_exists($pluginclass)) {
1441 ariadna 269
                $supportsnativelocking = array_key_exists(lockable_cache_interface::class, class_implements($pluginclass));
1 efrain 270
            }
271
        }
272
 
273
        if (!$supportsnativelocking) {
1441 ariadna 274
            $config = config::instance();
275
            $locks = [];
1 efrain 276
            foreach ($config->get_locks() as $lock => $conf) {
277
                if (!empty($conf['default'])) {
278
                    $name = get_string($lock, 'cache');
279
                } else {
280
                    $name = $lock;
281
                }
282
                $locks[$lock] = $name;
283
            }
284
        } else {
285
            $locks = false;
286
        }
287
 
288
        return $locks;
289
    }
290
 
291
    /**
292
     * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
293
     * store in configuration.
294
     *
295
     * @param stdClass $data The mform data.
296
     * @return array
297
     * @throws coding_exception
298
     */
1441 ariadna 299
    public function get_store_configuration_from_data(stdClass $data): array {
1 efrain 300
        global $CFG;
1441 ariadna 301
        $file = $CFG->dirroot . '/cache/stores/' . $data->plugin . '/lib.php';
1 efrain 302
        if (!file_exists($file)) {
1441 ariadna 303
            throw new coding_exception('Invalid cache plugin provided. ' . $file);
1 efrain 304
        }
305
        require_once($file);
1441 ariadna 306
        $class = 'cachestore_' . $data->plugin;
1 efrain 307
        if (!class_exists($class)) {
1441 ariadna 308
            throw new coding_exception('Invalid cache plugin provided.');
1 efrain 309
        }
1441 ariadna 310
        if (array_key_exists(configurable_cache_interface::class, class_implements($class))) {
1 efrain 311
            return $class::config_get_configuration_array($data);
312
        }
1441 ariadna 313
        return [];
1 efrain 314
    }
315
 
316
    /**
317
     * Returns an array of lock plugins for which we can add an instance.
318
     *
319
     * Suitable for use within an mform select element.
320
     *
321
     * @return array
322
     */
323
    public function get_addable_lock_options(): array {
1441 ariadna 324
        $plugins = core_component::get_plugin_list_with_class('cachelock', '', 'lib.php');
325
        $options = [];
1 efrain 326
        $len = strlen('cachelock_');
327
        foreach ($plugins as $plugin => $class) {
328
            $method = "$class::can_add_instance";
329
            if (is_callable($method) && !call_user_func($method)) {
330
                // Can't add an instance of this plugin.
331
                continue;
332
            }
333
            $options[substr($plugin, $len)] = get_string('pluginname', $plugin);
334
        }
335
        return $options;
336
    }
337
 
338
    /**
339
     * Gets the form to use when adding a lock instance.
340
     *
341
     * @param string $plugin
1441 ariadna 342
     * @param array|null $lockplugin
1 efrain 343
     * @return cache_lock_form
344
     * @throws coding_exception
345
     */
1441 ariadna 346
    public function get_add_lock_form(string $plugin, ?array $lockplugin = null): cache_lock_form {
1 efrain 347
        global $CFG; // Needed for includes.
1441 ariadna 348
        $plugins = core_component::get_plugin_list('cachelock');
1 efrain 349
        if (!array_key_exists($plugin, $plugins)) {
1441 ariadna 350
            throw new coding_exception('Invalid cache lock plugin requested when trying to create a form.');
1 efrain 351
        }
352
        $plugindir = $plugins[$plugin];
1441 ariadna 353
        $class = cache_lock_form::class;
354
        $hasaddinstanceform = file_exists($plugindir . '/addinstanceform.php');
355
        $hasaddinstanceform = $hasaddinstanceform && in_array(configurable_cache_interface::class, class_implements($class));
356
        if ($hasaddinstanceform) {
357
            require_once($plugindir . '/addinstanceform.php');
358
            if (class_exists('cachelock_' . $plugin . '_addinstance_form')) {
359
                $class = 'cachelock_' . $plugin . '_addinstance_form';
360
                if (!is_a($class, cache_lock_form::class, true)) {
361
                    throw new coding_exception('Cache lock plugin add instance forms must extend cache_lock_form');
1 efrain 362
                }
363
            }
364
        }
1441 ariadna 365
        return new $class(null, ['lock' => $plugin]);
1 efrain 366
    }
367
 
368
    /**
369
     * Gets configuration data from a new lock instance form.
370
     *
371
     * @param string $plugin
372
     * @param stdClass $data
373
     * @return array
374
     * @throws coding_exception
375
     */
1441 ariadna 376
    public function get_lock_configuration_from_data(string $plugin, stdClass $data): array {
1 efrain 377
        global $CFG;
1441 ariadna 378
        $file = $CFG->dirroot . '/cache/locks/' . $plugin . '/lib.php';
1 efrain 379
        if (!file_exists($file)) {
1441 ariadna 380
            throw new coding_exception('Invalid cache plugin provided. ' . $file);
1 efrain 381
        }
382
        require_once($file);
1441 ariadna 383
        $class = 'cachelock_' . $plugin;
1 efrain 384
        if (!class_exists($class)) {
1441 ariadna 385
            throw new coding_exception('Invalid cache plugin provided.');
1 efrain 386
        }
1441 ariadna 387
        if (array_key_exists(configurable_cache_interface::class, class_implements($class))) {
1 efrain 388
            return $class::config_get_configuration_array($data);
389
        }
1441 ariadna 390
        return [];
1 efrain 391
    }
392
 
393
    /**
394
     * Handles the page actions, based on the parameter.
395
     *
396
     * @param string $action the action to handle.
397
     * @param array $forminfo an empty array to be overridden and set.
398
     * @return array the empty or overridden forminfo array.
399
     */
400
    public function perform_cache_actions(string $action, array $forminfo): array {
401
        switch ($action) {
1441 ariadna 402
            case 'rescandefinitions': // Rescan definitions.
1 efrain 403
                $this->action_rescan_definition();
404
                break;
405
 
1441 ariadna 406
            case 'addstore': // Add the requested store.
1 efrain 407
                $forminfo = $this->action_addstore();
408
                break;
409
 
1441 ariadna 410
            case 'editstore': // Edit the requested store.
1 efrain 411
                $forminfo = $this->action_editstore();
412
                break;
413
 
1441 ariadna 414
            case 'deletestore': // Delete a given store.
1 efrain 415
                $this->action_deletestore($action);
416
                break;
417
 
1441 ariadna 418
            case 'editdefinitionmapping': // Edit definition mappings.
1 efrain 419
                $forminfo = $this->action_editdefinitionmapping();
420
                break;
421
 
1441 ariadna 422
            case 'editdefinitionsharing': // Edit definition sharing.
1 efrain 423
                $forminfo = $this->action_editdefinitionsharing();
424
                break;
425
 
426
            case 'editmodemappings': // Edit default mode mappings.
427
                $forminfo = $this->action_editmodemappings();
428
                break;
429
 
430
            case 'purgedefinition': // Purge a specific definition.
431
                $this->action_purgedefinition();
432
                break;
433
 
434
            case 'purgestore':
435
            case 'purge': // Purge a store cache.
436
                $this->action_purge();
437
                break;
438
 
439
            case 'newlockinstance':
440
                $forminfo = $this->action_newlockinstance();
441
                break;
442
 
443
            case 'deletelock':
444
                // Deletes a lock instance.
445
                $this->action_deletelock($action);
446
                break;
447
        }
448
 
449
        return $forminfo;
450
    }
451
 
452
    /**
453
     * Performs the rescan definition action.
454
     *
455
     * @return void
456
     */
457
    public function action_rescan_definition() {
458
        global $PAGE;
459
 
460
        require_sesskey();
1441 ariadna 461
        config_writer::update_definitions();
1 efrain 462
        redirect($PAGE->url);
463
    }
464
 
465
    /**
466
     * Performs the add store action.
467
     *
468
     * @return array an array of the form to display to the user, and the page title.
469
     */
470
    public function action_addstore(): array {
471
        global $PAGE;
472
        $storepluginsummaries = $this->get_store_plugin_summaries();
473
 
474
        $plugin = required_param('plugin', PARAM_PLUGIN);
475
        if (!$storepluginsummaries[$plugin]['canaddinstance']) {
1441 ariadna 476
            throw new moodle_exception('ex_unmetstorerequirements', 'cache');
1 efrain 477
        }
478
        $mform = $this->get_add_store_form($plugin);
479
        $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']);
480
        if ($mform->is_cancelled()) {
481
            redirect($PAGE->url);
482
        } else if ($data = $mform->get_data()) {
483
            $config = $this->get_store_configuration_from_data($data);
1441 ariadna 484
            $writer = config_writer::instance();
1 efrain 485
            unset($config['lock']);
486
            foreach ($writer->get_locks() as $lock => $lockconfig) {
487
                if ($lock == $data->lock) {
488
                    $config['lock'] = $data->lock;
489
                }
490
            }
491
            $writer->add_store_instance($data->name, $data->plugin, $config);
492
            redirect($PAGE->url, get_string('addstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5);
493
        }
494
 
495
        $PAGE->navbar->add(get_string('addstore', 'cache', 'cache'), $PAGE->url);
1441 ariadna 496
        return ['form' => $mform, 'title' => $title];
1 efrain 497
    }
498
 
499
    /**
500
     * Performs the edit store action.
501
     *
502
     * @return array an array of the form to display, and the page title.
503
     */
504
    public function action_editstore(): array {
505
        global $PAGE;
506
        $storepluginsummaries = $this->get_store_plugin_summaries();
507
 
508
        $plugin = required_param('plugin', PARAM_PLUGIN);
509
        $store = required_param('store', PARAM_TEXT);
510
        $mform = $this->get_edit_store_form($plugin, $store);
511
        $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']);
512
        if ($mform->is_cancelled()) {
513
            redirect($PAGE->url);
514
        } else if ($data = $mform->get_data()) {
515
            $config = $this->get_store_configuration_from_data($data);
1441 ariadna 516
            $writer = config_writer::instance();
1 efrain 517
 
518
            unset($config['lock']);
519
            foreach ($writer->get_locks() as $lock => $lockconfig) {
520
                if ($lock == $data->lock) {
521
                    $config['lock'] = $data->lock;
522
                }
523
            }
524
            $writer->edit_store_instance($data->name, $data->plugin, $config);
525
            redirect($PAGE->url, get_string('editstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5);
526
        }
527
 
1441 ariadna 528
        return ['form' => $mform, 'title' => $title];
1 efrain 529
    }
530
 
531
    /**
532
     * Performs the deletestore action.
533
     *
534
     * @param string $action the action calling to this function.
535
     */
536
    public function action_deletestore(string $action): void {
537
        global $OUTPUT, $PAGE, $SITE;
538
        $notifysuccess = true;
539
        $storeinstancesummaries = $this->get_store_instance_summaries();
540
 
541
        $store = required_param('store', PARAM_TEXT);
542
        $confirm = optional_param('confirm', false, PARAM_BOOL);
543
 
544
        if (!array_key_exists($store, $storeinstancesummaries)) {
545
            $notifysuccess = false;
546
            $notification = get_string('invalidstore', 'cache');
547
        } else if ($storeinstancesummaries[$store]['mappings'] > 0) {
548
            $notifysuccess = false;
549
            $notification = get_string('deletestorehasmappings', 'cache');
550
        }
551
 
552
        if ($notifysuccess) {
553
            if (!$confirm) {
554
                $title = get_string('confirmstoredeletion', 'cache');
1441 ariadna 555
                $params = ['store' => $store, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()];
556
                $url = new url($PAGE->url, $params);
557
                $button = new single_button($url, get_string('deletestore', 'cache'));
1 efrain 558
 
559
                $PAGE->set_title($title);
560
                $PAGE->set_heading($SITE->fullname);
561
                echo $OUTPUT->header();
562
                echo $OUTPUT->heading($title);
563
                $confirmation = get_string('deletestoreconfirmation', 'cache', $storeinstancesummaries[$store]['name']);
564
                echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
565
                echo $OUTPUT->footer();
566
                exit;
567
            } else {
568
                require_sesskey();
1441 ariadna 569
                $writer = config_writer::instance();
1 efrain 570
                $writer->delete_store_instance($store);
571
                redirect($PAGE->url, get_string('deletestoresuccess', 'cache'), 5);
572
            }
573
        } else {
574
            redirect($PAGE->url, $notification, null, notification::NOTIFY_ERROR);
575
        }
576
    }
577
 
578
    /**
579
     * Performs the edit definition mapping action.
580
     *
581
     * @return array an array of the form to display, and the page title.
582
     * @throws cache_exception
583
     */
584
    public function action_editdefinitionmapping(): array {
585
        global $PAGE;
586
        $definitionsummaries = $this->get_definition_summaries();
587
 
588
        $definition = required_param('definition', PARAM_SAFEPATH);
589
        if (!array_key_exists($definition, $definitionsummaries)) {
1441 ariadna 590
            throw new cache_exception('Invalid cache definition requested');
1 efrain 591
        }
592
        $title = get_string('editdefinitionmappings', 'cache', $definition);
1441 ariadna 593
        $mform = new cache_definition_mappings_form($PAGE->url, ['definition' => $definition]);
1 efrain 594
        if ($mform->is_cancelled()) {
595
            redirect($PAGE->url);
596
        } else if ($data = $mform->get_data()) {
1441 ariadna 597
            $writer = config_writer::instance();
598
            $mappings = [];
1 efrain 599
            foreach ($data->mappings as $mapping) {
600
                if (!empty($mapping)) {
601
                    $mappings[] = $mapping;
602
                }
603
            }
604
            $writer->set_definition_mappings($definition, $mappings);
605
            redirect($PAGE->url);
606
        }
607
 
608
        $PAGE->navbar->add(get_string('updatedefinitionmapping', 'cache'), $PAGE->url);
1441 ariadna 609
        return ['form' => $mform, 'title' => $title];
1 efrain 610
    }
611
 
612
    /**
613
     * Performs the edit definition sharing action.
614
     *
615
     * @return array an array of the edit definition sharing form, and the page title.
616
     */
617
    public function action_editdefinitionsharing(): array {
618
        global $PAGE;
619
        $definitionsummaries = $this->get_definition_summaries();
620
 
621
        $definition = required_param('definition', PARAM_SAFEPATH);
622
        if (!array_key_exists($definition, $definitionsummaries)) {
1441 ariadna 623
            throw new cache_exception('Invalid cache definition requested');
1 efrain 624
        }
625
        $title = get_string('editdefinitionsharing', 'cache', $definition);
626
        $sharingoptions = $definitionsummaries[$definition]['sharingoptions'];
1441 ariadna 627
        $customdata = ['definition' => $definition, 'sharingoptions' => $sharingoptions];
628
        $mform = new cache_definition_sharing_form($PAGE->url, $customdata);
629
        $mform->set_data([
1 efrain 630
            'sharing' => $definitionsummaries[$definition]['selectedsharingoption'],
1441 ariadna 631
            'userinputsharingkey' => $definitionsummaries[$definition]['userinputsharingkey'],
632
        ]);
1 efrain 633
        if ($mform->is_cancelled()) {
634
            redirect($PAGE->url);
635
        } else if ($data = $mform->get_data()) {
636
            $component = $definitionsummaries[$definition]['component'];
637
            $area = $definitionsummaries[$definition]['area'];
638
            // Purge the stores removing stale data before we alter the sharing option.
1441 ariadna 639
            cache_helper::purge_stores_used_by_definition($component, $area);
640
            $writer = config_writer::instance();
1 efrain 641
            $sharing = array_sum(array_keys($data->sharing));
642
            $userinputsharingkey = $data->userinputsharingkey;
643
            $writer->set_definition_sharing($definition, $sharing, $userinputsharingkey);
644
            redirect($PAGE->url);
645
        }
646
 
647
        $PAGE->navbar->add(get_string('updatedefinitionsharing', 'cache'), $PAGE->url);
1441 ariadna 648
        return ['form' => $mform, 'title' => $title];
1 efrain 649
    }
650
 
651
    /**
652
     * Performs the edit mode mappings action.
653
     *
654
     * @return array an array of the edit mode mappings form.
655
     */
656
    public function action_editmodemappings(): array {
657
        global $PAGE;
658
        $storeinstancesummaries = $this->get_store_instance_summaries();
659
        $defaultmodestores = $this->get_default_mode_stores();
660
 
1441 ariadna 661
        $mform = new cache_mode_mappings_form(null, $storeinstancesummaries);
662
        $mform->set_data([
663
            'mode_' . store::MODE_APPLICATION => key($defaultmodestores[store::MODE_APPLICATION]),
664
            'mode_' . store::MODE_SESSION => key($defaultmodestores[store::MODE_SESSION]),
665
            'mode_' . store::MODE_REQUEST => key($defaultmodestores[store::MODE_REQUEST]),
666
        ]);
1 efrain 667
        if ($mform->is_cancelled()) {
668
            redirect($PAGE->url);
669
        } else if ($data = $mform->get_data()) {
1441 ariadna 670
            $mappings = [
671
                store::MODE_APPLICATION => [$data->{'mode_' . store::MODE_APPLICATION}],
672
                store::MODE_SESSION => [$data->{'mode_' . store::MODE_SESSION}],
673
                store::MODE_REQUEST => [$data->{'mode_' . store::MODE_REQUEST}],
674
            ];
675
            $writer = config_writer::instance();
1 efrain 676
            $writer->set_mode_mappings($mappings);
677
            redirect($PAGE->url);
678
        }
679
 
1441 ariadna 680
        return ['form' => $mform];
1 efrain 681
    }
682
 
683
    /**
684
     * Performs the purge definition action.
685
     *
686
     * @return void
687
     */
688
    public function action_purgedefinition() {
689
        global $PAGE;
690
 
691
        require_sesskey();
692
        $id = required_param('definition', PARAM_SAFEPATH);
1441 ariadna 693
        [$component, $area] = explode('/', $id, 2);
694
        $factory = factory::instance();
1 efrain 695
        $definition = $factory->create_definition($component, $area);
696
        if ($definition->has_required_identifiers()) {
697
            // We will have to purge the stores used by this definition.
698
            cache_helper::purge_stores_used_by_definition($component, $area);
699
        } else {
700
            // Alrighty we can purge just the data belonging to this definition.
701
            cache_helper::purge_by_definition($component, $area);
702
        }
703
 
704
        $message = get_string('purgexdefinitionsuccess', 'cache', [
705
                    'name' => $definition->get_name(),
706
                    'component' => $component,
707
                    'area' => $area,
708
                ]);
1441 ariadna 709
        $purgeagainlink = html_writer::link(
710
            new url('/cache/admin.php', [
711
                'action' => 'purgedefinition', 'sesskey' => sesskey(), 'definition' => $id, ]),
712
            get_string('purgeagain', 'cache')
713
        );
1 efrain 714
        redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5);
715
    }
716
 
717
    /**
718
     * Performs the purge action.
719
     *
720
     * @return void
721
     */
722
    public function action_purge() {
723
        global $PAGE;
724
 
725
        require_sesskey();
726
        $store = required_param('store', PARAM_TEXT);
727
        cache_helper::purge_store($store);
728
        $message = get_string('purgexstoresuccess', 'cache', ['store' => $store]);
1441 ariadna 729
        $purgeagainlink = html_writer::link(
730
            new url('/cache/admin.php', [
731
                'action' => 'purgestore', 'sesskey' => sesskey(), 'store' => $store, ]),
732
            get_string('purgeagain', 'cache')
733
        );
1 efrain 734
        redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5);
735
    }
736
 
737
    /**
738
     * Performs the new lock instance action.
739
     *
740
     * @return array An array containing the new lock instance form.
741
     */
742
    public function action_newlockinstance(): array {
743
        global $PAGE;
744
 
745
        // Adds a new lock instance.
746
        $lock = required_param('lock', PARAM_ALPHANUMEXT);
747
        $mform = $this->get_add_lock_form($lock);
748
        if ($mform->is_cancelled()) {
749
            redirect($PAGE->url);
750
        } else if ($data = $mform->get_data()) {
1441 ariadna 751
            $factory = factory::instance();
1 efrain 752
            $config = $factory->create_config_instance(true);
753
            $name = $data->name;
754
            $data = $this->get_lock_configuration_from_data($lock, $data);
755
            $config->add_lock_instance($name, $lock, $data);
756
            redirect($PAGE->url, get_string('addlocksuccess', 'cache', $name), 5);
757
        }
758
 
1441 ariadna 759
        return ['form' => $mform];
1 efrain 760
    }
761
 
762
    /**
763
     * Performs the delete lock action.
764
     *
765
     * @param string $action the action calling this function.
766
     */
767
    public function action_deletelock(string $action): void {
768
        global $OUTPUT, $PAGE, $SITE;
769
        $notifysuccess = true;
770
        $locks = $this->get_lock_summaries();
771
 
772
        $lock = required_param('lock', PARAM_ALPHANUMEXT);
773
        $confirm = optional_param('confirm', false, PARAM_BOOL);
774
        if (!array_key_exists($lock, $locks)) {
775
            $notifysuccess = false;
776
            $notification = get_string('invalidlock', 'cache');
777
        } else if ($locks[$lock]['uses'] > 0) {
778
            $notifysuccess = false;
779
            $notification = get_string('deletelockhasuses', 'cache');
780
        }
781
        if ($notifysuccess) {
782
            if (!$confirm) {
783
                $title = get_string('confirmlockdeletion', 'cache');
1441 ariadna 784
                $params = ['lock' => $lock, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()];
785
                $url = new url($PAGE->url, $params);
786
                $button = new single_button($url, get_string('deletelock', 'cache'));
1 efrain 787
 
788
                $PAGE->set_title($title);
789
                $PAGE->set_heading($SITE->fullname);
790
                echo $OUTPUT->header();
791
                echo $OUTPUT->heading($title);
792
                $confirmation = get_string('deletelockconfirmation', 'cache', $lock);
793
                echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
794
                echo $OUTPUT->footer();
795
                exit;
796
            } else {
797
                require_sesskey();
1441 ariadna 798
                $writer = config_writer::instance();
1 efrain 799
                $writer->delete_lock_instance($lock);
800
                redirect($PAGE->url, get_string('deletelocksuccess', 'cache'), 5);
801
            }
802
        } else {
803
            redirect($PAGE->url, $notification, null, notification::NOTIFY_ERROR);
804
        }
805
    }
806
 
807
    /**
808
     * Outputs the main admin page by generating it through the renderer.
809
     *
810
     * @param \core_cache\output\renderer $renderer the renderer to use to generate the page.
811
     * @return string the HTML for the admin page.
812
     */
813
    public function generate_admin_page(\core_cache\output\renderer $renderer): string {
1441 ariadna 814
        $context = context_system::instance();
1 efrain 815
        $html = '';
816
 
817
        $storepluginsummaries = $this->get_store_plugin_summaries();
818
        $storeinstancesummaries = $this->get_store_instance_summaries();
819
        $definitionsummaries = $this->get_definition_summaries();
820
        $defaultmodestores = $this->get_default_mode_stores();
821
        $locks = $this->get_lock_summaries();
822
 
823
        $html .= $renderer->store_plugin_summaries($storepluginsummaries);
824
        $html .= $renderer->store_instance_summariers($storeinstancesummaries, $storepluginsummaries);
825
        $html .= $renderer->definition_summaries($definitionsummaries, $context);
826
        $html .= $renderer->lock_summaries($locks);
827
        $html .= $renderer->additional_lock_actions();
828
 
1441 ariadna 829
        $applicationstore = join(', ', $defaultmodestores[store::MODE_APPLICATION]);
830
        $sessionstore = join(', ', $defaultmodestores[store::MODE_SESSION]);
831
        $requeststore = join(', ', $defaultmodestores[store::MODE_REQUEST]);
832
        $editurl = new url('/cache/admin.php', ['action' => 'editmodemappings']);
1 efrain 833
        $html .= $renderer->mode_mappings($applicationstore, $sessionstore, $requeststore, $editurl);
834
 
835
        return $html;
836
    }
837
 
838
    /**
839
     * Gets usage information about the whole cache system.
840
     *
841
     * This is a slow function and should only be used on an admin information page.
842
     *
843
     * The returned array lists all cache definitions with fields 'cacheid' and 'stores'. For
844
     * each store, the following fields are available:
845
     *
846
     * - name (store name)
847
     * - class (e.g. cachestore_redis)
848
     * - supported (true if we have any information)
849
     * - items (number of items stored)
850
     * - mean (mean size of item)
851
     * - sd (standard deviation for item sizes)
852
     * - margin (margin of error for mean at 95% confidence)
853
     * - storetotal (total usage for store if known, otherwise null)
854
     *
855
     * The storetotal field will be the same for every cache that uses the same store.
856
     *
857
     * @param int $samplekeys Number of keys to sample when checking size of large caches
858
     * @return array Details of cache usage
859
     */
860
    public function get_usage(int $samplekeys): array {
861
        $results = [];
862
 
1441 ariadna 863
        $factory = factory::instance();
1 efrain 864
 
865
        // Check the caches we already have an instance of, so we don't make another one...
866
        $got = $factory->get_caches_in_use();
867
        $gotid = [];
868
        foreach ($got as $longid => $unused) {
869
            // The IDs here can be of the form cacheid/morestuff if there are parameters in the
870
            // cache. Any entry for a cacheid is good enough to consider that we don't need to make
871
            // another entry ourselves, so we remove the extra bits and track the basic cache id.
872
            $gotid[preg_replace('~^([^/]+/[^/]+)/.*$~', '$1', $longid)] = true;
873
        }
874
 
875
        $storetotals = [];
876
 
877
        $config = $factory->create_config_instance();
878
        foreach ($config->get_definitions() as $configdetails) {
879
            if (!array_key_exists($configdetails['component'] . '/' .  $configdetails['area'], $gotid)) {
880
                // Where possible (if it doesn't need identifiers), make an instance of the cache, otherwise
881
                // we can't get the store instances for it (and it won't show up in the list).
882
                if (empty($configdetails['requireidentifiers'])) {
1441 ariadna 883
                    cache::make($configdetails['component'], $configdetails['area']);
1 efrain 884
                }
885
            }
886
            $definition = $factory->create_definition($configdetails['component'], $configdetails['area']);
887
            $stores = $factory->get_store_instances_in_use($definition);
888
 
889
            // Create object for results about this cache definition.
890
            $currentresult = (object)['cacheid' => $definition->get_id(), 'stores' => []];
891
            $results[$currentresult->cacheid] = $currentresult;
892
 
1441 ariadna 893
            /** @var store $store */
1 efrain 894
            foreach ($stores as $store) {
895
                // Skip static cache.
896
                if ($store instanceof \cachestore_static) {
897
                    continue;
898
                }
899
 
900
                // Get cache size details from store.
901
                $currentstore = $store->cache_size_details($samplekeys);
902
 
903
                // Add in basic information about store.
904
                $currentstore->name = $store->my_name();
905
                $currentstore->class = get_class($store);
906
 
907
                // Add in store total.
908
                if (!array_key_exists($currentstore->name, $storetotals)) {
909
                    $storetotals[$currentstore->name] = $store->store_total_size();
910
                }
911
                $currentstore->storetotal = $storetotals[$currentstore->name];
912
 
913
                $currentresult->stores[] = $currentstore;
914
            }
915
        }
916
 
917
        ksort($results);
918
        return $results;
919
    }
920
}