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
namespace qbank_managecategories;
18
 
1441 ariadna 19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once($CFG->libdir . "/questionlib.php");
22
 
1 efrain 23
use context;
1441 ariadna 24
use core_question\category_manager;
1 efrain 25
use core_question\local\bank\question_version_status;
1441 ariadna 26
use core_question\output\question_category_selector;
1 efrain 27
use moodle_exception;
28
use html_writer;
29
 
30
/**
31
 * Class helper contains all the library functions.
32
 *
33
 * Library functions used by qbank_managecategories.
34
 * This code is based on lib/questionlib.php by Martin Dougiamas.
35
 *
36
 * @package    qbank_managecategories
37
 * @copyright  2021 Catalyst IT Australia Pty Ltd
38
 * @author     Guillermo Gomez Arias <guillermogomez@catalyst-au.net>
39
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40
 */
41
class helper {
42
    /**
43
     * Name of this plugin.
44
     */
45
    const PLUGINNAME = 'qbank_managecategories';
46
 
47
    /**
48
     * Remove stale questions from a category.
49
     *
1441 ariadna 50
     * This finds and removes any old-style random questions (qtype = random),
51
     * or any questions that were deleted while they were in use by a quiz (status = hidden),
52
     * but those usages have since been removed.
1 efrain 53
     *
1441 ariadna 54
     * If a category only contains stale questions, the users are unable to delete the question
55
     * category unless they move those stale questions to another one category, but to them the
56
     * category may appear empty. The purpose of this function is to detect the questions that
57
     * may have gone stale and remove them.
58
     *
1 efrain 59
     * You will typically use this prior to checking if the category contains questions.
60
     *
61
     * @param int $categoryid The category ID.
62
     * @throws \dml_exception
63
     */
64
    public static function question_remove_stale_questions_from_category(int $categoryid): void {
65
        global $DB;
66
 
67
        $sql = "SELECT q.id
68
                  FROM {question} q
69
                  JOIN {question_versions} qv ON qv.questionid = q.id
70
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
71
                 WHERE qbe.questioncategoryid = :categoryid
72
                   AND (q.qtype = :qtype OR qv.status = :status)";
73
 
74
        $params = ['categoryid' => $categoryid, 'qtype' => 'random', 'status' => question_version_status::QUESTION_STATUS_HIDDEN];
75
        $questions = $DB->get_records_sql($sql, $params);
76
        foreach ($questions as $question) {
77
            // The function question_delete_question does not delete questions in use.
78
            question_delete_question($question->id);
79
        }
80
    }
81
 
82
    /**
83
     * Checks whether this is the only child of a top category in a context.
84
     *
85
     * @param int $categoryid a category id.
86
     * @return bool
87
     * @throws \dml_exception
1441 ariadna 88
     * @deprecated Since Moodle 4.5. Use core_question\category_manager::is_only_child_of_top_category_in_context instead.
89
     * @todo Final removal in Moodle 6.0 MDL-80804
1 efrain 90
     */
1441 ariadna 91
    #[\core\attribute\deprecated(
92
        'core_question\category_manager::is_only_child_of_top_category_in_context',
93
        since: 4.5,
94
        reason: 'Moved to core namespace',
95
        mdl: 'MDL-72397'
96
    )]
1 efrain 97
    public static function question_is_only_child_of_top_category_in_context(int $categoryid): bool {
1441 ariadna 98
        \core\deprecation::emit_deprecation([__CLASS__, __FUNCTION__]);
99
        $manager = new category_manager();
100
        return $manager->is_only_child_of_top_category_in_context($categoryid);
1 efrain 101
    }
102
 
103
    /**
104
     * Checks whether the category is a "Top" category (with no parent).
105
     *
106
     * @param int $categoryid a category id.
107
     * @return bool
108
     * @throws \dml_exception
1441 ariadna 109
     * @deprecated Since Moodle 4.5. Use core_question\category_manager::is_top_category instead.
110
     * @todo Final removal in Moodle 6.0 MDL-80804.
1 efrain 111
     */
1441 ariadna 112
    #[\core\attribute\deprecated(
113
        'core_question\category_manager::is_top_category',
114
        since: 4.5,
115
        reason: 'Moved to core namespace',
116
        mdl: 'MDL-72397'
117
    )]
1 efrain 118
    public static function question_is_top_category(int $categoryid): bool {
1441 ariadna 119
        \core\deprecation::emit_deprecation([__CLASS__, __FUNCTION__]);
120
        $manager = new category_manager();
121
        return $manager->is_top_category($categoryid);
1 efrain 122
    }
123
 
