1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
20 * A namespace contains license specific functions
21 *
22 * @since      Moodle 2.0
23 * @package    core
24 * @subpackage lib
25 * @copyright  2010 Dongsheng Cai <dongsheng@moodle.com>
26 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29defined('MOODLE_INTERNAL') || die();
30
31class license_manager {
32
33    /**
34     * License is a core license and can not be updated or deleted.
35     */
36    const CORE_LICENSE = 0;
37
38    /**
39     * License is a custom license and can be updated and/or deleted.
40     */
41    const CUSTOM_LICENSE = 1;
42
43    /**
44     * Integer representation of boolean for a license that is enabled.
45     */
46    const LICENSE_ENABLED = 1;
47
48    /**
49     * Integer representation of boolean for a license that is disabled.
50     */
51    const LICENSE_DISABLED = 0;
52
53    /**
54     * Integer for moving a license up order.
55     */
56    const LICENSE_MOVE_UP = -1;
57
58    /**
59     * Integer for moving a license down order.
60     */
61    const LICENSE_MOVE_DOWN = 1;
62
63    /**
64     * Save a license record.
65     *
66     * @param object $license {
67     *            shortname => string a shortname of license, will be refered by files table[required]
68     *            fullname  => string the fullname of the license [required]
69     *            source => string the homepage of the license type[required]
70     *            enabled => int is it enabled?
71     *            version  => int a version number used by moodle [required]
72     * }
73     */
74    static public function save($license) {
75
76        $existinglicense = self::get_license_by_shortname($license->shortname);
77
78        if (!empty($existinglicense)) {
79            $id = $existinglicense->id;
80            if ($existinglicense->custom == self::CORE_LICENSE) {
81                // Can only update the enabled status and sortorder for core licenses.
82                $existinglicense->enabled = $license->enabled;
83                $existinglicense->sortorder = $license->sortorder;
84                $license = $existinglicense;
85            }
86            $license->id = $id;
87            self::update($license);
88        } else {
89            self::create($license);
90        }
91
92        return true;
93    }
94
95    /**
96     * Adding a new license type
97     *
98     * @deprecated Since Moodle 3.9, MDL-45184.
99     * @todo MDL-67344 This will be deleted in Moodle 4.1.
100     * @see license_manager::save()
101     *
102     * @param object $license the license record to add.
103     *
104     * @return bool true on success.
105     */
106    public function add($license) : bool {
107        debugging('add() is deprecated. Please use license_manager::save() instead.', DEBUG_DEVELOPER);
108
109        return self::save($license);
110    }
111
112    /**
113     * Create a license record.
114     *
115     * @param object $license the license to create record for.
116     */
117    static protected function create($license) {
118        global $DB;
119
120        $licensecount = count(self::get_licenses());
121        $license->sortorder = $licensecount + 1;
122        // Enable all created license by default.
123        $license->enabled = self::LICENSE_ENABLED;
124        // API can only create custom licenses, core licenses
125        // are directly created at install or upgrade.
126        $license->custom = self::CUSTOM_LICENSE;
127
128        $DB->insert_record('license', $license);
129        self::reset_license_cache();
130        // Update the config setting of active licenses.
131        self::set_active_licenses();
132    }
133
134    /**
135     * Read licens record(s) from database.
136     *
137     * @param array $params license parameters to return licenses for.
138     *
139     * @return array $filteredlicenses object[] of licenses.
140     */
141    static public function read(array $params = []) {
142        $licenses = self::get_licenses();
143
144        $filteredlicenses = [];
145
146        foreach ($licenses as $shortname => $license) {
147            $filtermatch = true;
148            foreach ($params as $key => $value) {
149                if ($license->$key != $value) {
150                    $filtermatch = false;
151                }
152            }
153            if ($filtermatch) {
154                $filteredlicenses[$shortname] = $license;
155            }
156        }
157        return $filteredlicenses;
158
159    }
160
161    /**
162     * Update a license record.
163     *
164     * @param object $license the license to update record for.
165     *
166     * @throws \moodle_exception if attempting to update a core license.
167     */
168    static protected function update($license) {
169        global $DB;
170
171        $DB->update_record('license', $license);
172        self::reset_license_cache();
173    }
174
175    /**
176     * Delete a custom license.
177     *
178     * @param string $licenseshortname the shortname of license.
179     *
180     * @throws \moodle_exception when attempting to delete a license you are not allowed to.
181     */
182    static public function delete($licenseshortname) {
183        global $DB;
184
185        $licensetodelete = self::get_license_by_shortname($licenseshortname);
186
187        if (!empty($licensetodelete)) {
188            if ($licensetodelete->custom == self::CUSTOM_LICENSE) {
189                // Check that the license is not in use by any files, if so it cannot be deleted.
190                $countfilesusinglicense = $DB->count_records('files', ['license' => $licenseshortname]);
191                if ($countfilesusinglicense > 0) {
192                    throw new moodle_exception('cannotdeletelicenseinuse', 'license');
193                }
194                $deletedsortorder = $licensetodelete->sortorder;
195                $DB->delete_records('license', ['id' => $licensetodelete->id]);
196
197                // We've deleted a license, so update our list of licenses so we don't save the deleted license again.
198                self::reset_license_cache();
199                $licenses = self::get_licenses();
200
201                foreach ($licenses as $license) {
202                    if ($license->sortorder > $deletedsortorder) {
203                        $license->sortorder = $license->sortorder - 1;
204                        self::save($license);
205                    }
206                }
207
208                // Update the config setting of active licenses as well.
209                self::set_active_licenses();
210
211            } else {
212                throw new moodle_exception('cannotdeletecore', 'license');
213            }
214        } else {
215            throw new moodle_exception('licensenotfoundshortname', 'license', $licenseshortname);
216        }
217    }
218
219    /**
220     * Get license records.
221     *
222     * @return array|false object[] of license records of false if none.
223     */
224    static public function get_licenses() {
225        global $DB;
226
227        $cache = \cache::make('core', 'license');
228        $licenses = $cache->get('licenses');
229
230        if ($licenses === false) {
231            $licenses = [];
232            $records = $DB->get_records_select('license', null, null, 'sortorder ASC');
233            foreach ($records as $license) {
234                $licenses[$license->shortname] = $license;
235            }
236            $cache->set('licenses', $licenses);
237        }
238
239        foreach ($licenses as $license) {
240            // Localise the license names.
241            if ($license->custom == self::CORE_LICENSE) {
242                $license->fullname = get_string($license->shortname, 'core_license');
243            } else {
244                $license->fullname = format_string($license->fullname);
245            }
246        }
247
248        return $licenses;
249    }
250
251    /**
252     * Change the sort order of a license (and it's sibling license as a result).
253     *
254     * @param int $direction value to change sortorder of license by.
255     * @param string $licenseshortname the shortname of license to changes sortorder for.
256     *
257     * @throws \moodle_exception if attempting to use invalid direction value.
258     */
259    static public function change_license_sortorder(int $direction, string $licenseshortname) : void {
260
261        if ($direction != self::LICENSE_MOVE_UP && $direction != self::LICENSE_MOVE_DOWN) {
262            throw new coding_exception(
263                'Must use a valid licence API move direction constant (LICENSE_MOVE_UP or LICENSE_MOVE_DOWN)');
264        }
265
266        $licenses = self::get_licenses();
267        $licensetoupdate = $licenses[$licenseshortname];
268
269        $currentsortorder = $licensetoupdate->sortorder;
270        $targetsortorder = $currentsortorder + $direction;
271
272        if ($targetsortorder > 0 && $targetsortorder <= count($licenses) ) {
273            foreach ($licenses as $license) {
274                if ($license->sortorder == $targetsortorder) {
275                    $license->sortorder = $license->sortorder - $direction;
276                    self::update($license);
277                }
278            }
279            $licensetoupdate->sortorder = $targetsortorder;
280            self::update($licensetoupdate);
281        }
282    }
283
284    /**
285     * Get license record by shortname
286     *
287     * @param string $name the shortname of license
288     * @return object|null the license or null if no license found.
289     */
290    static public function get_license_by_shortname(string $name) {
291        $licenses = self::read(['shortname' => $name]);
292
293        if (!empty($licenses)) {
294            $license = reset($licenses);
295        } else {
296            $license = null;
297        }
298
299        return $license;
300    }
301
302    /**
303     * Enable a license
304     * @param string $license the shortname of license
305     * @return boolean
306     */
307    static public function enable($license) {
308        if ($license = self::get_license_by_shortname($license)) {
309            $license->enabled = self::LICENSE_ENABLED;
310            self::update($license);
311        }
312        self::set_active_licenses();
313
314        return true;
315    }
316
317    /**
318     * Disable a license
319     * @param string $license the shortname of license
320     * @return boolean
321     */
322    static public function disable($license) {
323        global $CFG;
324        // Site default license cannot be disabled!
325        if ($license == $CFG->sitedefaultlicense) {
326            print_error('error');
327        }
328        if ($license = self::get_license_by_shortname($license)) {
329            $license->enabled = self::LICENSE_DISABLED;
330            self::update($license);
331        }
332        self::set_active_licenses();
333
334        return true;
335    }
336
337    /**
338     * Store active licenses in global config.
339     */
340    static protected function set_active_licenses() {
341        $licenses = self::read(['enabled' => self::LICENSE_ENABLED]);
342        $result = array();
343        foreach ($licenses as $l) {
344            $result[] = $l->shortname;
345        }
346        set_config('licenses', implode(',', $result));
347    }
348
349    /**
350     * Get the globally configured active licenses.
351     *
352     * @return array of license objects.
353     * @throws \coding_exception
354     */
355    static public function get_active_licenses() {
356        global $CFG;
357
358        $result = [];
359
360        if (!empty($CFG->licenses)) {
361            $activelicenses = explode(',', $CFG->licenses);
362            $licenses = self::get_licenses();
363            foreach ($licenses as $license) {
364                if (in_array($license->shortname, $activelicenses)) {
365                    $result[$license->shortname] = $license;
366                }
367            }
368        }
369
370        return $result;
371    }
372
373    /**
374     * Get the globally configured active licenses as an array.
375     *
376     * @return array $licenses an associative array of licenses shaped as ['shortname' => 'fullname']
377     */
378    static public function get_active_licenses_as_array() {
379        $activelicenses = self::get_active_licenses();
380
381        $licenses = [];
382        foreach ($activelicenses as $license) {
383            $licenses[$license->shortname] = $license->fullname;
384        }
385
386        return $licenses;
387    }
388
389    /**
390     * Install moodle built-in licenses.
391     */
392    static public function install_licenses() {
393        global $CFG;
394
395        require_once($CFG->libdir . '/db/upgradelib.php');
396
397        upgrade_core_licenses();
398    }
399
400    /**
401     * Reset the license cache so it rebuilds next time licenses are fetched.
402     */
403    static public function reset_license_cache() {
404        $cache = \cache::make('core', 'license');
405        $cache->delete('licenses');
406    }
407}
408