MDL-66079 core_grades: Add support for multiple grade items in an activity
[moodle.git] / grade / classes / component_gradeitems.php
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/>.
17 /**
18  * Helper class to fetch information about component grade items.
19  *
20  * @package   core_grades
21  * @copyright Andrew Nicols <andrew@nicols.co.uk>
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 declare(strict_types = 1);
27 namespace core_grades;
29 use code_grades\local\gradeitem\itemnumber_mapping;
30 use code_grades\local\gradeitem\advancedgrading_mapping;
32 /**
33  * Helper class to fetch information about component grade items.
34  *
35  * @package   core_grades
36  * @copyright Andrew Nicols <andrew@nicols.co.uk>
37  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class component_gradeitems {
41     /**
42      * Get the gradeitems classname for the specific component.
43      *
44      * @param string $component The component to fetch the classname for
45      * @return string The composed classname
46      */
47     protected static function get_component_classname(string $component): string {
48         return "{$component}\\grades\gradeitems";
49     }
51     /**
52      * Get the grade itemnumber mapping for a component.
53      *
54      * @param string $component The component that the grade item belongs to
55      * @return array
56      */
57     public static function get_itemname_mapping_for_component(string $component): array {
58         $classname = "{$component}\\grades\gradeitems";
60         if (!class_exists($classname)) {
61             return [
62                 0 => '',
63             ];
64         }
66         if (!is_subclass_of($classname, 'core_grades\local\gradeitem\itemnumber_mapping')) {
67             throw new \coding_exception("The {$classname} class does not implement " . itemnumber_mapping::class);
68         }
70         return $classname::get_itemname_mapping_for_component();
71     }
73     /**
74      * Whether the named grading item exists.
75      *
76      * @param string $component
77      * @param string $itemname
78      * @return bool
79      */
80     public static function is_valid_itemname(string $component, string $itemname): bool {
81         $items = self::get_itemname_mapping_for_component($component);
83         return array_search($itemname, $items) !== false;
84     }
86     /**
87      * Check whether the component class defines the advanced grading items.
88      *
89      * @param string $component The component to check
90      * @return bool
91      */
92     public static function defines_advancedgrading_itemnames_for_component(string $component): bool {
93         return is_subclass_of(self::get_component_classname($component), 'core_grades\local\gradeitem\advancedgrading_mapping');
94     }
96     /**
97      * Get the list of advanced grading item names for the named component.
98      *
99      * @param string $component
100      * @return array
101      */
102     public static function get_advancedgrading_itemnames_for_component(string $component): array {
103         $classname = self::get_component_classname($component);
104         if (!self::defines_advancedgrading_itemnames_for_component($component)) {
105             throw new \coding_exception("The {$classname} class does not implement " . advancedgrading_mapping::class);
106         }
108         return $classname::get_advancedgrading_itemnames();
109     }
111     /**
112      * Whether the named grading item name supports advanced grading.
113      *
114      * @param string $component
115      * @param string $itemname
116      * @return bool
117      */
118     public static function is_advancedgrading_itemname(string $component, string $itemname): bool {
119         $gradingareas = self::get_advancedgrading_itemnames_for_component($component);
121         return array_search($itemname, $gradingareas) !== false;
122     }
124     /**
125      * Get the suffixed field name for an activity field mapped from its itemnumber.
126      *
127      * For legacy reasons, the first itemnumber has no suffix on field names.
128      *
129      * @param string $component The component that the grade item belongs to
130      * @param int $itemnumber The grade itemnumber
131      * @param string $fieldname The name of the field to be rewritten
132      * @return string The translated field name
133      */
134     public static function get_field_name_for_itemnumber(string $component, int $itemnumber, string $fieldname): string {
135         $itemname = static::get_itemname_from_itemnumber($component, $itemnumber);
137         if ($itemname) {
138             return "{$fieldname}_{$itemname}";
139         }
141         return $fieldname;
142     }
144     /**
145      * Get the suffixed field name for an activity field mapped from its itemnumber.
146      *
147      * For legacy reasons, the first itemnumber has no suffix on field names.
148      *
149      * @param string $component The component that the grade item belongs to
150      * @param string $itemname The grade itemname
151      * @param string $fieldname The name of the field to be rewritten
152      * @return string The translated field name
153      */
154     public static function get_field_name_for_itemname(string $component, string $itemname, string $fieldname): string {
155         if (empty($itemname)) {
156             return $fieldname;
157         }
159         $itemnumber = static::get_itemnumber_from_itemname($component, $itemname);
161         if ($itemnumber > 0) {
162             return "{$fieldname}_{$itemname}";
163         }
165         return $fieldname;
166     }
168     /**
169      * Get the itemname for an itemnumber.
170      *
171      * For legacy compatability when the itemnumber is 0, the itemname will always be empty.
172      *
173      * @param string $component The component that the grade item belongs to
174      * @param int $itemnumber The grade itemnumber
175      * @return int The grade itemnumber of the itemname
176      */
177     public static function get_itemname_from_itemnumber(string $component, int $itemnumber): string {
178         if ($itemnumber === 0) {
179             return '';
180         }
182         $mappings = self::get_itemname_mapping_for_component($component);
184         if (isset($mappings[$itemnumber])) {
185             return $mappings[$itemnumber];
186         }
188         if ($itemnumber >= 1000) {
189             // An itemnumber >= 1000 belongs to an outcome.
190             return '';
191         }
193         throw new \coding_exception("Unknown itemnumber mapping for {$itemnumber} in {$component}");
194     }
196     /**
197      * Get the itemnumber for a item name.
198      *
199      * For legacy compatability when the itemname is empty, the itemnumber will always be 0.
200      *
201      * @param string $component The component that the grade item belongs to
202      * @param string $itemname The grade itemname
203      * @return int The grade itemname of the itemnumber
204      */
205     public static function get_itemnumber_from_itemname(string $component, string $itemname): int {
206         if (empty($itemname)) {
207             return 0;
208         }
210         $mappings = self::get_itemname_mapping_for_component($component);
212         $flipped = array_flip($mappings);
213         if (isset($flipped[$itemname])) {
214             return $flipped[$itemname];
215         }
217         throw new \coding_exception("Unknown itemnumber mapping for {$itemname} in {$component}");
218     }