124
    /**
125
     * Ensures that this user is allowed to delete this category.
126
     *
127
     * @param int $todelete a category id.
128
     * @throws \required_capability_exception
129
     * @throws \dml_exception|moodle_exception
1441 ariadna 130
     * @deprecated Since Moodle 4.5. Use core_question\category_manager::can_delete_category instead.
131
     * @todo Final removal in Moodle 6.0 MDL-80804.
1 efrain 132
     */
1441 ariadna 133
    #[\core\attribute\deprecated(
134
        'core_question\category_manager::can_delete_category',
135
        since: 4.5,
136
        reason: 'Moved to core namespace',
137
        mdl: 'MDL-72397'
138
    )]
1 efrain 139
    public static function question_can_delete_cat(int $todelete): void {
1441 ariadna 140
        \core\deprecation::emit_deprecation([__CLASS__, __FUNCTION__]);
141
        $manager = new category_manager();
142
        $manager->require_can_delete_category($todelete);
1 efrain 143
    }
144
 
145
    /**
146
     * Only for the use of add_indented_names().
147
     *
148
     * Recursively adds an indentedname field to each category, starting with the category
149
     * with id $id, and dealing with that category and all its children, and
150
     * return a new array, with those categories in the right order.
151
     *
152
     * @param array $categories an array of categories which has had childids
153
     *          fields added by flatten_category_tree(). Passed by reference for
154
     *          performance only. It is not modfied.
155
     * @param int $id the category to start the indenting process from.
156
     * @param int $depth the indent depth. Used in recursive calls.
157
     * @param int $nochildrenof
158
     * @return array a new array of categories, in the right order for the tree.
159
     */
160
    public static function flatten_category_tree(array &$categories, $id, int $depth = 0, int $nochildrenof = -1): array {
161
 
162
        // Indent the name of this category.
163
        $newcategories = [];
164
        $newcategories[$id] = $categories[$id];
165
        $newcategories[$id]->indentedname = str_repeat('&nbsp;&nbsp;&nbsp;', $depth) .
166
            $categories[$id]->name;
167
 
168
        // Recursively indent the children.
169
        foreach ($categories[$id]->childids as $childid) {
170
            if ($childid != $nochildrenof) {
171
                $newcategories = $newcategories + self::flatten_category_tree(
1441 ariadna 172
                    $categories,
173
                    $childid,
174
                    $depth + 1,
175
                    $nochildrenof,
176
                );
1 efrain 177
            }
178
        }
179
 
180
        // Remove the childids array that were temporarily added.
181
        unset($newcategories[$id]->childids);
182
 
183
        return $newcategories;
184
    }
185
 
186
    /**
187
     * Format categories into an indented list reflecting the tree structure.
188
     *
189
     * @param array $categories An array of category objects, for example from the.
190
     * @param int $nochildrenof
191
     * @return array The formatted list of categories.
192
     */
193
    public static function add_indented_names(array $categories, int $nochildrenof = -1): array {
194
 
195
        // Add an array to each category to hold the child category ids. This array
196
        // will be removed again by flatten_category_tree(). It should not be used
197
        // outside these two functions.
198
        foreach (array_keys($categories) as $id) {
199
            $categories[$id]->childids = [];
200
        }
201
 
202
        // Build the tree structure, and record which categories are top-level.
203
        // We have to be careful, because the categories array may include published
204
        // categories from other courses, but not their parents.
205
        $toplevelcategoryids = [];
206
        foreach (array_keys($categories) as $id) {
1441 ariadna 207
            if (
208
                !empty($categories[$id]->parent) &&
209
                array_key_exists($categories[$id]->parent, $categories)
210
            ) {
1 efrain 211
                $categories[$categories[$id]->parent]->childids[] = $id;
212
            } else {
213
                $toplevelcategoryids[] = $id;
214
            }
215
        }
216
 
217
        // Flatten the tree to and add the indents.
218
        $newcategories = [];
219
        foreach ($toplevelcategoryids as $id) {
220
            $newcategories = $newcategories + self::flatten_category_tree(
1441 ariadna 221
                $categories,
222
                $id,
223
                0,
224
                $nochildrenof,
225
            );
1 efrain 226
        }
227
 
228
        return $newcategories;
229
    }
230
 
231
    /**
232
     * Output a select menu of question categories.
233
     *
234
     * Categories from this course and (optionally) published categories from other courses
235
     * are included. Optionally, only categories the current user may edit can be included.
236
     *
237
     * @param array $contexts
238
     * @param bool $top
239
     * @param int $currentcat
240
     * @param string $selected optionally, the id of a category to be selected by
241
     *      default in the dropdown.
242
     * @param int $nochildrenof
243
     * @param bool $return to return the string of the select menu or echo that from the method
1441 ariadna 244
     * @return ?string The HTML, or null if the $return is false.
1 efrain 245
     * @throws \coding_exception|\dml_exception
246
     */
1441 ariadna 247
    public static function question_category_select_menu(
248
        array $contexts,
249
        bool $top = false,
250
        int $currentcat = 0,
251
        string $selected = "",
252
        int $nochildrenof = -1,
253
        bool $return = false,
254
    ): ?string {
255
        $categoriesarray = self::question_category_options(
256
            $contexts,
257
            $top,
258
            $currentcat,
259
            false,
260
            $nochildrenof,
261
            false,
262
        );
1 efrain 263
        $choose = '';
264
        $options = [];
265
        foreach ($categoriesarray as $group => $opts) {
266
            $options[] = [$group => $opts];
267
        }
1441 ariadna 268
        $outputhtml = html_writer::label(
269
            get_string('questioncategory', 'core_question'),
270
            'id_movetocategory',
271
            false,
272
            ['class' => 'accesshide'],
273
        );
1 efrain 274
        $attrs = [
275
            'id' => 'id_movetocategory',
1441 ariadna 276
            'class' => 'form-select',
1 efrain 277
            'data-action' => 'toggle',
278
            'data-togglegroup' => 'qbank',
279
            'data-toggle' => 'action',
280
            'disabled' => false,
281
        ];
282
        $outputhtml .= html_writer::select($options, 'category', $selected, $choose, $attrs);
283
        if ($return) {
284
            return $outputhtml;
285
        } else {
286
            echo $outputhtml;
1441 ariadna 287
            return null;
1 efrain 288
        }
289
    }
290
 
291
    /**
292
     * Get all the category objects, including a count of the number of questions in that category,
293
     * for all the categories in the lists $contexts.
294
     *
1441 ariadna 295
     * @param string $contexts comma separated list of contextids
1 efrain 296
     * @param string $sortorder used as the ORDER BY clause in the select statement.
297
     * @param bool $top Whether to return the top categories or not.
298
     * @param int $showallversions 1 to show all versions not only the latest.
299
     * @return array of category objects.
300
     * @throws \dml_exception
301
     */
1441 ariadna 302
    public static function get_categories_for_contexts(
303
        string $contexts,
304
        string $sortorder = 'parent, sortorder, name ASC',
305
        bool $top = false,
306
        int $showallversions = 0,
307
    ): array {
308
        return (new question_category_selector())->get_categories_for_contexts($contexts, $sortorder, $top, $showallversions);
1 efrain 309
    }
310
 
311
    /**
312
     * Output an array of question categories.
313
     *
314
     * @param array $contexts The list of contexts.
315
     * @param bool $top Whether to return the top categories or not.
316
     * @param int $currentcat
317
     * @param bool $popupform
318
     * @param int $nochildrenof
319
     * @param bool $escapecontextnames Whether the returned name of the thing is to be HTML escaped or not.
320
     * @return array
321
     * @throws \coding_exception|\dml_exception
322
     */
1441 ariadna 323
    public static function question_category_options(
324
        array $contexts,
325
        bool $top = false,
326
        int $currentcat = 0,
327
        bool $popupform = false,
328
        int $nochildrenof = -1,
329
        bool $escapecontextnames = true,
330
    ): array {
331
        return (new question_category_selector())->question_category_options(
332
            $contexts,
333
            $top,
334
            $currentcat,
335
            $popupform,
336
            $nochildrenof,
337
            $escapecontextnames,
338
        );
1 efrain 339
    }
340
 
341
    /**
342
     * Add context in categories key.
343
     *
344
     * @param array $categories The list of categories.
345
     * @return array
346
     */
347
    public static function question_add_context_in_key(array $categories): array {
1441 ariadna 348
        return (new question_category_selector())->question_add_context_in_key($categories);
1 efrain 349
    }
350
 
351
    /**
352
     * Finds top categories in the given categories hierarchy and replace their name with a proper localised string.
353
     *
354
     * @param array $categories An array of question categories.
355
     * @param bool $escape Whether the returned name of the thing is to be HTML escaped or not.
356
     * @return array The same question category list given to the function, with the top category names being translated.
357
     * @throws \coding_exception
358
     */
359
    public static function question_fix_top_names(array $categories, bool $escape = true): array {
1441 ariadna 360
        return (new question_category_selector())->question_fix_top_names($categories, $escape);
361
    }
1 efrain 362
 
1441 ariadna 363
    /**
364
     * Combine id and context id for a question category
365
     *
366
     * @param \stdClass $category a category to extract its id and context id
367
     * @return string the combined string
368
     */
369
    public static function combine_id_context(\stdClass $category): string {
370
        return $category->id . ',' . $category->contextid;
1 efrain 371
    }
372
}