| 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 |  * moodlelib.php - Moodle main library
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * Main library file of miscellaneous general-purpose Moodle functions.
 | 
        
           |  |  | 21 |  * Other main libraries:
 | 
        
           |  |  | 22 |  *  - weblib.php      - functions that produce web output
 | 
        
           |  |  | 23 |  *  - datalib.php     - functions that access the database
 | 
        
           |  |  | 24 |  *
 | 
        
           |  |  | 25 |  * @package    core
 | 
        
           |  |  | 26 |  * @subpackage lib
 | 
        
           |  |  | 27 |  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
 | 
        
           |  |  | 28 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 29 |  */
 | 
        
           |  |  | 30 |   | 
        
           |  |  | 31 | use core\di;
 | 
        
           |  |  | 32 | use core\hook;
 | 
        
           |  |  | 33 |   | 
        
           |  |  | 34 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 35 |   | 
        
           |  |  | 36 | // CONSTANTS (Encased in phpdoc proper comments).
 | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 | // Date and time constants.
 | 
        
           |  |  | 39 | /**
 | 
        
           |  |  | 40 |  * Time constant - the number of seconds in a year
 | 
        
           |  |  | 41 |  */
 | 
        
           |  |  | 42 | define('YEARSECS', 31536000);
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 | /**
 | 
        
           |  |  | 45 |  * Time constant - the number of seconds in a week
 | 
        
           |  |  | 46 |  */
 | 
        
           |  |  | 47 | define('WEEKSECS', 604800);
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 | /**
 | 
        
           |  |  | 50 |  * Time constant - the number of seconds in a day
 | 
        
           |  |  | 51 |  */
 | 
        
           |  |  | 52 | define('DAYSECS', 86400);
 | 
        
           |  |  | 53 |   | 
        
           |  |  | 54 | /**
 | 
        
           |  |  | 55 |  * Time constant - the number of seconds in an hour
 | 
        
           |  |  | 56 |  */
 | 
        
           |  |  | 57 | define('HOURSECS', 3600);
 | 
        
           |  |  | 58 |   | 
        
           |  |  | 59 | /**
 | 
        
           |  |  | 60 |  * Time constant - the number of seconds in a minute
 | 
        
           |  |  | 61 |  */
 | 
        
           |  |  | 62 | define('MINSECS', 60);
 | 
        
           |  |  | 63 |   | 
        
           |  |  | 64 | /**
 | 
        
           |  |  | 65 |  * Time constant - the number of minutes in a day
 | 
        
           |  |  | 66 |  */
 | 
        
           |  |  | 67 | define('DAYMINS', 1440);
 | 
        
           |  |  | 68 |   | 
        
           |  |  | 69 | /**
 | 
        
           |  |  | 70 |  * Time constant - the number of minutes in an hour
 | 
        
           |  |  | 71 |  */
 | 
        
           |  |  | 72 | define('HOURMINS', 60);
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 | // Parameter constants - every call to optional_param(), required_param()
 | 
        
           |  |  | 75 | // or clean_param() should have a specified type of parameter.
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 | // We currently include \core\param manually here to avoid broken upgrades.
 | 
        
           |  |  | 78 | // This may change after the next LTS release as LTS releases require the previous LTS release.
 | 
        
           |  |  | 79 | require_once(__DIR__ . '/classes/deprecation.php');
 | 
        
           |  |  | 80 | require_once(__DIR__ . '/classes/param.php');
 | 
        
           |  |  | 81 |   | 
        
           |  |  | 82 | /**
 | 
        
           |  |  | 83 |  * PARAM_ALPHA - contains only English ascii letters [a-zA-Z].
 | 
        
           |  |  | 84 |  */
 | 
        
           |  |  | 85 | define('PARAM_ALPHA', \core\param::ALPHA->value);
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 | /**
 | 
        
           |  |  | 88 |  * PARAM_ALPHAEXT the same contents as PARAM_ALPHA (English ascii letters [a-zA-Z]) plus the chars in quotes: "_-" allowed
 | 
        
           |  |  | 89 |  * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed
 | 
        
           |  |  | 90 |  */
 | 
        
           |  |  | 91 | define('PARAM_ALPHAEXT', \core\param::ALPHAEXT->value);
 | 
        
           |  |  | 92 |   | 
        
           |  |  | 93 | /**
 | 
        
           |  |  | 94 |  * PARAM_ALPHANUM - expected numbers 0-9 and English ascii letters [a-zA-Z] only.
 | 
        
           |  |  | 95 |  */
 | 
        
           |  |  | 96 | define('PARAM_ALPHANUM', \core\param::ALPHANUM->value);
 | 
        
           |  |  | 97 |   | 
        
           |  |  | 98 | /**
 | 
        
           |  |  | 99 |  * PARAM_ALPHANUMEXT - expected numbers 0-9, letters (English ascii letters [a-zA-Z]) and _- only.
 | 
        
           |  |  | 100 |  */
 | 
        
           |  |  | 101 | define('PARAM_ALPHANUMEXT', \core\param::ALPHANUMEXT->value);
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 | /**
 | 
        
           |  |  | 104 |  * PARAM_AUTH - actually checks to make sure the string is a valid auth plugin
 | 
        
           |  |  | 105 |  */
 | 
        
           |  |  | 106 | define('PARAM_AUTH', \core\param::AUTH->value);
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 | /**
 | 
        
           |  |  | 109 |  * PARAM_BASE64 - Base 64 encoded format
 | 
        
           |  |  | 110 |  */
 | 
        
           |  |  | 111 | define('PARAM_BASE64', \core\param::BASE64->value);
 | 
        
           |  |  | 112 |   | 
        
           |  |  | 113 | /**
 | 
        
           |  |  | 114 |  * PARAM_BOOL - converts input into 0 or 1, use for switches in forms and urls.
 | 
        
           |  |  | 115 |  */
 | 
        
           |  |  | 116 | define('PARAM_BOOL', \core\param::BOOL->value);
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 | /**
 | 
        
           |  |  | 119 |  * PARAM_CAPABILITY - A capability name, like 'moodle/role:manage'. Actually
 | 
        
           |  |  | 120 |  * checked against the list of capabilities in the database.
 | 
        
           |  |  | 121 |  */
 | 
        
           |  |  | 122 | define('PARAM_CAPABILITY', \core\param::CAPABILITY->value);
 | 
        
           |  |  | 123 |   | 
        
           |  |  | 124 | /**
 | 
        
           |  |  | 125 |  * PARAM_CLEANHTML - cleans submitted HTML code. Note that you almost never want
 | 
        
           |  |  | 126 |  * to use this. The normal mode of operation is to use PARAM_RAW when receiving
 | 
        
           |  |  | 127 |  * the input (required/optional_param or formslib) and then sanitise the HTML
 | 
        
           |  |  | 128 |  * using format_text on output. This is for the rare cases when you want to
 | 
        
           |  |  | 129 |  * sanitise the HTML on input. This cleaning may also fix xhtml strictness.
 | 
        
           |  |  | 130 |  */
 | 
        
           |  |  | 131 | define('PARAM_CLEANHTML', \core\param::CLEANHTML->value);
 | 
        
           |  |  | 132 |   | 
        
           |  |  | 133 | /**
 | 
        
           |  |  | 134 |  * PARAM_EMAIL - an email address following the RFC
 | 
        
           |  |  | 135 |  */
 | 
        
           |  |  | 136 | define('PARAM_EMAIL', \core\param::EMAIL->value);
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 | /**
 | 
        
           |  |  | 139 |  * PARAM_FILE - safe file name, all dangerous chars are stripped, protects against XSS, SQL injections and directory traversals
 | 
        
           |  |  | 140 |  */
 | 
        
           |  |  | 141 | define('PARAM_FILE', \core\param::FILE->value);
 | 
        
           |  |  | 142 |   | 
        
           |  |  | 143 | /**
 | 
        
           |  |  | 144 |  * PARAM_FLOAT - a real/floating point number.
 | 
        
           |  |  | 145 |  *
 | 
        
           |  |  | 146 |  * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
 | 
        
           |  |  | 147 |  * It does not work for languages that use , as a decimal separator.
 | 
        
           |  |  | 148 |  * Use PARAM_LOCALISEDFLOAT instead.
 | 
        
           |  |  | 149 |  */
 | 
        
           |  |  | 150 | define('PARAM_FLOAT', \core\param::FLOAT->value);
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 152 | /**
 | 
        
           |  |  | 153 |  * PARAM_LOCALISEDFLOAT - a localised real/floating point number.
 | 
        
           |  |  | 154 |  * This is preferred over PARAM_FLOAT for numbers typed in by the user.
 | 
        
           |  |  | 155 |  * Cleans localised numbers to computer readable numbers; false for invalid numbers.
 | 
        
           |  |  | 156 |  */
 | 
        
           |  |  | 157 | define('PARAM_LOCALISEDFLOAT', \core\param::LOCALISEDFLOAT->value);
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 | /**
 | 
        
           |  |  | 160 |  * PARAM_HOST - expected fully qualified domain name (FQDN) or an IPv4 dotted quad (IP address)
 | 
        
           |  |  | 161 |  */
 | 
        
           |  |  | 162 | define('PARAM_HOST', \core\param::HOST->value);
 | 
        
           |  |  | 163 |   | 
        
           |  |  | 164 | /**
 | 
        
           |  |  | 165 |  * PARAM_INT - integers only, use when expecting only numbers.
 | 
        
           |  |  | 166 |  */
 | 
        
           |  |  | 167 | define('PARAM_INT', \core\param::INT->value);
 | 
        
           |  |  | 168 |   | 
        
           |  |  | 169 | /**
 | 
        
           |  |  | 170 |  * PARAM_LANG - checks to see if the string is a valid installed language in the current site.
 | 
        
           |  |  | 171 |  */
 | 
        
           |  |  | 172 | define('PARAM_LANG', \core\param::LANG->value);
 | 
        
           |  |  | 173 |   | 
        
           |  |  | 174 | /**
 | 
        
           |  |  | 175 |  * PARAM_LOCALURL - expected properly formatted URL as well as one that refers to the local server itself. (NOT orthogonal to the
 | 
        
           |  |  | 176 |  * others! Implies PARAM_URL!)
 | 
        
           |  |  | 177 |  */
 | 
        
           |  |  | 178 | define('PARAM_LOCALURL', \core\param::LOCALURL->value);
 | 
        
           |  |  | 179 |   | 
        
           |  |  | 180 | /**
 | 
        
           |  |  | 181 |  * PARAM_NOTAGS - all html tags are stripped from the text. Do not abuse this type.
 | 
        
           |  |  | 182 |  */
 | 
        
           |  |  | 183 | define('PARAM_NOTAGS', \core\param::NOTAGS->value);
 | 
        
           |  |  | 184 |   | 
        
           |  |  | 185 | /**
 | 
        
           |  |  | 186 |  * PARAM_PATH - safe relative path name, all dangerous chars are stripped, protects against XSS, SQL injections and directory
 | 
        
           |  |  | 187 |  * traversals note: the leading slash is not removed, window drive letter is not allowed
 | 
        
           |  |  | 188 |  */
 | 
        
           |  |  | 189 | define('PARAM_PATH', \core\param::PATH->value);
 | 
        
           |  |  | 190 |   | 
        
           |  |  | 191 | /**
 | 
        
           |  |  | 192 |  * PARAM_PEM - Privacy Enhanced Mail format
 | 
        
           |  |  | 193 |  */
 | 
        
           |  |  | 194 | define('PARAM_PEM', \core\param::PEM->value);
 | 
        
           |  |  | 195 |   | 
        
           |  |  | 196 | /**
 | 
        
           |  |  | 197 |  * PARAM_PERMISSION - A permission, one of CAP_INHERIT, CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT.
 | 
        
           |  |  | 198 |  */
 | 
        
           |  |  | 199 | define('PARAM_PERMISSION', \core\param::PERMISSION->value);
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 | /**
 | 
        
           |  |  | 202 |  * PARAM_RAW specifies a parameter that is not cleaned/processed in any way except the discarding of the invalid utf-8 characters
 | 
        
           |  |  | 203 |  */
 | 
        
           |  |  | 204 | define('PARAM_RAW', \core\param::RAW->value);
 | 
        
           |  |  | 205 |   | 
        
           |  |  | 206 | /**
 | 
        
           |  |  | 207 |  * PARAM_RAW_TRIMMED like PARAM_RAW but leading and trailing whitespace is stripped.
 | 
        
           |  |  | 208 |  */
 | 
        
           |  |  | 209 | define('PARAM_RAW_TRIMMED', \core\param::RAW_TRIMMED->value);
 | 
        
           |  |  | 210 |   | 
        
           |  |  | 211 | /**
 | 
        
           |  |  | 212 |  * PARAM_SAFEDIR - safe directory name, suitable for include() and require()
 | 
        
           |  |  | 213 |  */
 | 
        
           |  |  | 214 | define('PARAM_SAFEDIR', \core\param::SAFEDIR->value);
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 | /**
 | 
        
           |  |  | 217 |  * PARAM_SAFEPATH - several PARAM_SAFEDIR joined by "/", suitable for include() and require(), plugin paths
 | 
        
           |  |  | 218 |  * and other references to Moodle code files.
 | 
        
           |  |  | 219 |  *
 | 
        
           |  |  | 220 |  * This is NOT intended to be used for absolute paths or any user uploaded files.
 | 
        
           |  |  | 221 |  */
 | 
        
           |  |  | 222 | define('PARAM_SAFEPATH', \core\param::SAFEPATH->value);
 | 
        
           |  |  | 223 |   | 
        
           |  |  | 224 | /**
 | 
        
           |  |  | 225 |  * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9.  Numbers and comma only.
 | 
        
           |  |  | 226 |  */
 | 
        
           |  |  | 227 | define('PARAM_SEQUENCE', \core\param::SEQUENCE->value);
 | 
        
           |  |  | 228 |   | 
        
           |  |  | 229 | /**
 | 
        
           |  |  | 230 |  * PARAM_TAG - one tag (interests, blogs, etc.) - mostly international characters and space, <> not supported
 | 
        
           |  |  | 231 |  */
 | 
        
           |  |  | 232 | define('PARAM_TAG', \core\param::TAG->value);
 | 
        
           |  |  | 233 |   | 
        
           |  |  | 234 | /**
 | 
        
           |  |  | 235 |  * PARAM_TAGLIST - list of tags separated by commas (interests, blogs, etc.)
 | 
        
           |  |  | 236 |  */
 | 
        
           |  |  | 237 | define('PARAM_TAGLIST', \core\param::TAGLIST->value);
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 | /**
 | 
        
           |  |  | 240 |  * PARAM_TEXT - general plain text compatible with multilang filter, no other html tags. Please note '<', or '>' are allowed here.
 | 
        
           |  |  | 241 |  */
 | 
        
           |  |  | 242 | define('PARAM_TEXT', \core\param::TEXT->value);
 | 
        
           |  |  | 243 |   | 
        
           |  |  | 244 | /**
 | 
        
           |  |  | 245 |  * PARAM_THEME - Checks to see if the string is a valid theme name in the current site
 | 
        
           |  |  | 246 |  */
 | 
        
           |  |  | 247 | define('PARAM_THEME', \core\param::THEME->value);
 | 
        
           |  |  | 248 |   | 
        
           |  |  | 249 | /**
 | 
        
           |  |  | 250 |  * PARAM_URL - expected properly formatted URL. Please note that domain part is required, http://localhost/ is not accepted but
 | 
        
           |  |  | 251 |  * http://localhost.localdomain/ is ok.
 | 
        
           |  |  | 252 |  */
 | 
        
           |  |  | 253 | define('PARAM_URL', \core\param::URL->value);
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 | /**
 | 
        
           |  |  | 256 |  * PARAM_USERNAME - Clean username to only contains allowed characters. This is to be used ONLY when manually creating user
 | 
        
           |  |  | 257 |  * accounts, do NOT use when syncing with external systems!!
 | 
        
           |  |  | 258 |  */
 | 
        
           |  |  | 259 | define('PARAM_USERNAME', \core\param::USERNAME->value);
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 | /**
 | 
        
           |  |  | 262 |  * PARAM_STRINGID - used to check if the given string is valid string identifier for get_string()
 | 
        
           |  |  | 263 |  */
 | 
        
           |  |  | 264 | define('PARAM_STRINGID', \core\param::STRINGID->value);
 | 
        
           |  |  | 265 |   | 
        
           |  |  | 266 | // DEPRECATED PARAM TYPES OR ALIASES - DO NOT USE FOR NEW CODE.
 | 
        
           |  |  | 267 | /**
 | 
        
           |  |  | 268 |  * PARAM_CLEAN - obsoleted, please use a more specific type of parameter.
 | 
        
           |  |  | 269 |  * It was one of the first types, that is why it is abused so much ;-)
 | 
        
           |  |  | 270 |  * @deprecated since 2.0
 | 
        
           |  |  | 271 |  */
 | 
        
           |  |  | 272 | define('PARAM_CLEAN', \core\param::CLEAN->value);
 | 
        
           |  |  | 273 |   | 
        
           |  |  | 274 | /**
 | 
        
           |  |  | 275 |  * PARAM_INTEGER - deprecated alias for PARAM_INT
 | 
        
           |  |  | 276 |  * @deprecated since 2.0
 | 
        
           |  |  | 277 |  */
 | 
        
           |  |  | 278 | define('PARAM_INTEGER', \core\param::INT->value);
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 | /**
 | 
        
           |  |  | 281 |  * PARAM_NUMBER - deprecated alias of PARAM_FLOAT
 | 
        
           |  |  | 282 |  * @deprecated since 2.0
 | 
        
           |  |  | 283 |  */
 | 
        
           |  |  | 284 | define('PARAM_NUMBER', \core\param::FLOAT->value);
 | 
        
           |  |  | 285 |   | 
        
           |  |  | 286 | /**
 | 
        
           |  |  | 287 |  * PARAM_ACTION - deprecated alias for PARAM_ALPHANUMEXT, use for various actions in forms and urls
 | 
        
           |  |  | 288 |  * NOTE: originally alias for PARAM_APLHA
 | 
        
           |  |  | 289 |  * @deprecated since 2.0
 | 
        
           |  |  | 290 |  */
 | 
        
           |  |  | 291 | define('PARAM_ACTION', \core\param::ALPHANUMEXT->value);
 | 
        
           |  |  | 292 |   | 
        
           |  |  | 293 | /**
 | 
        
           |  |  | 294 |  * PARAM_FORMAT - deprecated alias for PARAM_ALPHANUMEXT, use for names of plugins, formats, etc.
 | 
        
           |  |  | 295 |  * NOTE: originally alias for PARAM_APLHA
 | 
        
           |  |  | 296 |  * @deprecated since 2.0
 | 
        
           |  |  | 297 |  */
 | 
        
           |  |  | 298 | define('PARAM_FORMAT', \core\param::ALPHANUMEXT->value);
 | 
        
           |  |  | 299 |   | 
        
           |  |  | 300 | /**
 | 
        
           |  |  | 301 |  * PARAM_MULTILANG - deprecated alias of PARAM_TEXT.
 | 
        
           |  |  | 302 |  * @deprecated since 2.0
 | 
        
           |  |  | 303 |  */
 | 
        
           |  |  | 304 | define('PARAM_MULTILANG', \core\param::TEXT->value);
 | 
        
           |  |  | 305 |   | 
        
           |  |  | 306 | /**
 | 
        
           |  |  | 307 |  * PARAM_TIMEZONE - expected timezone. Timezone can be int +-(0-13) or float +-(0.5-12.5) or
 | 
        
           |  |  | 308 |  * string separated by '/' and can have '-' &/ '_' (eg. America/North_Dakota/New_Salem
 | 
        
           |  |  | 309 |  * America/Port-au-Prince)
 | 
        
           |  |  | 310 |  */
 | 
        
           |  |  | 311 | define('PARAM_TIMEZONE', \core\param::TIMEZONE->value);
 | 
        
           |  |  | 312 |   | 
        
           |  |  | 313 | /**
 | 
        
           |  |  | 314 |  * PARAM_CLEANFILE - deprecated alias of PARAM_FILE; originally was removing regional chars too
 | 
        
           |  |  | 315 |  * @deprecated since 2.0
 | 
        
           |  |  | 316 |  */
 | 
        
           |  |  | 317 | define('PARAM_CLEANFILE', \core\param::CLEANFILE->value);
 | 
        
           |  |  | 318 |   | 
        
           |  |  | 319 | /**
 | 
        
           |  |  | 320 |  * PARAM_COMPONENT is used for full component names (aka frankenstyle) such as 'mod_forum', 'core_rating', 'auth_ldap'.
 | 
        
           |  |  | 321 |  * Short legacy subsystem names and module names are accepted too ex: 'forum', 'rating', 'user'.
 | 
        
           |  |  | 322 |  * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 | 
        
           |  |  | 323 |  * NOTE: numbers and underscores are strongly discouraged in plugin names!
 | 
        
           |  |  | 324 |  */
 | 
        
           |  |  | 325 | define('PARAM_COMPONENT', \core\param::COMPONENT->value);
 | 
        
           |  |  | 326 |   | 
        
           |  |  | 327 | /**
 | 
        
           |  |  | 328 |  * PARAM_AREA is a name of area used when addressing files, comments, ratings, etc.
 | 
        
           |  |  | 329 |  * It is usually used together with context id and component.
 | 
        
           |  |  | 330 |  * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 | 
        
           |  |  | 331 |  */
 | 
        
           |  |  | 332 | define('PARAM_AREA', \core\param::AREA->value);
 | 
        
           |  |  | 333 |   | 
        
           |  |  | 334 | /**
 | 
        
           |  |  | 335 |  * PARAM_PLUGIN is used for plugin names such as 'forum', 'glossary', 'ldap', 'paypal', 'completionstatus'.
 | 
        
           |  |  | 336 |  * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter.
 | 
        
           |  |  | 337 |  * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names.
 | 
        
           |  |  | 338 |  */
 | 
        
           |  |  | 339 | define('PARAM_PLUGIN', \core\param::PLUGIN->value);
 | 
        
           |  |  | 340 |   | 
        
           |  |  | 341 |   | 
        
           |  |  | 342 | // Web Services.
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 | /**
 | 
        
           |  |  | 345 |  * VALUE_REQUIRED - if the parameter is not supplied, there is an error
 | 
        
           |  |  | 346 |  */
 | 
        
           |  |  | 347 | define('VALUE_REQUIRED', 1);
 | 
        
           |  |  | 348 |   | 
        
           |  |  | 349 | /**
 | 
        
           |  |  | 350 |  * VALUE_OPTIONAL - if the parameter is not supplied, then the param has no value
 | 
        
           |  |  | 351 |  */
 | 
        
           |  |  | 352 | define('VALUE_OPTIONAL', 2);
 | 
        
           |  |  | 353 |   | 
        
           |  |  | 354 | /**
 | 
        
           |  |  | 355 |  * VALUE_DEFAULT - if the parameter is not supplied, then the default value is used
 | 
        
           |  |  | 356 |  */
 | 
        
           |  |  | 357 | define('VALUE_DEFAULT', 0);
 | 
        
           |  |  | 358 |   | 
        
           |  |  | 359 | /**
 | 
        
           |  |  | 360 |  * NULL_NOT_ALLOWED - the parameter can not be set to null in the database
 | 
        
           |  |  | 361 |  */
 | 
        
           |  |  | 362 | define('NULL_NOT_ALLOWED', false);
 | 
        
           |  |  | 363 |   | 
        
           |  |  | 364 | /**
 | 
        
           |  |  | 365 |  * NULL_ALLOWED - the parameter can be set to null in the database
 | 
        
           |  |  | 366 |  */
 | 
        
           |  |  | 367 | define('NULL_ALLOWED', true);
 | 
        
           |  |  | 368 |   | 
        
           |  |  | 369 | // Page types.
 | 
        
           |  |  | 370 |   | 
        
           |  |  | 371 | /**
 | 
        
           |  |  | 372 |  * PAGE_COURSE_VIEW is a definition of a page type. For more information on the page class see moodle/lib/pagelib.php.
 | 
        
           |  |  | 373 |  */
 | 
        
           |  |  | 374 | define('PAGE_COURSE_VIEW', 'course-view');
 | 
        
           |  |  | 375 |   | 
        
           |  |  | 376 | /** Get remote addr constant */
 | 
        
           |  |  | 377 | define('GETREMOTEADDR_SKIP_HTTP_CLIENT_IP', '1');
 | 
        
           |  |  | 378 | /** Get remote addr constant */
 | 
        
           |  |  | 379 | define('GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR', '2');
 | 
        
           |  |  | 380 | /**
 | 
        
           |  |  | 381 |  * GETREMOTEADDR_SKIP_DEFAULT defines the default behavior remote IP address validation.
 | 
        
           |  |  | 382 |  */
 | 
        
           | 1326 | ariadna | 383 | define('GETREMOTEADDR_SKIP_DEFAULT', GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR | GETREMOTEADDR_SKIP_HTTP_CLIENT_IP);
 | 
        
           | 1 | efrain | 384 |   | 
        
           |  |  | 385 | // Blog access level constant declaration.
 | 
        
           | 1326 | ariadna | 386 | define('BLOG_USER_LEVEL', 1);
 | 
        
           |  |  | 387 | define('BLOG_GROUP_LEVEL', 2);
 | 
        
           |  |  | 388 | define('BLOG_COURSE_LEVEL', 3);
 | 
        
           |  |  | 389 | define('BLOG_SITE_LEVEL', 4);
 | 
        
           |  |  | 390 | define('BLOG_GLOBAL_LEVEL', 5);
 | 
        
           | 1 | efrain | 391 |   | 
        
           |  |  | 392 |   | 
        
           |  |  | 393 | // Tag constants.
 | 
        
           |  |  | 394 | /**
 | 
        
           |  |  | 395 |  * To prevent problems with multibytes strings,Flag updating in nav not working on the review page. this should not exceed the
 | 
        
           |  |  | 396 |  * length of "varchar(255) / 3 (bytes / utf-8 character) = 85".
 | 
        
           |  |  | 397 |  * TODO: this is not correct, varchar(255) are 255 unicode chars ;-)
 | 
        
           |  |  | 398 |  *
 | 
        
           |  |  | 399 |  * @todo define(TAG_MAX_LENGTH) this is not correct, varchar(255) are 255 unicode chars ;-)
 | 
        
           |  |  | 400 |  */
 | 
        
           |  |  | 401 | define('TAG_MAX_LENGTH', 50);
 | 
        
           |  |  | 402 |   | 
        
           |  |  | 403 | // Password policy constants.
 | 
        
           | 1326 | ariadna | 404 | define('PASSWORD_LOWER', 'abcdefghijklmnopqrstuvwxyz');
 | 
        
           |  |  | 405 | define('PASSWORD_UPPER', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
 | 
        
           |  |  | 406 | define('PASSWORD_DIGITS', '0123456789');
 | 
        
           |  |  | 407 | define('PASSWORD_NONALPHANUM', '.,;:!?_-+/*@#&$');
 | 
        
           | 1 | efrain | 408 |   | 
        
           |  |  | 409 | /**
 | 
        
           |  |  | 410 |  * Required password pepper entropy.
 | 
        
           |  |  | 411 |  */
 | 
        
           | 1326 | ariadna | 412 | define('PEPPER_ENTROPY', 112);
 | 
        
           | 1 | efrain | 413 |   | 
        
           |  |  | 414 | // Feature constants.
 | 
        
           |  |  | 415 | // Used for plugin_supports() to report features that are, or are not, supported by a module.
 | 
        
           |  |  | 416 |   | 
        
           |  |  | 417 | /** True if module can provide a grade */
 | 
        
           |  |  | 418 | define('FEATURE_GRADE_HAS_GRADE', 'grade_has_grade');
 | 
        
           |  |  | 419 | /** True if module supports outcomes */
 | 
        
           |  |  | 420 | define('FEATURE_GRADE_OUTCOMES', 'outcomes');
 | 
        
           |  |  | 421 | /** True if module supports advanced grading methods */
 | 
        
           |  |  | 422 | define('FEATURE_ADVANCED_GRADING', 'grade_advanced_grading');
 | 
        
           |  |  | 423 | /** True if module controls the grade visibility over the gradebook */
 | 
        
           |  |  | 424 | define('FEATURE_CONTROLS_GRADE_VISIBILITY', 'controlsgradevisbility');
 | 
        
           |  |  | 425 | /** True if module supports plagiarism plugins */
 | 
        
           |  |  | 426 | define('FEATURE_PLAGIARISM', 'plagiarism');
 | 
        
           |  |  | 427 |   | 
        
           |  |  | 428 | /** True if module has code to track whether somebody viewed it */
 | 
        
           |  |  | 429 | define('FEATURE_COMPLETION_TRACKS_VIEWS', 'completion_tracks_views');
 | 
        
           |  |  | 430 | /** True if module has custom completion rules */
 | 
        
           |  |  | 431 | define('FEATURE_COMPLETION_HAS_RULES', 'completion_has_rules');
 | 
        
           |  |  | 432 |   | 
        
           |  |  | 433 | /** True if module has no 'view' page (like label) */
 | 
        
           |  |  | 434 | define('FEATURE_NO_VIEW_LINK', 'viewlink');
 | 
        
           |  |  | 435 | /** True (which is default) if the module wants support for setting the ID number for grade calculation purposes. */
 | 
        
           |  |  | 436 | define('FEATURE_IDNUMBER', 'idnumber');
 | 
        
           |  |  | 437 | /** True if module supports groups */
 | 
        
           |  |  | 438 | define('FEATURE_GROUPS', 'groups');
 | 
        
           |  |  | 439 | /** True if module supports groupings */
 | 
        
           |  |  | 440 | define('FEATURE_GROUPINGS', 'groupings');
 | 
        
           |  |  | 441 | /**
 | 
        
           |  |  | 442 |  * True if module supports groupmembersonly (which no longer exists)
 | 
        
           |  |  | 443 |  * @deprecated Since Moodle 2.8
 | 
        
           |  |  | 444 |  */
 | 
        
           |  |  | 445 | define('FEATURE_GROUPMEMBERSONLY', 'groupmembersonly');
 | 
        
           |  |  | 446 |   | 
        
           |  |  | 447 | /** Type of module */
 | 
        
           |  |  | 448 | define('FEATURE_MOD_ARCHETYPE', 'mod_archetype');
 | 
        
           |  |  | 449 | /** True if module supports intro editor */
 | 
        
           |  |  | 450 | define('FEATURE_MOD_INTRO', 'mod_intro');
 | 
        
           |  |  | 451 | /** True if module has default completion */
 | 
        
           |  |  | 452 | define('FEATURE_MODEDIT_DEFAULT_COMPLETION', 'modedit_default_completion');
 | 
        
           |  |  | 453 |   | 
        
           |  |  | 454 | define('FEATURE_COMMENT', 'comment');
 | 
        
           |  |  | 455 |   | 
        
           |  |  | 456 | define('FEATURE_RATE', 'rate');
 | 
        
           |  |  | 457 | /** True if module supports backup/restore of moodle2 format */
 | 
        
           |  |  | 458 | define('FEATURE_BACKUP_MOODLE2', 'backup_moodle2');
 | 
        
           |  |  | 459 |   | 
        
           |  |  | 460 | /** True if module can show description on course main page */
 | 
        
           |  |  | 461 | define('FEATURE_SHOW_DESCRIPTION', 'showdescription');
 | 
        
           |  |  | 462 |   | 
        
           |  |  | 463 | /** True if module uses the question bank */
 | 
        
           |  |  | 464 | define('FEATURE_USES_QUESTIONS', 'usesquestions');
 | 
        
           |  |  | 465 |   | 
        
           |  |  | 466 | /**
 | 
        
           |  |  | 467 |  * Maximum filename char size
 | 
        
           |  |  | 468 |  */
 | 
        
           |  |  | 469 | define('MAX_FILENAME_SIZE', 100);
 | 
        
           |  |  | 470 |   | 
        
           |  |  | 471 | /** Unspecified module archetype */
 | 
        
           |  |  | 472 | define('MOD_ARCHETYPE_OTHER', 0);
 | 
        
           |  |  | 473 | /** Resource-like type module */
 | 
        
           |  |  | 474 | define('MOD_ARCHETYPE_RESOURCE', 1);
 | 
        
           |  |  | 475 | /** Assignment module archetype */
 | 
        
           |  |  | 476 | define('MOD_ARCHETYPE_ASSIGNMENT', 2);
 | 
        
           |  |  | 477 | /** System (not user-addable) module archetype */
 | 
        
           |  |  | 478 | define('MOD_ARCHETYPE_SYSTEM', 3);
 | 
        
           |  |  | 479 |   | 
        
           |  |  | 480 | /** Type of module */
 | 
        
           |  |  | 481 | define('FEATURE_MOD_PURPOSE', 'mod_purpose');
 | 
        
           |  |  | 482 | /** Module purpose administration */
 | 
        
           |  |  | 483 | define('MOD_PURPOSE_ADMINISTRATION', 'administration');
 | 
        
           |  |  | 484 | /** Module purpose assessment */
 | 
        
           |  |  | 485 | define('MOD_PURPOSE_ASSESSMENT', 'assessment');
 | 
        
           |  |  | 486 | /** Module purpose communication */
 | 
        
           |  |  | 487 | define('MOD_PURPOSE_COLLABORATION', 'collaboration');
 | 
        
           |  |  | 488 | /** Module purpose communication */
 | 
        
           |  |  | 489 | define('MOD_PURPOSE_COMMUNICATION', 'communication');
 | 
        
           |  |  | 490 | /** Module purpose content */
 | 
        
           |  |  | 491 | define('MOD_PURPOSE_CONTENT', 'content');
 | 
        
           |  |  | 492 | /** Module purpose interactive content */
 | 
        
           |  |  | 493 | define('MOD_PURPOSE_INTERACTIVECONTENT', 'interactivecontent');
 | 
        
           |  |  | 494 | /** Module purpose other */
 | 
        
           |  |  | 495 | define('MOD_PURPOSE_OTHER', 'other');
 | 
        
           |  |  | 496 | /**
 | 
        
           |  |  | 497 |  * Module purpose interface
 | 
        
           |  |  | 498 |  * @deprecated since Moodle 4.4
 | 
        
           |  |  | 499 |  * @todo MDL-80701 Remove in Moodle 4.8
 | 
        
           | 1326 | ariadna | 500 |  */
 | 
        
           | 1 | efrain | 501 | define('MOD_PURPOSE_INTERFACE', 'interface');
 | 
        
           |  |  | 502 |   | 
        
           |  |  | 503 | /**
 | 
        
           |  |  | 504 |  * Security token used for allowing access
 | 
        
           |  |  | 505 |  * from external application such as web services.
 | 
        
           |  |  | 506 |  * Scripts do not use any session, performance is relatively
 | 
        
           |  |  | 507 |  * low because we need to load access info in each request.
 | 
        
           |  |  | 508 |  * Scripts are executed in parallel.
 | 
        
           |  |  | 509 |  */
 | 
        
           |  |  | 510 | define('EXTERNAL_TOKEN_PERMANENT', 0);
 | 
        
           |  |  | 511 |   | 
        
           |  |  | 512 | /**
 | 
        
           |  |  | 513 |  * Security token used for allowing access
 | 
        
           |  |  | 514 |  * of embedded applications, the code is executed in the
 | 
        
           |  |  | 515 |  * active user session. Token is invalidated after user logs out.
 | 
        
           |  |  | 516 |  * Scripts are executed serially - normal session locking is used.
 | 
        
           |  |  | 517 |  */
 | 
        
           |  |  | 518 | define('EXTERNAL_TOKEN_EMBEDDED', 1);
 | 
        
           |  |  | 519 |   | 
        
           |  |  | 520 | /**
 | 
        
           |  |  | 521 |  * The home page should be the site home
 | 
        
           |  |  | 522 |  */
 | 
        
           |  |  | 523 | define('HOMEPAGE_SITE', 0);
 | 
        
           |  |  | 524 | /**
 | 
        
           |  |  | 525 |  * The home page should be the users my page
 | 
        
           |  |  | 526 |  */
 | 
        
           |  |  | 527 | define('HOMEPAGE_MY', 1);
 | 
        
           |  |  | 528 | /**
 | 
        
           |  |  | 529 |  * The home page can be chosen by the user
 | 
        
           |  |  | 530 |  */
 | 
        
           |  |  | 531 | define('HOMEPAGE_USER', 2);
 | 
        
           |  |  | 532 | /**
 | 
        
           |  |  | 533 |  * The home page should be the users my courses page
 | 
        
           |  |  | 534 |  */
 | 
        
           |  |  | 535 | define('HOMEPAGE_MYCOURSES', 3);
 | 
        
           |  |  | 536 |   | 
        
           |  |  | 537 | /**
 | 
        
           |  |  | 538 |  * URL of the Moodle sites registration portal.
 | 
        
           |  |  | 539 |  */
 | 
        
           |  |  | 540 | defined('HUB_MOODLEORGHUBURL') || define('HUB_MOODLEORGHUBURL', 'https://stats.moodle.org');
 | 
        
           |  |  | 541 |   | 
        
           |  |  | 542 | /**
 | 
        
           |  |  | 543 |  * URL of main Moodle site for marketing, products and services.
 | 
        
           |  |  | 544 |  */
 | 
        
           |  |  | 545 | defined('MOODLE_PRODUCTURL') || define('MOODLE_PRODUCTURL', 'https://moodle.com');
 | 
        
           |  |  | 546 |   | 
        
           |  |  | 547 | /**
 | 
        
           |  |  | 548 |  * URL of the statistic server public key.
 | 
        
           |  |  | 549 |  */
 | 
        
           |  |  | 550 | defined('HUB_STATSPUBLICKEY') || define('HUB_STATSPUBLICKEY', 'https://moodle.org/static/statspubkey.pem');
 | 
        
           |  |  | 551 |   | 
        
           |  |  | 552 | /**
 | 
        
           |  |  | 553 |  * Moodle mobile app service name
 | 
        
           |  |  | 554 |  */
 | 
        
           |  |  | 555 | define('MOODLE_OFFICIAL_MOBILE_SERVICE', 'moodle_mobile_app');
 | 
        
           |  |  | 556 |   | 
        
           |  |  | 557 | /**
 | 
        
           |  |  | 558 |  * Indicates the user has the capabilities required to ignore activity and course file size restrictions
 | 
        
           |  |  | 559 |  */
 | 
        
           |  |  | 560 | define('USER_CAN_IGNORE_FILE_SIZE_LIMITS', -1);
 | 
        
           |  |  | 561 |   | 
        
           |  |  | 562 | /**
 | 
        
           |  |  | 563 |  * Course display settings: display all sections on one page.
 | 
        
           |  |  | 564 |  */
 | 
        
           |  |  | 565 | define('COURSE_DISPLAY_SINGLEPAGE', 0);
 | 
        
           |  |  | 566 | /**
 | 
        
           |  |  | 567 |  * Course display settings: split pages into a page per section.
 | 
        
           |  |  | 568 |  */
 | 
        
           |  |  | 569 | define('COURSE_DISPLAY_MULTIPAGE', 1);
 | 
        
           |  |  | 570 |   | 
        
           |  |  | 571 | /**
 | 
        
           |  |  | 572 |  * Authentication constant: String used in password field when password is not stored.
 | 
        
           |  |  | 573 |  */
 | 
        
           |  |  | 574 | define('AUTH_PASSWORD_NOT_CACHED', 'not cached');
 | 
        
           |  |  | 575 |   | 
        
           |  |  | 576 | /**
 | 
        
           |  |  | 577 |  * Email from header to never include via information.
 | 
        
           |  |  | 578 |  */
 | 
        
           |  |  | 579 | define('EMAIL_VIA_NEVER', 0);
 | 
        
           |  |  | 580 |   | 
        
           |  |  | 581 | /**
 | 
        
           |  |  | 582 |  * Email from header to always include via information.
 | 
        
           |  |  | 583 |  */
 | 
        
           |  |  | 584 | define('EMAIL_VIA_ALWAYS', 1);
 | 
        
           |  |  | 585 |   | 
        
           |  |  | 586 | /**
 | 
        
           |  |  | 587 |  * Email from header to only include via information if the address is no-reply.
 | 
        
           |  |  | 588 |  */
 | 
        
           |  |  | 589 | define('EMAIL_VIA_NO_REPLY_ONLY', 2);
 | 
        
           |  |  | 590 |   | 
        
           |  |  | 591 | /**
 | 
        
           |  |  | 592 |  * Contact site support form/link disabled.
 | 
        
           |  |  | 593 |  */
 | 
        
           |  |  | 594 | define('CONTACT_SUPPORT_DISABLED', 0);
 | 
        
           |  |  | 595 |   | 
        
           |  |  | 596 | /**
 | 
        
           |  |  | 597 |  * Contact site support form/link only available to authenticated users.
 | 
        
           |  |  | 598 |  */
 | 
        
           |  |  | 599 | define('CONTACT_SUPPORT_AUTHENTICATED', 1);
 | 
        
           |  |  | 600 |   | 
        
           |  |  | 601 | /**
 | 
        
           |  |  | 602 |  * Contact site support form/link available to anyone visiting the site.
 | 
        
           |  |  | 603 |  */
 | 
        
           |  |  | 604 | define('CONTACT_SUPPORT_ANYONE', 2);
 | 
        
           |  |  | 605 |   | 
        
           |  |  | 606 | /**
 | 
        
           |  |  | 607 |  * Maximum number of characters for password.
 | 
        
           |  |  | 608 |  */
 | 
        
           |  |  | 609 | define('MAX_PASSWORD_CHARACTERS', 128);
 | 
        
           |  |  | 610 |   | 
        
           |  |  | 611 | /**
 | 
        
           |  |  | 612 |  * Toggle sensitive feature is disabled. Used for sensitive inputs (passwords, tokens, keys).
 | 
        
           |  |  | 613 |  */
 | 
        
           |  |  | 614 | define('TOGGLE_SENSITIVE_DISABLED', 0);
 | 
        
           |  |  | 615 |   | 
        
           |  |  | 616 | /**
 | 
        
           |  |  | 617 |  * Toggle sensitive feature is enabled. Used for sensitive inputs (passwords, tokens, keys).
 | 
        
           |  |  | 618 |  */
 | 
        
           |  |  | 619 | define('TOGGLE_SENSITIVE_ENABLED', 1);
 | 
        
           |  |  | 620 |   | 
        
           |  |  | 621 | /**
 | 
        
           |  |  | 622 |  * Toggle sensitive feature is enabled for small screens only. Used for sensitive inputs (passwords, tokens, keys).
 | 
        
           |  |  | 623 |  */
 | 
        
           |  |  | 624 | define('TOGGLE_SENSITIVE_SMALL_SCREENS_ONLY', 2);
 | 
        
           |  |  | 625 |   | 
        
           |  |  | 626 | // PARAMETER HANDLING.
 | 
        
           |  |  | 627 |   | 
        
           |  |  | 628 | /**
 | 
        
           |  |  | 629 |  * Returns a particular value for the named variable, taken from
 | 
        
           |  |  | 630 |  * POST or GET.  If the parameter doesn't exist then an error is
 | 
        
           |  |  | 631 |  * thrown because we require this variable.
 | 
        
           |  |  | 632 |  *
 | 
        
           |  |  | 633 |  * This function should be used to initialise all required values
 | 
        
           |  |  | 634 |  * in a script that are based on parameters.  Usually it will be
 | 
        
           |  |  | 635 |  * used like this:
 | 
        
           |  |  | 636 |  *    $id = required_param('id', PARAM_INT);
 | 
        
           |  |  | 637 |  *
 | 
        
           |  |  | 638 |  * Please note the $type parameter is now required and the value can not be array.
 | 
        
           |  |  | 639 |  *
 | 
        
           |  |  | 640 |  * @param string $parname the name of the page parameter we want
 | 
        
           |  |  | 641 |  * @param string $type expected type of parameter
 | 
        
           |  |  | 642 |  * @return mixed
 | 
        
           |  |  | 643 |  * @throws coding_exception
 | 
        
           |  |  | 644 |  */
 | 
        
           | 1326 | ariadna | 645 | function required_param($parname, $type)
 | 
        
           |  |  | 646 | {
 | 
        
           | 1 | efrain | 647 |     return \core\param::from_type($type)->required_param($parname);
 | 
        
           |  |  | 648 | }
 | 
        
           |  |  | 649 |   | 
        
           |  |  | 650 | /**
 | 
        
           |  |  | 651 |  * Returns a particular array value for the named variable, taken from
 | 
        
           |  |  | 652 |  * POST or GET.  If the parameter doesn't exist then an error is
 | 
        
           |  |  | 653 |  * thrown because we require this variable.
 | 
        
           |  |  | 654 |  *
 | 
        
           |  |  | 655 |  * This function should be used to initialise all required values
 | 
        
           |  |  | 656 |  * in a script that are based on parameters.  Usually it will be
 | 
        
           |  |  | 657 |  * used like this:
 | 
        
           |  |  | 658 |  *    $ids = required_param_array('ids', PARAM_INT);
 | 
        
           |  |  | 659 |  *
 | 
        
           |  |  | 660 |  *  Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
 | 
        
           |  |  | 661 |  *
 | 
        
           |  |  | 662 |  * @param string $parname the name of the page parameter we want
 | 
        
           |  |  | 663 |  * @param string $type expected type of parameter
 | 
        
           |  |  | 664 |  * @return array
 | 
        
           |  |  | 665 |  * @throws coding_exception
 | 
        
           |  |  | 666 |  */
 | 
        
           | 1326 | ariadna | 667 | function required_param_array($parname, $type)
 | 
        
           |  |  | 668 | {
 | 
        
           | 1 | efrain | 669 |     return \core\param::from_type($type)->required_param_array($parname);
 | 
        
           |  |  | 670 | }
 | 
        
           |  |  | 671 |   | 
        
           |  |  | 672 | /**
 | 
        
           |  |  | 673 |  * Returns a particular value for the named variable, taken from
 | 
        
           |  |  | 674 |  * POST or GET, otherwise returning a given default.
 | 
        
           |  |  | 675 |  *
 | 
        
           |  |  | 676 |  * This function should be used to initialise all optional values
 | 
        
           |  |  | 677 |  * in a script that are based on parameters.  Usually it will be
 | 
        
           |  |  | 678 |  * used like this:
 | 
        
           |  |  | 679 |  *    $name = optional_param('name', 'Fred', PARAM_TEXT);
 | 
        
           |  |  | 680 |  *
 | 
        
           |  |  | 681 |  * Please note the $type parameter is now required and the value can not be array.
 | 
        
           |  |  | 682 |  *
 | 
        
           |  |  | 683 |  * @param string $parname the name of the page parameter we want
 | 
        
           |  |  | 684 |  * @param mixed  $default the default value to return if nothing is found
 | 
        
           |  |  | 685 |  * @param string $type expected type of parameter
 | 
        
           |  |  | 686 |  * @return mixed
 | 
        
           |  |  | 687 |  * @throws coding_exception
 | 
        
           |  |  | 688 |  */
 | 
        
           | 1326 | ariadna | 689 | function optional_param($parname, $default, $type)
 | 
        
           |  |  | 690 | {
 | 
        
           | 1 | efrain | 691 |     return \core\param::from_type($type)->optional_param(
 | 
        
           |  |  | 692 |         paramname: $parname,
 | 
        
           |  |  | 693 |         default: $default,
 | 
        
           |  |  | 694 |     );
 | 
        
           |  |  | 695 | }
 | 
        
           |  |  | 696 |   | 
        
           |  |  | 697 | /**
 | 
        
           |  |  | 698 |  * Returns a particular array value for the named variable, taken from
 | 
        
           |  |  | 699 |  * POST or GET, otherwise returning a given default.
 | 
        
           |  |  | 700 |  *
 | 
        
           |  |  | 701 |  * This function should be used to initialise all optional values
 | 
        
           |  |  | 702 |  * in a script that are based on parameters.  Usually it will be
 | 
        
           |  |  | 703 |  * used like this:
 | 
        
           |  |  | 704 |  *    $ids = optional_param('id', array(), PARAM_INT);
 | 
        
           |  |  | 705 |  *
 | 
        
           |  |  | 706 |  * Note: arrays of arrays are not supported, only alphanumeric keys with _ and - are supported
 | 
        
           |  |  | 707 |  *
 | 
        
           |  |  | 708 |  * @param string $parname the name of the page parameter we want
 | 
        
           |  |  | 709 |  * @param mixed $default the default value to return if nothing is found
 | 
        
           |  |  | 710 |  * @param string $type expected type of parameter
 | 
        
           |  |  | 711 |  * @return array
 | 
        
           |  |  | 712 |  * @throws coding_exception
 | 
        
           |  |  | 713 |  */
 | 
        
           | 1326 | ariadna | 714 | function optional_param_array($parname, $default, $type)
 | 
        
           |  |  | 715 | {
 | 
        
           | 1 | efrain | 716 |     return \core\param::from_type($type)->optional_param_array(
 | 
        
           |  |  | 717 |         paramname: $parname,
 | 
        
           |  |  | 718 |         default: $default,
 | 
        
           |  |  | 719 |     );
 | 
        
           |  |  | 720 | }
 | 
        
           |  |  | 721 |   | 
        
           |  |  | 722 | /**
 | 
        
           |  |  | 723 |  * Strict validation of parameter values, the values are only converted
 | 
        
           |  |  | 724 |  * to requested PHP type. Internally it is using clean_param, the values
 | 
        
           |  |  | 725 |  * before and after cleaning must be equal - otherwise
 | 
        
           |  |  | 726 |  * an invalid_parameter_exception is thrown.
 | 
        
           |  |  | 727 |  * Objects and classes are not accepted.
 | 
        
           |  |  | 728 |  *
 | 
        
           |  |  | 729 |  * @param mixed $param
 | 
        
           |  |  | 730 |  * @param string $type PARAM_ constant
 | 
        
           |  |  | 731 |  * @param bool $allownull are nulls valid value?
 | 
        
           |  |  | 732 |  * @param string $debuginfo optional debug information
 | 
        
           |  |  | 733 |  * @return mixed the $param value converted to PHP type
 | 
        
           |  |  | 734 |  * @throws invalid_parameter_exception if $param is not of given type
 | 
        
           |  |  | 735 |  */
 | 
        
           | 1326 | ariadna | 736 | function validate_param($param, $type, $allownull = NULL_NOT_ALLOWED, $debuginfo = '')
 | 
        
           |  |  | 737 | {
 | 
        
           | 1 | efrain | 738 |     return \core\param::from_type($type)->validate_param(
 | 
        
           |  |  | 739 |         param: $param,
 | 
        
           |  |  | 740 |         allownull: $allownull,
 | 
        
           |  |  | 741 |         debuginfo: $debuginfo,
 | 
        
           |  |  | 742 |     );
 | 
        
           |  |  | 743 | }
 | 
        
           |  |  | 744 |   | 
        
           |  |  | 745 | /**
 | 
        
           |  |  | 746 |  * Makes sure array contains only the allowed types, this function does not validate array key names!
 | 
        
           |  |  | 747 |  *
 | 
        
           |  |  | 748 |  * <code>
 | 
        
           |  |  | 749 |  * $options = clean_param($options, PARAM_INT);
 | 
        
           |  |  | 750 |  * </code>
 | 
        
           |  |  | 751 |  *
 | 
        
           |  |  | 752 |  * @param array|null $param the variable array we are cleaning
 | 
        
           |  |  | 753 |  * @param string $type expected format of param after cleaning.
 | 
        
           |  |  | 754 |  * @param bool $recursive clean recursive arrays
 | 
        
           |  |  | 755 |  * @return array
 | 
        
           |  |  | 756 |  * @throws coding_exception
 | 
        
           |  |  | 757 |  */
 | 
        
           | 1326 | ariadna | 758 | function clean_param_array(?array $param, $type, $recursive = false)
 | 
        
           |  |  | 759 | {
 | 
        
           | 1 | efrain | 760 |     return \core\param::from_type($type)->clean_param_array(
 | 
        
           |  |  | 761 |         param: $param,
 | 
        
           |  |  | 762 |         recursive: $recursive,
 | 
        
           |  |  | 763 |     );
 | 
        
           |  |  | 764 | }
 | 
        
           |  |  | 765 |   | 
        
           |  |  | 766 | /**
 | 
        
           |  |  | 767 |  * Used by {@link optional_param()} and {@link required_param()} to
 | 
        
           |  |  | 768 |  * clean the variables and/or cast to specific types, based on
 | 
        
           |  |  | 769 |  * an options field.
 | 
        
           |  |  | 770 |  * <code>
 | 
        
           |  |  | 771 |  * $course->format = clean_param($course->format, PARAM_ALPHA);
 | 
        
           |  |  | 772 |  * $selectedgradeitem = clean_param($selectedgradeitem, PARAM_INT);
 | 
        
           |  |  | 773 |  * </code>
 | 
        
           |  |  | 774 |  *
 | 
        
           |  |  | 775 |  * @param mixed $param the variable we are cleaning
 | 
        
           |  |  | 776 |  * @param string $type expected format of param after cleaning.
 | 
        
           |  |  | 777 |  * @return mixed
 | 
        
           |  |  | 778 |  * @throws coding_exception
 | 
        
           |  |  | 779 |  */
 | 
        
           | 1326 | ariadna | 780 | function clean_param($param, $type)
 | 
        
           |  |  | 781 | {
 | 
        
           | 1 | efrain | 782 |     return \core\param::from_type($type)->clean($param);
 | 
        
           |  |  | 783 | }
 | 
        
           |  |  | 784 |   | 
        
           |  |  | 785 | /**
 | 
        
           |  |  | 786 |  * Whether the PARAM_* type is compatible in RTL.
 | 
        
           |  |  | 787 |  *
 | 
        
           |  |  | 788 |  * Being compatible with RTL means that the data they contain can flow
 | 
        
           |  |  | 789 |  * from right-to-left or left-to-right without compromising the user experience.
 | 
        
           |  |  | 790 |  *
 | 
        
           |  |  | 791 |  * Take URLs for example, they are not RTL compatible as they should always
 | 
        
           |  |  | 792 |  * flow from the left to the right. This also applies to numbers, email addresses,
 | 
        
           |  |  | 793 |  * configuration snippets, base64 strings, etc...
 | 
        
           |  |  | 794 |  *
 | 
        
           |  |  | 795 |  * This function tries to best guess which parameters can contain localised strings.
 | 
        
           |  |  | 796 |  *
 | 
        
           |  |  | 797 |  * @param string $paramtype Constant PARAM_*.
 | 
        
           |  |  | 798 |  * @return bool
 | 
        
           |  |  | 799 |  */
 | 
        
           | 1326 | ariadna | 800 | function is_rtl_compatible($paramtype)
 | 
        
           |  |  | 801 | {
 | 
        
           | 1 | efrain | 802 |     return $paramtype == PARAM_TEXT || $paramtype == PARAM_NOTAGS;
 | 
        
           |  |  | 803 | }
 | 
        
           |  |  | 804 |   | 
        
           |  |  | 805 | /**
 | 
        
           |  |  | 806 |  * Makes sure the data is using valid utf8, invalid characters are discarded.
 | 
        
           |  |  | 807 |  *
 | 
        
           |  |  | 808 |  * Note: this function is not intended for full objects with methods and private properties.
 | 
        
           |  |  | 809 |  *
 | 
        
           |  |  | 810 |  * @param mixed $value
 | 
        
           |  |  | 811 |  * @return mixed with proper utf-8 encoding
 | 
        
           |  |  | 812 |  */
 | 
        
           | 1326 | ariadna | 813 | function fix_utf8($value)
 | 
        
           |  |  | 814 | {
 | 
        
           | 1 | efrain | 815 |     if (is_null($value) or $value === '') {
 | 
        
           |  |  | 816 |         return $value;
 | 
        
           |  |  | 817 |     } else if (is_string($value)) {
 | 
        
           |  |  | 818 |         if ((string)(int)$value === $value) {
 | 
        
           |  |  | 819 |             // Shortcut.
 | 
        
           |  |  | 820 |             return $value;
 | 
        
           |  |  | 821 |         }
 | 
        
           |  |  | 822 |   | 
        
           |  |  | 823 |         // Remove null bytes or invalid Unicode sequences from value.
 | 
        
           |  |  | 824 |         $value = str_replace(["\0", "\xef\xbf\xbe", "\xef\xbf\xbf"], '', $value);
 | 
        
           |  |  | 825 |   | 
        
           |  |  | 826 |         // Note: this duplicates min_fix_utf8() intentionally.
 | 
        
           |  |  | 827 |         static $buggyiconv = null;
 | 
        
           |  |  | 828 |         if ($buggyiconv === null) {
 | 
        
           | 1326 | ariadna | 829 |             $buggyiconv = (!function_exists('iconv') or @iconv('UTF-8', 'UTF-8//IGNORE', '100' . chr(130) . '€') !== '100€');
 | 
        
           | 1 | efrain | 830 |         }
 | 
        
           |  |  | 831 |   | 
        
           |  |  | 832 |         if ($buggyiconv) {
 | 
        
           |  |  | 833 |             if (function_exists('mb_convert_encoding')) {
 | 
        
           |  |  | 834 |                 $subst = mb_substitute_character();
 | 
        
           |  |  | 835 |                 mb_substitute_character('none');
 | 
        
           |  |  | 836 |                 $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
 | 
        
           |  |  | 837 |                 mb_substitute_character($subst);
 | 
        
           |  |  | 838 |             } else {
 | 
        
           |  |  | 839 |                 // Warn admins on admin/index.php page.
 | 
        
           |  |  | 840 |                 $result = $value;
 | 
        
           |  |  | 841 |             }
 | 
        
           |  |  | 842 |         } else {
 | 
        
           |  |  | 843 |             $result = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
 | 
        
           |  |  | 844 |         }
 | 
        
           |  |  | 845 |   | 
        
           |  |  | 846 |         return $result;
 | 
        
           |  |  | 847 |     } else if (is_array($value)) {
 | 
        
           |  |  | 848 |         foreach ($value as $k => $v) {
 | 
        
           |  |  | 849 |             $value[$k] = fix_utf8($v);
 | 
        
           |  |  | 850 |         }
 | 
        
           |  |  | 851 |         return $value;
 | 
        
           |  |  | 852 |     } else if (is_object($value)) {
 | 
        
           |  |  | 853 |         // Do not modify original.
 | 
        
           | 1326 | ariadna | 854 |         $value = clone ($value);
 | 
        
           | 1 | efrain | 855 |         foreach ($value as $k => $v) {
 | 
        
           |  |  | 856 |             $value->$k = fix_utf8($v);
 | 
        
           |  |  | 857 |         }
 | 
        
           |  |  | 858 |         return $value;
 | 
        
           |  |  | 859 |     } else {
 | 
        
           |  |  | 860 |         // This is some other type, no utf-8 here.
 | 
        
           |  |  | 861 |         return $value;
 | 
        
           |  |  | 862 |     }
 | 
        
           |  |  | 863 | }
 | 
        
           |  |  | 864 |   | 
        
           |  |  | 865 | /**
 | 
        
           |  |  | 866 |  * Return true if given value is integer or string with integer value
 | 
        
           |  |  | 867 |  *
 | 
        
           |  |  | 868 |  * @param mixed $value String or Int
 | 
        
           |  |  | 869 |  * @return bool true if number, false if not
 | 
        
           |  |  | 870 |  */
 | 
        
           | 1326 | ariadna | 871 | function is_number($value)
 | 
        
           |  |  | 872 | {
 | 
        
           | 1 | efrain | 873 |     if (is_int($value)) {
 | 
        
           |  |  | 874 |         return true;
 | 
        
           |  |  | 875 |     } else if (is_string($value)) {
 | 
        
           |  |  | 876 |         return ((string)(int)$value) === $value;
 | 
        
           |  |  | 877 |     } else {
 | 
        
           |  |  | 878 |         return false;
 | 
        
           |  |  | 879 |     }
 | 
        
           |  |  | 880 | }
 | 
        
           |  |  | 881 |   | 
        
           |  |  | 882 | /**
 | 
        
           |  |  | 883 |  * Returns host part from url.
 | 
        
           |  |  | 884 |  *
 | 
        
           |  |  | 885 |  * @param string $url full url
 | 
        
           |  |  | 886 |  * @return string host, null if not found
 | 
        
           |  |  | 887 |  */
 | 
        
           | 1326 | ariadna | 888 | function get_host_from_url($url)
 | 
        
           |  |  | 889 | {
 | 
        
           | 1 | efrain | 890 |     preg_match('|^[a-z]+://([a-zA-Z0-9-.]+)|i', $url, $matches);
 | 
        
           |  |  | 891 |     if ($matches) {
 | 
        
           |  |  | 892 |         return $matches[1];
 | 
        
           |  |  | 893 |     }
 | 
        
           |  |  | 894 |     return null;
 | 
        
           |  |  | 895 | }
 | 
        
           |  |  | 896 |   | 
        
           |  |  | 897 | /**
 | 
        
           |  |  | 898 |  * Tests whether anything was returned by text editor
 | 
        
           |  |  | 899 |  *
 | 
        
           |  |  | 900 |  * This function is useful for testing whether something you got back from
 | 
        
           |  |  | 901 |  * the HTML editor actually contains anything. Sometimes the HTML editor
 | 
        
           |  |  | 902 |  * appear to be empty, but actually you get back a <br> tag or something.
 | 
        
           |  |  | 903 |  *
 | 
        
           |  |  | 904 |  * @param string $string a string containing HTML.
 | 
        
           |  |  | 905 |  * @return boolean does the string contain any actual content - that is text,
 | 
        
           |  |  | 906 |  * images, objects, etc.
 | 
        
           |  |  | 907 |  */
 | 
        
           | 1326 | ariadna | 908 | function html_is_blank($string)
 | 
        
           |  |  | 909 | {
 | 
        
           | 1 | efrain | 910 |     return trim(strip_tags((string)$string, '<img><object><applet><input><select><textarea><hr>')) == '';
 | 
        
           |  |  | 911 | }
 | 
        
           |  |  | 912 |   | 
        
           |  |  | 913 | /**
 | 
        
           |  |  | 914 |  * Set a key in global configuration
 | 
        
           |  |  | 915 |  *
 | 
        
           |  |  | 916 |  * Set a key/value pair in both this session's {@link $CFG} global variable
 | 
        
           |  |  | 917 |  * and in the 'config' database table for future sessions.
 | 
        
           |  |  | 918 |  *
 | 
        
           |  |  | 919 |  * Can also be used to update keys for plugin-scoped configs in config_plugin table.
 | 
        
           |  |  | 920 |  * In that case it doesn't affect $CFG.
 | 
        
           |  |  | 921 |  *
 | 
        
           |  |  | 922 |  * A NULL value will delete the entry.
 | 
        
           |  |  | 923 |  *
 | 
        
           |  |  | 924 |  * NOTE: this function is called from lib/db/upgrade.php
 | 
        
           |  |  | 925 |  *
 | 
        
           |  |  | 926 |  * @param string $name the key to set
 | 
        
           |  |  | 927 |  * @param string|int|bool|null $value the value to set (without magic quotes),
 | 
        
           |  |  | 928 |  *               null to unset the value
 | 
        
           |  |  | 929 |  * @param string $plugin (optional) the plugin scope, default null
 | 
        
           |  |  | 930 |  * @return bool true or exception
 | 
        
           |  |  | 931 |  */
 | 
        
           | 1326 | ariadna | 932 | function set_config($name, $value, $plugin = null)
 | 
        
           |  |  | 933 | {
 | 
        
           | 1 | efrain | 934 |     global $CFG, $DB;
 | 
        
           |  |  | 935 |   | 
        
           |  |  | 936 |     // Redirect to appropriate handler when value is null.
 | 
        
           |  |  | 937 |     if ($value === null) {
 | 
        
           |  |  | 938 |         return unset_config($name, $plugin);
 | 
        
           |  |  | 939 |     }
 | 
        
           |  |  | 940 |   | 
        
           |  |  | 941 |     // Set variables determining conditions and where to store the new config.
 | 
        
           |  |  | 942 |     // Plugin config goes to {config_plugins}, core config goes to {config}.
 | 
        
           |  |  | 943 |     $iscore = empty($plugin);
 | 
        
           |  |  | 944 |     if ($iscore) {
 | 
        
           |  |  | 945 |         // If it's for core config.
 | 
        
           |  |  | 946 |         $table = 'config';
 | 
        
           |  |  | 947 |         $conditions = ['name' => $name];
 | 
        
           |  |  | 948 |         $invalidatecachekey = 'core';
 | 
        
           |  |  | 949 |     } else {
 | 
        
           |  |  | 950 |         // If it's a plugin.
 | 
        
           |  |  | 951 |         $table = 'config_plugins';
 | 
        
           |  |  | 952 |         $conditions = ['name' => $name, 'plugin' => $plugin];
 | 
        
           |  |  | 953 |         $invalidatecachekey = $plugin;
 | 
        
           |  |  | 954 |     }
 | 
        
           |  |  | 955 |   | 
        
           |  |  | 956 |     // DB handling - checks for existing config, updating or inserting only if necessary.
 | 
        
           |  |  | 957 |     $invalidatecache = true;
 | 
        
           |  |  | 958 |     $inserted = false;
 | 
        
           |  |  | 959 |     $record = $DB->get_record($table, $conditions, 'id, value');
 | 
        
           |  |  | 960 |     if ($record === false) {
 | 
        
           |  |  | 961 |         // Inserts a new config record.
 | 
        
           |  |  | 962 |         $config = new stdClass();
 | 
        
           |  |  | 963 |         $config->name  = $name;
 | 
        
           |  |  | 964 |         $config->value = $value;
 | 
        
           |  |  | 965 |         if (!$iscore) {
 | 
        
           |  |  | 966 |             $config->plugin = $plugin;
 | 
        
           |  |  | 967 |         }
 | 
        
           |  |  | 968 |         $inserted = $DB->insert_record($table, $config, false);
 | 
        
           |  |  | 969 |     } else if ($invalidatecache = ($record->value !== $value)) {
 | 
        
           |  |  | 970 |         // Record exists - Check and only set new value if it has changed.
 | 
        
           |  |  | 971 |         $DB->set_field($table, 'value', $value, ['id' => $record->id]);
 | 
        
           |  |  | 972 |     }
 | 
        
           |  |  | 973 |   | 
        
           |  |  | 974 |     if ($iscore && !isset($CFG->config_php_settings[$name])) {
 | 
        
           |  |  | 975 |         // So it's defined for this invocation at least.
 | 
        
           |  |  | 976 |         // Settings from db are always strings.
 | 
        
           |  |  | 977 |         $CFG->$name = (string) $value;
 | 
        
           |  |  | 978 |     }
 | 
        
           |  |  | 979 |   | 
        
           |  |  | 980 |     // When setting config during a Behat test (in the CLI script, not in the web browser
 | 
        
           |  |  | 981 |     // requests), remember which ones are set so that we can clear them later.
 | 
        
           |  |  | 982 |     if ($iscore && $inserted && defined('BEHAT_TEST')) {
 | 
        
           |  |  | 983 |         $CFG->behat_cli_added_config[$name] = true;
 | 
        
           |  |  | 984 |     }
 | 
        
           |  |  | 985 |   | 
        
           |  |  | 986 |     // Update siteidentifier cache, if required.
 | 
        
           |  |  | 987 |     if ($iscore && $name === 'siteidentifier') {
 | 
        
           |  |  | 988 |         cache_helper::update_site_identifier($value);
 | 
        
           |  |  | 989 |     }
 | 
        
           |  |  | 990 |   | 
        
           |  |  | 991 |     // Invalidate cache, if required.
 | 
        
           |  |  | 992 |     if ($invalidatecache) {
 | 
        
           |  |  | 993 |         cache_helper::invalidate_by_definition('core', 'config', [], $invalidatecachekey);
 | 
        
           |  |  | 994 |     }
 | 
        
           |  |  | 995 |   | 
        
           |  |  | 996 |     return true;
 | 
        
           |  |  | 997 | }
 | 
        
           |  |  | 998 |   | 
        
           |  |  | 999 | /**
 | 
        
           |  |  | 1000 |  * Get configuration values from the global config table
 | 
        
           |  |  | 1001 |  * or the config_plugins table.
 | 
        
           |  |  | 1002 |  *
 | 
        
           |  |  | 1003 |  * If called with one parameter, it will load all the config
 | 
        
           |  |  | 1004 |  * variables for one plugin, and return them as an object.
 | 
        
           |  |  | 1005 |  *
 | 
        
           |  |  | 1006 |  * If called with 2 parameters it will return a string single
 | 
        
           |  |  | 1007 |  * value or false if the value is not found.
 | 
        
           |  |  | 1008 |  *
 | 
        
           |  |  | 1009 |  * NOTE: this function is called from lib/db/upgrade.php
 | 
        
           |  |  | 1010 |  *
 | 
        
           |  |  | 1011 |  * @param string $plugin full component name
 | 
        
           |  |  | 1012 |  * @param string $name default null
 | 
        
           |  |  | 1013 |  * @return mixed hash-like object or single value, return false no config found
 | 
        
           |  |  | 1014 |  * @throws dml_exception
 | 
        
           |  |  | 1015 |  */
 | 
        
           | 1326 | ariadna | 1016 | function get_config($plugin, $name = null)
 | 
        
           |  |  | 1017 | {
 | 
        
           | 1 | efrain | 1018 |     global $CFG, $DB;
 | 
        
           |  |  | 1019 |   | 
        
           |  |  | 1020 |     if ($plugin === 'moodle' || $plugin === 'core' || empty($plugin)) {
 | 
        
           | 1326 | ariadna | 1021 |         $forced = &$CFG->config_php_settings;
 | 
        
           | 1 | efrain | 1022 |         $iscore = true;
 | 
        
           |  |  | 1023 |         $plugin = 'core';
 | 
        
           |  |  | 1024 |     } else {
 | 
        
           |  |  | 1025 |         if (array_key_exists($plugin, $CFG->forced_plugin_settings)) {
 | 
        
           | 1326 | ariadna | 1026 |             $forced = &$CFG->forced_plugin_settings[$plugin];
 | 
        
           | 1 | efrain | 1027 |         } else {
 | 
        
           |  |  | 1028 |             $forced = array();
 | 
        
           |  |  | 1029 |         }
 | 
        
           |  |  | 1030 |         $iscore = false;
 | 
        
           |  |  | 1031 |     }
 | 
        
           |  |  | 1032 |   | 
        
           |  |  | 1033 |     if (!isset($CFG->siteidentifier)) {
 | 
        
           |  |  | 1034 |         try {
 | 
        
           |  |  | 1035 |             // This may throw an exception during installation, which is how we detect the
 | 
        
           |  |  | 1036 |             // need to install the database. For more details see {@see initialise_cfg()}.
 | 
        
           |  |  | 1037 |             $CFG->siteidentifier = $DB->get_field('config', 'value', array('name' => 'siteidentifier'));
 | 
        
           |  |  | 1038 |         } catch (dml_exception $ex) {
 | 
        
           |  |  | 1039 |             // Set siteidentifier to false. We don't want to trip this continually.
 | 
        
           |  |  | 1040 |             $siteidentifier = false;
 | 
        
           |  |  | 1041 |             throw $ex;
 | 
        
           |  |  | 1042 |         }
 | 
        
           |  |  | 1043 |     }
 | 
        
           |  |  | 1044 |   | 
        
           |  |  | 1045 |     if (!empty($name)) {
 | 
        
           |  |  | 1046 |         if (array_key_exists($name, $forced)) {
 | 
        
           |  |  | 1047 |             return (string)$forced[$name];
 | 
        
           |  |  | 1048 |         } else if ($name === 'siteidentifier' && $plugin == 'core') {
 | 
        
           |  |  | 1049 |             return $CFG->siteidentifier;
 | 
        
           |  |  | 1050 |         }
 | 
        
           |  |  | 1051 |     }
 | 
        
           |  |  | 1052 |   | 
        
           |  |  | 1053 |     $cache = cache::make('core', 'config');
 | 
        
           |  |  | 1054 |     $result = $cache->get($plugin);
 | 
        
           |  |  | 1055 |     if ($result === false) {
 | 
        
           |  |  | 1056 |         // The user is after a recordset.
 | 
        
           |  |  | 1057 |         if (!$iscore) {
 | 
        
           |  |  | 1058 |             $result = $DB->get_records_menu('config_plugins', array('plugin' => $plugin), '', 'name,value');
 | 
        
           |  |  | 1059 |         } else {
 | 
        
           |  |  | 1060 |             // This part is not really used any more, but anyway...
 | 
        
           |  |  | 1061 |             $result = $DB->get_records_menu('config', array(), '', 'name,value');;
 | 
        
           |  |  | 1062 |         }
 | 
        
           |  |  | 1063 |         $cache->set($plugin, $result);
 | 
        
           |  |  | 1064 |     }
 | 
        
           |  |  | 1065 |   | 
        
           |  |  | 1066 |     if (!empty($name)) {
 | 
        
           |  |  | 1067 |         if (array_key_exists($name, $result)) {
 | 
        
           |  |  | 1068 |             return $result[$name];
 | 
        
           |  |  | 1069 |         }
 | 
        
           |  |  | 1070 |         return false;
 | 
        
           |  |  | 1071 |     }
 | 
        
           |  |  | 1072 |   | 
        
           |  |  | 1073 |     if ($plugin === 'core') {
 | 
        
           |  |  | 1074 |         $result['siteidentifier'] = $CFG->siteidentifier;
 | 
        
           |  |  | 1075 |     }
 | 
        
           |  |  | 1076 |   | 
        
           |  |  | 1077 |     foreach ($forced as $key => $value) {
 | 
        
           |  |  | 1078 |         if (is_null($value) or is_array($value) or is_object($value)) {
 | 
        
           |  |  | 1079 |             // We do not want any extra mess here, just real settings that could be saved in db.
 | 
        
           |  |  | 1080 |             unset($result[$key]);
 | 
        
           |  |  | 1081 |         } else {
 | 
        
           |  |  | 1082 |             // Convert to string as if it went through the DB.
 | 
        
           |  |  | 1083 |             $result[$key] = (string)$value;
 | 
        
           |  |  | 1084 |         }
 | 
        
           |  |  | 1085 |     }
 | 
        
           |  |  | 1086 |   | 
        
           |  |  | 1087 |     return (object)$result;
 | 
        
           |  |  | 1088 | }
 | 
        
           |  |  | 1089 |   | 
        
           |  |  | 1090 | /**
 | 
        
           |  |  | 1091 |  * Removes a key from global configuration.
 | 
        
           |  |  | 1092 |  *
 | 
        
           |  |  | 1093 |  * NOTE: this function is called from lib/db/upgrade.php
 | 
        
           |  |  | 1094 |  *
 | 
        
           |  |  | 1095 |  * @param string $name the key to set
 | 
        
           |  |  | 1096 |  * @param string $plugin (optional) the plugin scope
 | 
        
           |  |  | 1097 |  * @return boolean whether the operation succeeded.
 | 
        
           |  |  | 1098 |  */
 | 
        
           | 1326 | ariadna | 1099 | function unset_config($name, $plugin = null)
 | 
        
           |  |  | 1100 | {
 | 
        
           | 1 | efrain | 1101 |     global $CFG, $DB;
 | 
        
           |  |  | 1102 |   | 
        
           |  |  | 1103 |     if (empty($plugin)) {
 | 
        
           |  |  | 1104 |         unset($CFG->$name);
 | 
        
           |  |  | 1105 |         $DB->delete_records('config', array('name' => $name));
 | 
        
           |  |  | 1106 |         cache_helper::invalidate_by_definition('core', 'config', array(), 'core');
 | 
        
           |  |  | 1107 |     } else {
 | 
        
           |  |  | 1108 |         $DB->delete_records('config_plugins', array('name' => $name, 'plugin' => $plugin));
 | 
        
           |  |  | 1109 |         cache_helper::invalidate_by_definition('core', 'config', array(), $plugin);
 | 
        
           |  |  | 1110 |     }
 | 
        
           |  |  | 1111 |   | 
        
           |  |  | 1112 |     return true;
 | 
        
           |  |  | 1113 | }
 | 
        
           |  |  | 1114 |   | 
        
           |  |  | 1115 | /**
 | 
        
           |  |  | 1116 |  * Remove all the config variables for a given plugin.
 | 
        
           |  |  | 1117 |  *
 | 
        
           |  |  | 1118 |  * NOTE: this function is called from lib/db/upgrade.php
 | 
        
           |  |  | 1119 |  *
 | 
        
           |  |  | 1120 |  * @param string $plugin a plugin, for example 'quiz' or 'qtype_multichoice';
 | 
        
           |  |  | 1121 |  * @return boolean whether the operation succeeded.
 | 
        
           |  |  | 1122 |  */
 | 
        
           | 1326 | ariadna | 1123 | function unset_all_config_for_plugin($plugin)
 | 
        
           |  |  | 1124 | {
 | 
        
           | 1 | efrain | 1125 |     global $DB;
 | 
        
           |  |  | 1126 |     // Delete from the obvious config_plugins first.
 | 
        
           |  |  | 1127 |     $DB->delete_records('config_plugins', array('plugin' => $plugin));
 | 
        
           |  |  | 1128 |     // Next delete any suspect settings from config.
 | 
        
           |  |  | 1129 |     $like = $DB->sql_like('name', '?', true, true, false, '|');
 | 
        
           | 1326 | ariadna | 1130 |     $params = array($DB->sql_like_escape($plugin . '_', '|') . '%');
 | 
        
           | 1 | efrain | 1131 |     $DB->delete_records_select('config', $like, $params);
 | 
        
           |  |  | 1132 |     // Finally clear both the plugin cache and the core cache (suspect settings now removed from core).
 | 
        
           |  |  | 1133 |     cache_helper::invalidate_by_definition('core', 'config', array(), array('core', $plugin));
 | 
        
           |  |  | 1134 |   | 
        
           |  |  | 1135 |     return true;
 | 
        
           |  |  | 1136 | }
 | 
        
           |  |  | 1137 |   | 
        
           |  |  | 1138 | /**
 | 
        
           |  |  | 1139 |  * Use this function to get a list of users from a config setting of type admin_setting_users_with_capability.
 | 
        
           |  |  | 1140 |  *
 | 
        
           |  |  | 1141 |  * All users are verified if they still have the necessary capability.
 | 
        
           |  |  | 1142 |  *
 | 
        
           |  |  | 1143 |  * @param string $value the value of the config setting.
 | 
        
           |  |  | 1144 |  * @param string $capability the capability - must match the one passed to the admin_setting_users_with_capability constructor.
 | 
        
           |  |  | 1145 |  * @param bool $includeadmins include administrators.
 | 
        
           |  |  | 1146 |  * @return array of user objects.
 | 
        
           |  |  | 1147 |  */
 | 
        
           | 1326 | ariadna | 1148 | function get_users_from_config($value, $capability, $includeadmins = true)
 | 
        
           |  |  | 1149 | {
 | 
        
           | 1 | efrain | 1150 |     if (empty($value) or $value === '$@NONE@$') {
 | 
        
           |  |  | 1151 |         return array();
 | 
        
           |  |  | 1152 |     }
 | 
        
           |  |  | 1153 |   | 
        
           |  |  | 1154 |     // We have to make sure that users still have the necessary capability,
 | 
        
           |  |  | 1155 |     // it should be faster to fetch them all first and then test if they are present
 | 
        
           |  |  | 1156 |     // instead of validating them one-by-one.
 | 
        
           |  |  | 1157 |     $users = get_users_by_capability(context_system::instance(), $capability);
 | 
        
           |  |  | 1158 |     if ($includeadmins) {
 | 
        
           |  |  | 1159 |         $admins = get_admins();
 | 
        
           |  |  | 1160 |         foreach ($admins as $admin) {
 | 
        
           |  |  | 1161 |             $users[$admin->id] = $admin;
 | 
        
           |  |  | 1162 |         }
 | 
        
           |  |  | 1163 |     }
 | 
        
           |  |  | 1164 |   | 
        
           |  |  | 1165 |     if ($value === '$@ALL@$') {
 | 
        
           |  |  | 1166 |         return $users;
 | 
        
           |  |  | 1167 |     }
 | 
        
           |  |  | 1168 |   | 
        
           |  |  | 1169 |     $result = array(); // Result in correct order.
 | 
        
           |  |  | 1170 |     $allowed = explode(',', $value);
 | 
        
           |  |  | 1171 |     foreach ($allowed as $uid) {
 | 
        
           |  |  | 1172 |         if (isset($users[$uid])) {
 | 
        
           |  |  | 1173 |             $user = $users[$uid];
 | 
        
           |  |  | 1174 |             $result[$user->id] = $user;
 | 
        
           |  |  | 1175 |         }
 | 
        
           |  |  | 1176 |     }
 | 
        
           |  |  | 1177 |   | 
        
           |  |  | 1178 |     return $result;
 | 
        
           |  |  | 1179 | }
 | 
        
           |  |  | 1180 |   | 
        
           |  |  | 1181 |   | 
        
           |  |  | 1182 | /**
 | 
        
           |  |  | 1183 |  * Invalidates browser caches and cached data in temp.
 | 
        
           |  |  | 1184 |  *
 | 
        
           |  |  | 1185 |  * @return void
 | 
        
           |  |  | 1186 |  */
 | 
        
           | 1326 | ariadna | 1187 | function purge_all_caches()
 | 
        
           |  |  | 1188 | {
 | 
        
           | 1 | efrain | 1189 |     purge_caches();
 | 
        
           |  |  | 1190 | }
 | 
        
           |  |  | 1191 |   | 
        
           |  |  | 1192 | /**
 | 
        
           |  |  | 1193 |  * Selectively invalidate different types of cache.
 | 
        
           |  |  | 1194 |  *
 | 
        
           |  |  | 1195 |  * Purges the cache areas specified.  By default, this will purge all caches but can selectively purge specific
 | 
        
           |  |  | 1196 |  * areas alone or in combination.
 | 
        
           |  |  | 1197 |  *
 | 
        
           |  |  | 1198 |  * @param bool[] $options Specific parts of the cache to purge. Valid options are:
 | 
        
           |  |  | 1199 |  *        'muc'    Purge MUC caches?
 | 
        
           | 11 | efrain | 1200 |  *        'courses' Purge all course caches, or specific course caches (CLI only)
 | 
        
           | 1 | efrain | 1201 |  *        'theme'  Purge theme cache?
 | 
        
           |  |  | 1202 |  *        'lang'   Purge language string cache?
 | 
        
           |  |  | 1203 |  *        'js'     Purge javascript cache?
 | 
        
           |  |  | 1204 |  *        'filter' Purge text filter cache?
 | 
        
           |  |  | 1205 |  *        'other'  Purge all other caches?
 | 
        
           |  |  | 1206 |  */
 | 
        
           | 1326 | ariadna | 1207 | function purge_caches($options = [])
 | 
        
           |  |  | 1208 | {
 | 
        
           | 1 | efrain | 1209 |     $defaults = array_fill_keys(['muc', 'courses', 'theme', 'lang', 'js', 'template', 'filter', 'other'], false);
 | 
        
           |  |  | 1210 |     if (empty(array_filter($options))) {
 | 
        
           |  |  | 1211 |         $options = array_fill_keys(array_keys($defaults), true); // Set all options to true.
 | 
        
           |  |  | 1212 |     } else {
 | 
        
           |  |  | 1213 |         $options = array_merge($defaults, array_intersect_key($options, $defaults)); // Override defaults with specified options.
 | 
        
           |  |  | 1214 |     }
 | 
        
           |  |  | 1215 |     if ($options['muc']) {
 | 
        
           |  |  | 1216 |         cache_helper::purge_all();
 | 
        
           | 11 | efrain | 1217 |     } else if ($options['courses']) {
 | 
        
           | 1 | efrain | 1218 |         if ($options['courses'] === true) {
 | 
        
           |  |  | 1219 |             $courseids = [];
 | 
        
           |  |  | 1220 |         } else {
 | 
        
           |  |  | 1221 |             $courseids = preg_split('/\s*,\s*/', $options['courses'], -1, PREG_SPLIT_NO_EMPTY);
 | 
        
           |  |  | 1222 |         }
 | 
        
           |  |  | 1223 |         course_modinfo::purge_course_caches($courseids);
 | 
        
           |  |  | 1224 |     }
 | 
        
           |  |  | 1225 |     if ($options['theme']) {
 | 
        
           |  |  | 1226 |         theme_reset_all_caches();
 | 
        
           |  |  | 1227 |     }
 | 
        
           |  |  | 1228 |     if ($options['lang']) {
 | 
        
           |  |  | 1229 |         get_string_manager()->reset_caches();
 | 
        
           |  |  | 1230 |     }
 | 
        
           |  |  | 1231 |     if ($options['js']) {
 | 
        
           |  |  | 1232 |         js_reset_all_caches();
 | 
        
           |  |  | 1233 |     }
 | 
        
           |  |  | 1234 |     if ($options['template']) {
 | 
        
           |  |  | 1235 |         template_reset_all_caches();
 | 
        
           |  |  | 1236 |     }
 | 
        
           |  |  | 1237 |     if ($options['filter']) {
 | 
        
           |  |  | 1238 |         reset_text_filters_cache();
 | 
        
           |  |  | 1239 |     }
 | 
        
           |  |  | 1240 |     if ($options['other']) {
 | 
        
           |  |  | 1241 |         purge_other_caches();
 | 
        
           |  |  | 1242 |     }
 | 
        
           |  |  | 1243 | }
 | 
        
           |  |  | 1244 |   | 
        
           |  |  | 1245 | /**
 | 
        
           |  |  | 1246 |  * Purge all non-MUC caches not otherwise purged in purge_caches.
 | 
        
           |  |  | 1247 |  *
 | 
        
           |  |  | 1248 |  * IMPORTANT - If you are adding anything here to do with the cache directory you should also have a look at
 | 
        
           |  |  | 1249 |  * {@link phpunit_util::reset_dataroot()}
 | 
        
           |  |  | 1250 |  */
 | 
        
           | 1326 | ariadna | 1251 | function purge_other_caches()
 | 
        
           |  |  | 1252 | {
 | 
        
           | 1 | efrain | 1253 |     global $DB, $CFG;
 | 
        
           |  |  | 1254 |     if (class_exists('core_plugin_manager')) {
 | 
        
           |  |  | 1255 |         core_plugin_manager::reset_caches();
 | 
        
           |  |  | 1256 |     }
 | 
        
           |  |  | 1257 |   | 
        
           |  |  | 1258 |     // Bump up cacherev field for all courses.
 | 
        
           |  |  | 1259 |     try {
 | 
        
           |  |  | 1260 |         increment_revision_number('course', 'cacherev', '');
 | 
        
           |  |  | 1261 |     } catch (moodle_exception $e) {
 | 
        
           |  |  | 1262 |         // Ignore exception since this function is also called before upgrade script when field course.cacherev does not exist yet.
 | 
        
           |  |  | 1263 |     }
 | 
        
           |  |  | 1264 |   | 
        
           |  |  | 1265 |     $DB->reset_caches();
 | 
        
           |  |  | 1266 |   | 
        
           |  |  | 1267 |     // Purge all other caches: rss, simplepie, etc.
 | 
        
           |  |  | 1268 |     clearstatcache();
 | 
        
           | 1326 | ariadna | 1269 |     remove_dir($CFG->cachedir . '', true);
 | 
        
           | 1 | efrain | 1270 |   | 
        
           |  |  | 1271 |     // Make sure cache dir is writable, throws exception if not.
 | 
        
           |  |  | 1272 |     make_cache_directory('');
 | 
        
           |  |  | 1273 |   | 
        
           |  |  | 1274 |     // This is the only place where we purge local caches, we are only adding files there.
 | 
        
           |  |  | 1275 |     // The $CFG->localcachedirpurged flag forces local directories to be purged on cluster nodes.
 | 
        
           |  |  | 1276 |     remove_dir($CFG->localcachedir, true);
 | 
        
           |  |  | 1277 |     set_config('localcachedirpurged', time());
 | 
        
           |  |  | 1278 |     make_localcache_directory('', true);
 | 
        
           | 11 | efrain | 1279 |   | 
        
           |  |  | 1280 |     // Rewarm the bootstrap.php files so the siteid is always present after a purge.
 | 
        
           |  |  | 1281 |     initialise_local_config_cache();
 | 
        
           | 1 | efrain | 1282 |     \core\task\manager::clear_static_caches();
 | 
        
           |  |  | 1283 | }
 | 
        
           |  |  | 1284 |   | 
        
           |  |  | 1285 | /**
 | 
        
           |  |  | 1286 |  * Get volatile flags
 | 
        
           |  |  | 1287 |  *
 | 
        
           |  |  | 1288 |  * @param string $type
 | 
        
           |  |  | 1289 |  * @param int $changedsince default null
 | 
        
           |  |  | 1290 |  * @return array records array
 | 
        
           |  |  | 1291 |  */
 | 
        
           | 1326 | ariadna | 1292 | function get_cache_flags($type, $changedsince = null)
 | 
        
           |  |  | 1293 | {
 | 
        
           | 1 | efrain | 1294 |     global $DB;
 | 
        
           |  |  | 1295 |   | 
        
           |  |  | 1296 |     $params = array('type' => $type, 'expiry' => time());
 | 
        
           |  |  | 1297 |     $sqlwhere = "flagtype = :type AND expiry >= :expiry";
 | 
        
           |  |  | 1298 |     if ($changedsince !== null) {
 | 
        
           |  |  | 1299 |         $params['changedsince'] = $changedsince;
 | 
        
           |  |  | 1300 |         $sqlwhere .= " AND timemodified > :changedsince";
 | 
        
           |  |  | 1301 |     }
 | 
        
           |  |  | 1302 |     $cf = array();
 | 
        
           |  |  | 1303 |     if ($flags = $DB->get_records_select('cache_flags', $sqlwhere, $params, '', 'name,value')) {
 | 
        
           |  |  | 1304 |         foreach ($flags as $flag) {
 | 
        
           |  |  | 1305 |             $cf[$flag->name] = $flag->value;
 | 
        
           |  |  | 1306 |         }
 | 
        
           |  |  | 1307 |     }
 | 
        
           |  |  | 1308 |     return $cf;
 | 
        
           |  |  | 1309 | }
 | 
        
           |  |  | 1310 |   | 
        
           |  |  | 1311 | /**
 | 
        
           |  |  | 1312 |  * Get volatile flags
 | 
        
           |  |  | 1313 |  *
 | 
        
           |  |  | 1314 |  * @param string $type
 | 
        
           |  |  | 1315 |  * @param string $name
 | 
        
           |  |  | 1316 |  * @param int $changedsince default null
 | 
        
           |  |  | 1317 |  * @return string|false The cache flag value or false
 | 
        
           |  |  | 1318 |  */
 | 
        
           | 1326 | ariadna | 1319 | function get_cache_flag($type, $name, $changedsince = null)
 | 
        
           |  |  | 1320 | {
 | 
        
           | 1 | efrain | 1321 |     global $DB;
 | 
        
           |  |  | 1322 |   | 
        
           |  |  | 1323 |     $params = array('type' => $type, 'name' => $name, 'expiry' => time());
 | 
        
           |  |  | 1324 |   | 
        
           |  |  | 1325 |     $sqlwhere = "flagtype = :type AND name = :name AND expiry >= :expiry";
 | 
        
           |  |  | 1326 |     if ($changedsince !== null) {
 | 
        
           |  |  | 1327 |         $params['changedsince'] = $changedsince;
 | 
        
           |  |  | 1328 |         $sqlwhere .= " AND timemodified > :changedsince";
 | 
        
           |  |  | 1329 |     }
 | 
        
           |  |  | 1330 |   | 
        
           |  |  | 1331 |     return $DB->get_field_select('cache_flags', 'value', $sqlwhere, $params);
 | 
        
           |  |  | 1332 | }
 | 
        
           |  |  | 1333 |   | 
        
           |  |  | 1334 | /**
 | 
        
           |  |  | 1335 |  * Set a volatile flag
 | 
        
           |  |  | 1336 |  *
 | 
        
           |  |  | 1337 |  * @param string $type the "type" namespace for the key
 | 
        
           |  |  | 1338 |  * @param string $name the key to set
 | 
        
           |  |  | 1339 |  * @param string $value the value to set (without magic quotes) - null will remove the flag
 | 
        
           |  |  | 1340 |  * @param int $expiry (optional) epoch indicating expiry - defaults to now()+ 24hs
 | 
        
           |  |  | 1341 |  * @return bool Always returns true
 | 
        
           |  |  | 1342 |  */
 | 
        
           | 1326 | ariadna | 1343 | function set_cache_flag($type, $name, $value, $expiry = null)
 | 
        
           |  |  | 1344 | {
 | 
        
           | 1 | efrain | 1345 |     global $DB;
 | 
        
           |  |  | 1346 |   | 
        
           |  |  | 1347 |     $timemodified = time();
 | 
        
           |  |  | 1348 |     if ($expiry === null || $expiry < $timemodified) {
 | 
        
           |  |  | 1349 |         $expiry = $timemodified + 24 * 60 * 60;
 | 
        
           |  |  | 1350 |     } else {
 | 
        
           |  |  | 1351 |         $expiry = (int)$expiry;
 | 
        
           |  |  | 1352 |     }
 | 
        
           |  |  | 1353 |   | 
        
           |  |  | 1354 |     if ($value === null) {
 | 
        
           |  |  | 1355 |         unset_cache_flag($type, $name);
 | 
        
           |  |  | 1356 |         return true;
 | 
        
           |  |  | 1357 |     }
 | 
        
           |  |  | 1358 |   | 
        
           |  |  | 1359 |     if ($f = $DB->get_record('cache_flags', array('name' => $name, 'flagtype' => $type), '*', IGNORE_MULTIPLE)) {
 | 
        
           |  |  | 1360 |         // This is a potential problem in DEBUG_DEVELOPER.
 | 
        
           |  |  | 1361 |         if ($f->value == $value and $f->expiry == $expiry and $f->timemodified == $timemodified) {
 | 
        
           |  |  | 1362 |             return true; // No need to update.
 | 
        
           |  |  | 1363 |         }
 | 
        
           |  |  | 1364 |         $f->value        = $value;
 | 
        
           |  |  | 1365 |         $f->expiry       = $expiry;
 | 
        
           |  |  | 1366 |         $f->timemodified = $timemodified;
 | 
        
           |  |  | 1367 |         $DB->update_record('cache_flags', $f);
 | 
        
           |  |  | 1368 |     } else {
 | 
        
           |  |  | 1369 |         $f = new stdClass();
 | 
        
           |  |  | 1370 |         $f->flagtype     = $type;
 | 
        
           |  |  | 1371 |         $f->name         = $name;
 | 
        
           |  |  | 1372 |         $f->value        = $value;
 | 
        
           |  |  | 1373 |         $f->expiry       = $expiry;
 | 
        
           |  |  | 1374 |         $f->timemodified = $timemodified;
 | 
        
           |  |  | 1375 |         $DB->insert_record('cache_flags', $f);
 | 
        
           |  |  | 1376 |     }
 | 
        
           |  |  | 1377 |     return true;
 | 
        
           |  |  | 1378 | }
 | 
        
           |  |  | 1379 |   | 
        
           |  |  | 1380 | /**
 | 
        
           |  |  | 1381 |  * Removes a single volatile flag
 | 
        
           |  |  | 1382 |  *
 | 
        
           |  |  | 1383 |  * @param string $type the "type" namespace for the key
 | 
        
           |  |  | 1384 |  * @param string $name the key to set
 | 
        
           |  |  | 1385 |  * @return bool
 | 
        
           |  |  | 1386 |  */
 | 
        
           | 1326 | ariadna | 1387 | function unset_cache_flag($type, $name)
 | 
        
           |  |  | 1388 | {
 | 
        
           | 1 | efrain | 1389 |     global $DB;
 | 
        
           |  |  | 1390 |     $DB->delete_records('cache_flags', array('name' => $name, 'flagtype' => $type));
 | 
        
           |  |  | 1391 |     return true;
 | 
        
           |  |  | 1392 | }
 | 
        
           |  |  | 1393 |   | 
        
           |  |  | 1394 | /**
 | 
        
           |  |  | 1395 |  * Garbage-collect volatile flags
 | 
        
           |  |  | 1396 |  *
 | 
        
           |  |  | 1397 |  * @return bool Always returns true
 | 
        
           |  |  | 1398 |  */
 | 
        
           | 1326 | ariadna | 1399 | function gc_cache_flags()
 | 
        
           |  |  | 1400 | {
 | 
        
           | 1 | efrain | 1401 |     global $DB;
 | 
        
           |  |  | 1402 |     $DB->delete_records_select('cache_flags', 'expiry < ?', array(time()));
 | 
        
           |  |  | 1403 |     return true;
 | 
        
           |  |  | 1404 | }
 | 
        
           |  |  | 1405 |   | 
        
           |  |  | 1406 | // USER PREFERENCE API.
 | 
        
           |  |  | 1407 |   | 
        
           |  |  | 1408 | /**
 | 
        
           |  |  | 1409 |  * Refresh user preference cache. This is used most often for $USER
 | 
        
           |  |  | 1410 |  * object that is stored in session, but it also helps with performance in cron script.
 | 
        
           |  |  | 1411 |  *
 | 
        
           |  |  | 1412 |  * Preferences for each user are loaded on first use on every page, then again after the timeout expires.
 | 
        
           |  |  | 1413 |  *
 | 
        
           |  |  | 1414 |  * @package  core
 | 
        
           |  |  | 1415 |  * @category preference
 | 
        
           |  |  | 1416 |  * @access   public
 | 
        
           |  |  | 1417 |  * @param    stdClass         $user          User object. Preferences are preloaded into 'preference' property
 | 
        
           |  |  | 1418 |  * @param    int              $cachelifetime Cache life time on the current page (in seconds)
 | 
        
           |  |  | 1419 |  * @throws   coding_exception
 | 
        
           |  |  | 1420 |  * @return   null
 | 
        
           |  |  | 1421 |  */
 | 
        
           | 1326 | ariadna | 1422 | function check_user_preferences_loaded(stdClass $user, $cachelifetime = 120)
 | 
        
           |  |  | 1423 | {
 | 
        
           | 1 | efrain | 1424 |     global $DB;
 | 
        
           |  |  | 1425 |     // Static cache, we need to check on each page load, not only every 2 minutes.
 | 
        
           |  |  | 1426 |     static $loadedusers = array();
 | 
        
           |  |  | 1427 |   | 
        
           |  |  | 1428 |     if (!isset($user->id)) {
 | 
        
           |  |  | 1429 |         throw new coding_exception('Invalid $user parameter in check_user_preferences_loaded() call, missing id field');
 | 
        
           |  |  | 1430 |     }
 | 
        
           |  |  | 1431 |   | 
        
           |  |  | 1432 |     if (empty($user->id) or isguestuser($user->id)) {
 | 
        
           |  |  | 1433 |         // No permanent storage for not-logged-in users and guest.
 | 
        
           |  |  | 1434 |         if (!isset($user->preference)) {
 | 
        
           |  |  | 1435 |             $user->preference = array();
 | 
        
           |  |  | 1436 |         }
 | 
        
           |  |  | 1437 |         return;
 | 
        
           |  |  | 1438 |     }
 | 
        
           |  |  | 1439 |   | 
        
           |  |  | 1440 |     $timenow = time();
 | 
        
           |  |  | 1441 |   | 
        
           |  |  | 1442 |     if (isset($loadedusers[$user->id]) and isset($user->preference) and isset($user->preference['_lastloaded'])) {
 | 
        
           |  |  | 1443 |         // Already loaded at least once on this page. Are we up to date?
 | 
        
           |  |  | 1444 |         if ($user->preference['_lastloaded'] + $cachelifetime > $timenow) {
 | 
        
           |  |  | 1445 |             // No need to reload - we are on the same page and we loaded prefs just a moment ago.
 | 
        
           |  |  | 1446 |             return;
 | 
        
           |  |  | 1447 |         } else if (!get_cache_flag('userpreferenceschanged', $user->id, $user->preference['_lastloaded'])) {
 | 
        
           |  |  | 1448 |             // No change since the lastcheck on this page.
 | 
        
           |  |  | 1449 |             $user->preference['_lastloaded'] = $timenow;
 | 
        
           |  |  | 1450 |             return;
 | 
        
           |  |  | 1451 |         }
 | 
        
           |  |  | 1452 |     }
 | 
        
           |  |  | 1453 |   | 
        
           |  |  | 1454 |     // OK, so we have to reload all preferences.
 | 
        
           |  |  | 1455 |     $loadedusers[$user->id] = true;
 | 
        
           |  |  | 1456 |     $user->preference = $DB->get_records_menu('user_preferences', array('userid' => $user->id), '', 'name,value'); // All values.
 | 
        
           |  |  | 1457 |     $user->preference['_lastloaded'] = $timenow;
 | 
        
           |  |  | 1458 | }
 | 
        
           |  |  | 1459 |   | 
        
           |  |  | 1460 | /**
 | 
        
           |  |  | 1461 |  * Called from set/unset_user_preferences, so that the prefs can be correctly reloaded in different sessions.
 | 
        
           |  |  | 1462 |  *
 | 
        
           |  |  | 1463 |  * NOTE: internal function, do not call from other code.
 | 
        
           |  |  | 1464 |  *
 | 
        
           |  |  | 1465 |  * @package core
 | 
        
           |  |  | 1466 |  * @access private
 | 
        
           |  |  | 1467 |  * @param integer $userid the user whose prefs were changed.
 | 
        
           |  |  | 1468 |  */
 | 
        
           | 1326 | ariadna | 1469 | function mark_user_preferences_changed($userid)
 | 
        
           |  |  | 1470 | {
 | 
        
           | 1 | efrain | 1471 |     global $CFG;
 | 
        
           |  |  | 1472 |   | 
        
           |  |  | 1473 |     if (empty($userid) or isguestuser($userid)) {
 | 
        
           |  |  | 1474 |         // No cache flags for guest and not-logged-in users.
 | 
        
           |  |  | 1475 |         return;
 | 
        
           |  |  | 1476 |     }
 | 
        
           |  |  | 1477 |   | 
        
           |  |  | 1478 |     set_cache_flag('userpreferenceschanged', $userid, 1, time() + $CFG->sessiontimeout);
 | 
        
           |  |  | 1479 | }
 | 
        
           |  |  | 1480 |   | 
        
           |  |  | 1481 | /**
 | 
        
           |  |  | 1482 |  * Sets a preference for the specified user.
 | 
        
           |  |  | 1483 |  *
 | 
        
           |  |  | 1484 |  * If a $user object is submitted it's 'preference' property is used for the preferences cache.
 | 
        
           |  |  | 1485 |  *
 | 
        
           |  |  | 1486 |  * When additional validation/permission check is needed it is better to use {@see useredit_update_user_preference()}
 | 
        
           |  |  | 1487 |  *
 | 
        
           |  |  | 1488 |  * @package  core
 | 
        
           |  |  | 1489 |  * @category preference
 | 
        
           |  |  | 1490 |  * @access   public
 | 
        
           |  |  | 1491 |  * @param    string            $name  The key to set as preference for the specified user
 | 
        
           |  |  | 1492 |  * @param    string|int|bool|null $value The value to set for the $name key in the specified user's
 | 
        
           |  |  | 1493 |  *                                    record, null means delete current value.
 | 
        
           |  |  | 1494 |  * @param    stdClass|int|null $user  A moodle user object or id, null means current user
 | 
        
           |  |  | 1495 |  * @throws   coding_exception
 | 
        
           |  |  | 1496 |  * @return   bool                     Always true or exception
 | 
        
           |  |  | 1497 |  */
 | 
        
           | 1326 | ariadna | 1498 | function set_user_preference($name, $value, $user = null)
 | 
        
           |  |  | 1499 | {
 | 
        
           | 1 | efrain | 1500 |     global $USER, $DB;
 | 
        
           |  |  | 1501 |   | 
        
           |  |  | 1502 |     if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
 | 
        
           |  |  | 1503 |         throw new coding_exception('Invalid preference name in set_user_preference() call');
 | 
        
           |  |  | 1504 |     }
 | 
        
           |  |  | 1505 |   | 
        
           |  |  | 1506 |     if (is_null($value)) {
 | 
        
           |  |  | 1507 |         // Null means delete current.
 | 
        
           |  |  | 1508 |         return unset_user_preference($name, $user);
 | 
        
           |  |  | 1509 |     } else if (is_object($value)) {
 | 
        
           |  |  | 1510 |         throw new coding_exception('Invalid value in set_user_preference() call, objects are not allowed');
 | 
        
           |  |  | 1511 |     } else if (is_array($value)) {
 | 
        
           |  |  | 1512 |         throw new coding_exception('Invalid value in set_user_preference() call, arrays are not allowed');
 | 
        
           |  |  | 1513 |     }
 | 
        
           |  |  | 1514 |     // Value column maximum length is 1333 characters.
 | 
        
           |  |  | 1515 |     $value = (string)$value;
 | 
        
           |  |  | 1516 |     if (core_text::strlen($value) > 1333) {
 | 
        
           |  |  | 1517 |         throw new coding_exception('Invalid value in set_user_preference() call, value is is too long for the value column');
 | 
        
           |  |  | 1518 |     }
 | 
        
           |  |  | 1519 |   | 
        
           |  |  | 1520 |     if (is_null($user)) {
 | 
        
           |  |  | 1521 |         $user = $USER;
 | 
        
           |  |  | 1522 |     } else if (isset($user->id)) {
 | 
        
           |  |  | 1523 |         // It is a valid object.
 | 
        
           |  |  | 1524 |     } else if (is_numeric($user)) {
 | 
        
           |  |  | 1525 |         $user = (object)array('id' => (int)$user);
 | 
        
           |  |  | 1526 |     } else {
 | 
        
           |  |  | 1527 |         throw new coding_exception('Invalid $user parameter in set_user_preference() call');
 | 
        
           |  |  | 1528 |     }
 | 
        
           |  |  | 1529 |   | 
        
           |  |  | 1530 |     check_user_preferences_loaded($user);
 | 
        
           |  |  | 1531 |   | 
        
           |  |  | 1532 |     if (empty($user->id) or isguestuser($user->id)) {
 | 
        
           |  |  | 1533 |         // No permanent storage for not-logged-in users and guest.
 | 
        
           |  |  | 1534 |         $user->preference[$name] = $value;
 | 
        
           |  |  | 1535 |         return true;
 | 
        
           |  |  | 1536 |     }
 | 
        
           |  |  | 1537 |   | 
        
           |  |  | 1538 |     if ($preference = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => $name))) {
 | 
        
           |  |  | 1539 |         if ($preference->value === $value and isset($user->preference[$name]) and $user->preference[$name] === $value) {
 | 
        
           |  |  | 1540 |             // Preference already set to this value.
 | 
        
           |  |  | 1541 |             return true;
 | 
        
           |  |  | 1542 |         }
 | 
        
           |  |  | 1543 |         $DB->set_field('user_preferences', 'value', $value, array('id' => $preference->id));
 | 
        
           |  |  | 1544 |     } else {
 | 
        
           |  |  | 1545 |         $preference = new stdClass();
 | 
        
           |  |  | 1546 |         $preference->userid = $user->id;
 | 
        
           |  |  | 1547 |         $preference->name   = $name;
 | 
        
           |  |  | 1548 |         $preference->value  = $value;
 | 
        
           |  |  | 1549 |         $DB->insert_record('user_preferences', $preference);
 | 
        
           |  |  | 1550 |     }
 | 
        
           |  |  | 1551 |   | 
        
           |  |  | 1552 |     // Update value in cache.
 | 
        
           |  |  | 1553 |     $user->preference[$name] = $value;
 | 
        
           |  |  | 1554 |     // Update the $USER in case where we've not a direct reference to $USER.
 | 
        
           |  |  | 1555 |     if ($user !== $USER && $user->id == $USER->id) {
 | 
        
           |  |  | 1556 |         $USER->preference[$name] = $value;
 | 
        
           |  |  | 1557 |     }
 | 
        
           |  |  | 1558 |   | 
        
           |  |  | 1559 |     // Set reload flag for other sessions.
 | 
        
           |  |  | 1560 |     mark_user_preferences_changed($user->id);
 | 
        
           |  |  | 1561 |   | 
        
           |  |  | 1562 |     return true;
 | 
        
           |  |  | 1563 | }
 | 
        
           |  |  | 1564 |   | 
        
           |  |  | 1565 | /**
 | 
        
           |  |  | 1566 |  * Sets a whole array of preferences for the current user
 | 
        
           |  |  | 1567 |  *
 | 
        
           |  |  | 1568 |  * If a $user object is submitted it's 'preference' property is used for the preferences cache.
 | 
        
           |  |  | 1569 |  *
 | 
        
           |  |  | 1570 |  * @package  core
 | 
        
           |  |  | 1571 |  * @category preference
 | 
        
           |  |  | 1572 |  * @access   public
 | 
        
           |  |  | 1573 |  * @param    array             $prefarray An array of key/value pairs to be set
 | 
        
           |  |  | 1574 |  * @param    stdClass|int|null $user      A moodle user object or id, null means current user
 | 
        
           |  |  | 1575 |  * @return   bool                         Always true or exception
 | 
        
           |  |  | 1576 |  */
 | 
        
           | 1326 | ariadna | 1577 | function set_user_preferences(array $prefarray, $user = null)
 | 
        
           |  |  | 1578 | {
 | 
        
           | 1 | efrain | 1579 |     foreach ($prefarray as $name => $value) {
 | 
        
           |  |  | 1580 |         set_user_preference($name, $value, $user);
 | 
        
           |  |  | 1581 |     }
 | 
        
           |  |  | 1582 |     return true;
 | 
        
           |  |  | 1583 | }
 | 
        
           |  |  | 1584 |   | 
        
           |  |  | 1585 | /**
 | 
        
           |  |  | 1586 |  * Unsets a preference completely by deleting it from the database
 | 
        
           |  |  | 1587 |  *
 | 
        
           |  |  | 1588 |  * If a $user object is submitted it's 'preference' property is used for the preferences cache.
 | 
        
           |  |  | 1589 |  *
 | 
        
           |  |  | 1590 |  * @package  core
 | 
        
           |  |  | 1591 |  * @category preference
 | 
        
           |  |  | 1592 |  * @access   public
 | 
        
           |  |  | 1593 |  * @param    string            $name The key to unset as preference for the specified user
 | 
        
           |  |  | 1594 |  * @param    stdClass|int|null $user A moodle user object or id, null means current user
 | 
        
           |  |  | 1595 |  * @throws   coding_exception
 | 
        
           |  |  | 1596 |  * @return   bool                    Always true or exception
 | 
        
           |  |  | 1597 |  */
 | 
        
           | 1326 | ariadna | 1598 | function unset_user_preference($name, $user = null)
 | 
        
           |  |  | 1599 | {
 | 
        
           | 1 | efrain | 1600 |     global $USER, $DB;
 | 
        
           |  |  | 1601 |   | 
        
           |  |  | 1602 |     if (empty($name) or is_numeric($name) or $name === '_lastloaded') {
 | 
        
           |  |  | 1603 |         throw new coding_exception('Invalid preference name in unset_user_preference() call');
 | 
        
           |  |  | 1604 |     }
 | 
        
           |  |  | 1605 |   | 
        
           |  |  | 1606 |     if (is_null($user)) {
 | 
        
           |  |  | 1607 |         $user = $USER;
 | 
        
           |  |  | 1608 |     } else if (isset($user->id)) {
 | 
        
           |  |  | 1609 |         // It is a valid object.
 | 
        
           |  |  | 1610 |     } else if (is_numeric($user)) {
 | 
        
           |  |  | 1611 |         $user = (object)array('id' => (int)$user);
 | 
        
           |  |  | 1612 |     } else {
 | 
        
           |  |  | 1613 |         throw new coding_exception('Invalid $user parameter in unset_user_preference() call');
 | 
        
           |  |  | 1614 |     }
 | 
        
           |  |  | 1615 |   | 
        
           |  |  | 1616 |     check_user_preferences_loaded($user);
 | 
        
           |  |  | 1617 |   | 
        
           |  |  | 1618 |     if (empty($user->id) or isguestuser($user->id)) {
 | 
        
           |  |  | 1619 |         // No permanent storage for not-logged-in user and guest.
 | 
        
           |  |  | 1620 |         unset($user->preference[$name]);
 | 
        
           |  |  | 1621 |         return true;
 | 
        
           |  |  | 1622 |     }
 | 
        
           |  |  | 1623 |   | 
        
           |  |  | 1624 |     // Delete from DB.
 | 
        
           |  |  | 1625 |     $DB->delete_records('user_preferences', array('userid' => $user->id, 'name' => $name));
 | 
        
           |  |  | 1626 |   | 
        
           |  |  | 1627 |     // Delete the preference from cache.
 | 
        
           |  |  | 1628 |     unset($user->preference[$name]);
 | 
        
           |  |  | 1629 |     // Update the $USER in case where we've not a direct reference to $USER.
 | 
        
           |  |  | 1630 |     if ($user !== $USER && $user->id == $USER->id) {
 | 
        
           |  |  | 1631 |         unset($USER->preference[$name]);
 | 
        
           |  |  | 1632 |     }
 | 
        
           |  |  | 1633 |   | 
        
           |  |  | 1634 |     // Set reload flag for other sessions.
 | 
        
           |  |  | 1635 |     mark_user_preferences_changed($user->id);
 | 
        
           |  |  | 1636 |   | 
        
           |  |  | 1637 |     return true;
 | 
        
           |  |  | 1638 | }
 | 
        
           |  |  | 1639 |   | 
        
           |  |  | 1640 | /**
 | 
        
           |  |  | 1641 |  * Used to fetch user preference(s)
 | 
        
           |  |  | 1642 |  *
 | 
        
           |  |  | 1643 |  * If no arguments are supplied this function will return
 | 
        
           |  |  | 1644 |  * all of the current user preferences as an array.
 | 
        
           |  |  | 1645 |  *
 | 
        
           |  |  | 1646 |  * If a name is specified then this function
 | 
        
           |  |  | 1647 |  * attempts to return that particular preference value.  If
 | 
        
           |  |  | 1648 |  * none is found, then the optional value $default is returned,
 | 
        
           |  |  | 1649 |  * otherwise null.
 | 
        
           |  |  | 1650 |  *
 | 
        
           |  |  | 1651 |  * If a $user object is submitted it's 'preference' property is used for the preferences cache.
 | 
        
           |  |  | 1652 |  *
 | 
        
           |  |  | 1653 |  * @package  core
 | 
        
           |  |  | 1654 |  * @category preference
 | 
        
           |  |  | 1655 |  * @access   public
 | 
        
           |  |  | 1656 |  * @param    string            $name    Name of the key to use in finding a preference value
 | 
        
           |  |  | 1657 |  * @param    mixed|null        $default Value to be returned if the $name key is not set in the user preferences
 | 
        
           |  |  | 1658 |  * @param    stdClass|int|null $user    A moodle user object or id, null means current user
 | 
        
           |  |  | 1659 |  * @throws   coding_exception
 | 
        
           |  |  | 1660 |  * @return   string|mixed|null          A string containing the value of a single preference. An
 | 
        
           |  |  | 1661 |  *                                      array with all of the preferences or null
 | 
        
           |  |  | 1662 |  */
 | 
        
           | 1326 | ariadna | 1663 | function get_user_preferences($name = null, $default = null, $user = null)
 | 
        
           |  |  | 1664 | {
 | 
        
           | 1 | efrain | 1665 |     global $USER;
 | 
        
           |  |  | 1666 |   | 
        
           |  |  | 1667 |     if (is_null($name)) {
 | 
        
           |  |  | 1668 |         // All prefs.
 | 
        
           |  |  | 1669 |     } else if (is_numeric($name) or $name === '_lastloaded') {
 | 
        
           |  |  | 1670 |         throw new coding_exception('Invalid preference name in get_user_preferences() call');
 | 
        
           |  |  | 1671 |     }
 | 
        
           |  |  | 1672 |   | 
        
           |  |  | 1673 |     if (is_null($user)) {
 | 
        
           |  |  | 1674 |         $user = $USER;
 | 
        
           |  |  | 1675 |     } else if (isset($user->id)) {
 | 
        
           |  |  | 1676 |         // Is a valid object.
 | 
        
           |  |  | 1677 |     } else if (is_numeric($user)) {
 | 
        
           |  |  | 1678 |         if ($USER->id == $user) {
 | 
        
           |  |  | 1679 |             $user = $USER;
 | 
        
           |  |  | 1680 |         } else {
 | 
        
           |  |  | 1681 |             $user = (object)array('id' => (int)$user);
 | 
        
           |  |  | 1682 |         }
 | 
        
           |  |  | 1683 |     } else {
 | 
        
           |  |  | 1684 |         throw new coding_exception('Invalid $user parameter in get_user_preferences() call');
 | 
        
           |  |  | 1685 |     }
 | 
        
           |  |  | 1686 |   | 
        
           |  |  | 1687 |     check_user_preferences_loaded($user);
 | 
        
           |  |  | 1688 |   | 
        
           |  |  | 1689 |     if (empty($name)) {
 | 
        
           |  |  | 1690 |         // All values.
 | 
        
           |  |  | 1691 |         return $user->preference;
 | 
        
           |  |  | 1692 |     } else if (isset($user->preference[$name])) {
 | 
        
           |  |  | 1693 |         // The single string value.
 | 
        
           |  |  | 1694 |         return $user->preference[$name];
 | 
        
           |  |  | 1695 |     } else {
 | 
        
           |  |  | 1696 |         // Default value (null if not specified).
 | 
        
           |  |  | 1697 |         return $default;
 | 
        
           |  |  | 1698 |     }
 | 
        
           |  |  | 1699 | }
 | 
        
           |  |  | 1700 |   | 
        
           |  |  | 1701 | // FUNCTIONS FOR HANDLING TIME.
 | 
        
           |  |  | 1702 |   | 
        
           |  |  | 1703 | /**
 | 
        
           |  |  | 1704 |  * Given Gregorian date parts in user time produce a GMT timestamp.
 | 
        
           |  |  | 1705 |  *
 | 
        
           |  |  | 1706 |  * @package core
 | 
        
           |  |  | 1707 |  * @category time
 | 
        
           |  |  | 1708 |  * @param int $year The year part to create timestamp of
 | 
        
           |  |  | 1709 |  * @param int $month The month part to create timestamp of
 | 
        
           |  |  | 1710 |  * @param int $day The day part to create timestamp of
 | 
        
           |  |  | 1711 |  * @param int $hour The hour part to create timestamp of
 | 
        
           |  |  | 1712 |  * @param int $minute The minute part to create timestamp of
 | 
        
           |  |  | 1713 |  * @param int $second The second part to create timestamp of
 | 
        
           |  |  | 1714 |  * @param int|float|string $timezone Timezone modifier, used to calculate GMT time offset.
 | 
        
           |  |  | 1715 |  *             if 99 then default user's timezone is used {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
 | 
        
           |  |  | 1716 |  * @param bool $applydst Toggle Daylight Saving Time, default true, will be
 | 
        
           |  |  | 1717 |  *             applied only if timezone is 99 or string.
 | 
        
           |  |  | 1718 |  * @return int GMT timestamp
 | 
        
           |  |  | 1719 |  */
 | 
        
           | 1326 | ariadna | 1720 | function make_timestamp($year, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $timezone = 99, $applydst = true)
 | 
        
           |  |  | 1721 | {
 | 
        
           | 1 | efrain | 1722 |     $date = new DateTime('now', core_date::get_user_timezone_object($timezone));
 | 
        
           |  |  | 1723 |     $date->setDate((int)$year, (int)$month, (int)$day);
 | 
        
           |  |  | 1724 |     $date->setTime((int)$hour, (int)$minute, (int)$second);
 | 
        
           |  |  | 1725 |   | 
        
           |  |  | 1726 |     $time = $date->getTimestamp();
 | 
        
           |  |  | 1727 |   | 
        
           |  |  | 1728 |     if ($time === false) {
 | 
        
           | 1326 | ariadna | 1729 |         throw new coding_exception('getTimestamp() returned false, please ensure you have passed correct values.' .
 | 
        
           | 1 | efrain | 1730 |             ' This can fail if year is more than 2038 and OS is 32 bit windows');
 | 
        
           |  |  | 1731 |     }
 | 
        
           |  |  | 1732 |   | 
        
           |  |  | 1733 |     // Moodle BC DST stuff.
 | 
        
           |  |  | 1734 |     if (!$applydst) {
 | 
        
           |  |  | 1735 |         $time += dst_offset_on($time, $timezone);
 | 
        
           |  |  | 1736 |     }
 | 
        
           |  |  | 1737 |   | 
        
           |  |  | 1738 |     return $time;
 | 
        
           |  |  | 1739 | }
 | 
        
           |  |  | 1740 |   | 
        
           |  |  | 1741 | /**
 | 
        
           |  |  | 1742 |  * Format a date/time (seconds) as weeks, days, hours etc as needed
 | 
        
           |  |  | 1743 |  *
 | 
        
           |  |  | 1744 |  * Given an amount of time in seconds, returns string
 | 
        
           |  |  | 1745 |  * formatted nicely as years, days, hours etc as needed
 | 
        
           |  |  | 1746 |  *
 | 
        
           |  |  | 1747 |  * @package core
 | 
        
           |  |  | 1748 |  * @category time
 | 
        
           |  |  | 1749 |  * @uses MINSECS
 | 
        
           |  |  | 1750 |  * @uses HOURSECS
 | 
        
           |  |  | 1751 |  * @uses DAYSECS
 | 
        
           |  |  | 1752 |  * @uses YEARSECS
 | 
        
           |  |  | 1753 |  * @param int $totalsecs Time in seconds
 | 
        
           |  |  | 1754 |  * @param stdClass $str Should be a time object
 | 
        
           |  |  | 1755 |  * @return string A nicely formatted date/time string
 | 
        
           |  |  | 1756 |  */
 | 
        
           | 1326 | ariadna | 1757 | function format_time($totalsecs, $str = null)
 | 
        
           |  |  | 1758 | {
 | 
        
           | 1 | efrain | 1759 |   | 
        
           |  |  | 1760 |     $totalsecs = abs($totalsecs);
 | 
        
           |  |  | 1761 |   | 
        
           |  |  | 1762 |     if (!$str) {
 | 
        
           |  |  | 1763 |         // Create the str structure the slow way.
 | 
        
           |  |  | 1764 |         $str = new stdClass();
 | 
        
           |  |  | 1765 |         $str->day   = get_string('day');
 | 
        
           |  |  | 1766 |         $str->days  = get_string('days');
 | 
        
           |  |  | 1767 |         $str->hour  = get_string('hour');
 | 
        
           |  |  | 1768 |         $str->hours = get_string('hours');
 | 
        
           |  |  | 1769 |         $str->min   = get_string('min');
 | 
        
           |  |  | 1770 |         $str->mins  = get_string('mins');
 | 
        
           |  |  | 1771 |         $str->sec   = get_string('sec');
 | 
        
           |  |  | 1772 |         $str->secs  = get_string('secs');
 | 
        
           |  |  | 1773 |         $str->year  = get_string('year');
 | 
        
           |  |  | 1774 |         $str->years = get_string('years');
 | 
        
           |  |  | 1775 |     }
 | 
        
           |  |  | 1776 |   | 
        
           | 1326 | ariadna | 1777 |     $years     = floor($totalsecs / YEARSECS);
 | 
        
           |  |  | 1778 |     $remainder = $totalsecs - ($years * YEARSECS);
 | 
        
           |  |  | 1779 |     $days      = floor($remainder / DAYSECS);
 | 
        
           |  |  | 1780 |     $remainder = $totalsecs - ($days * DAYSECS);
 | 
        
           |  |  | 1781 |     $hours     = floor($remainder / HOURSECS);
 | 
        
           |  |  | 1782 |     $remainder = $remainder - ($hours * HOURSECS);
 | 
        
           |  |  | 1783 |     $mins      = floor($remainder / MINSECS);
 | 
        
           |  |  | 1784 |     $secs      = $remainder - ($mins * MINSECS);
 | 
        
           | 1 | efrain | 1785 |   | 
        
           |  |  | 1786 |     $ss = ($secs == 1)  ? $str->sec  : $str->secs;
 | 
        
           |  |  | 1787 |     $sm = ($mins == 1)  ? $str->min  : $str->mins;
 | 
        
           |  |  | 1788 |     $sh = ($hours == 1) ? $str->hour : $str->hours;
 | 
        
           |  |  | 1789 |     $sd = ($days == 1)  ? $str->day  : $str->days;
 | 
        
           |  |  | 1790 |     $sy = ($years == 1)  ? $str->year  : $str->years;
 | 
        
           |  |  | 1791 |   | 
        
           |  |  | 1792 |     $oyears = '';
 | 
        
           |  |  | 1793 |     $odays = '';
 | 
        
           |  |  | 1794 |     $ohours = '';
 | 
        
           |  |  | 1795 |     $omins = '';
 | 
        
           |  |  | 1796 |     $osecs = '';
 | 
        
           |  |  | 1797 |   | 
        
           |  |  | 1798 |     if ($years) {
 | 
        
           | 1326 | ariadna | 1799 |         $oyears  = $years . ' ' . $sy;
 | 
        
           | 1 | efrain | 1800 |     }
 | 
        
           |  |  | 1801 |     if ($days) {
 | 
        
           | 1326 | ariadna | 1802 |         $odays  = $days . ' ' . $sd;
 | 
        
           | 1 | efrain | 1803 |     }
 | 
        
           |  |  | 1804 |     if ($hours) {
 | 
        
           | 1326 | ariadna | 1805 |         $ohours = $hours . ' ' . $sh;
 | 
        
           | 1 | efrain | 1806 |     }
 | 
        
           |  |  | 1807 |     if ($mins) {
 | 
        
           | 1326 | ariadna | 1808 |         $omins  = $mins . ' ' . $sm;
 | 
        
           | 1 | efrain | 1809 |     }
 | 
        
           |  |  | 1810 |     if ($secs) {
 | 
        
           | 1326 | ariadna | 1811 |         $osecs  = $secs . ' ' . $ss;
 | 
        
           | 1 | efrain | 1812 |     }
 | 
        
           |  |  | 1813 |   | 
        
           |  |  | 1814 |     if ($years) {
 | 
        
           | 1326 | ariadna | 1815 |         return trim($oyears . ' ' . $odays);
 | 
        
           | 1 | efrain | 1816 |     }
 | 
        
           |  |  | 1817 |     if ($days) {
 | 
        
           | 1326 | ariadna | 1818 |         return trim($odays . ' ' . $ohours);
 | 
        
           | 1 | efrain | 1819 |     }
 | 
        
           |  |  | 1820 |     if ($hours) {
 | 
        
           | 1326 | ariadna | 1821 |         return trim($ohours . ' ' . $omins);
 | 
        
           | 1 | efrain | 1822 |     }
 | 
        
           |  |  | 1823 |     if ($mins) {
 | 
        
           | 1326 | ariadna | 1824 |         return trim($omins . ' ' . $osecs);
 | 
        
           | 1 | efrain | 1825 |     }
 | 
        
           |  |  | 1826 |     if ($secs) {
 | 
        
           |  |  | 1827 |         return $osecs;
 | 
        
           |  |  | 1828 |     }
 | 
        
           |  |  | 1829 |     return get_string('now');
 | 
        
           |  |  | 1830 | }
 | 
        
           |  |  | 1831 |   | 
        
           |  |  | 1832 | /**
 | 
        
           |  |  | 1833 |  * Returns a formatted string that represents a date in user time.
 | 
        
           |  |  | 1834 |  *
 | 
        
           |  |  | 1835 |  * @package core
 | 
        
           |  |  | 1836 |  * @category time
 | 
        
           |  |  | 1837 |  * @param int $date the timestamp in UTC, as obtained from the database.
 | 
        
           |  |  | 1838 |  * @param string $format strftime format. You should probably get this using
 | 
        
           |  |  | 1839 |  *        get_string('strftime...', 'langconfig');
 | 
        
           |  |  | 1840 |  * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
 | 
        
           |  |  | 1841 |  *        not 99 then daylight saving will not be added.
 | 
        
           |  |  | 1842 |  *        {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
 | 
        
           |  |  | 1843 |  * @param bool $fixday If true (default) then the leading zero from %d is removed.
 | 
        
           |  |  | 1844 |  *        If false then the leading zero is maintained.
 | 
        
           |  |  | 1845 |  * @param bool $fixhour If true (default) then the leading zero from %I is removed.
 | 
        
           |  |  | 1846 |  * @return string the formatted date/time.
 | 
        
           |  |  | 1847 |  */
 | 
        
           | 1326 | ariadna | 1848 | function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true)
 | 
        
           |  |  | 1849 | {
 | 
        
           | 1 | efrain | 1850 |     $calendartype = \core_calendar\type_factory::get_calendar_instance();
 | 
        
           |  |  | 1851 |     return $calendartype->timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour);
 | 
        
           |  |  | 1852 | }
 | 
        
           |  |  | 1853 |   | 
        
           |  |  | 1854 | /**
 | 
        
           |  |  | 1855 |  * Returns a html "time" tag with both the exact user date with timezone information
 | 
        
           |  |  | 1856 |  * as a datetime attribute in the W3C format, and the user readable date and time as text.
 | 
        
           |  |  | 1857 |  *
 | 
        
           |  |  | 1858 |  * @package core
 | 
        
           |  |  | 1859 |  * @category time
 | 
        
           |  |  | 1860 |  * @param int $date the timestamp in UTC, as obtained from the database.
 | 
        
           |  |  | 1861 |  * @param string $format strftime format. You should probably get this using
 | 
        
           |  |  | 1862 |  *        get_string('strftime...', 'langconfig');
 | 
        
           |  |  | 1863 |  * @param int|float|string $timezone by default, uses the user's time zone. if numeric and
 | 
        
           |  |  | 1864 |  *        not 99 then daylight saving will not be added.
 | 
        
           |  |  | 1865 |  *        {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
 | 
        
           |  |  | 1866 |  * @param bool $fixday If true (default) then the leading zero from %d is removed.
 | 
        
           |  |  | 1867 |  *        If false then the leading zero is maintained.
 | 
        
           |  |  | 1868 |  * @param bool $fixhour If true (default) then the leading zero from %I is removed.
 | 
        
           |  |  | 1869 |  * @return string the formatted date/time.
 | 
        
           |  |  | 1870 |  */
 | 
        
           | 1326 | ariadna | 1871 | function userdate_htmltime($date, $format = '', $timezone = 99, $fixday = true, $fixhour = true)
 | 
        
           |  |  | 1872 | {
 | 
        
           | 1 | efrain | 1873 |     $userdatestr = userdate($date, $format, $timezone, $fixday, $fixhour);
 | 
        
           |  |  | 1874 |     if (CLI_SCRIPT && !PHPUNIT_TEST) {
 | 
        
           |  |  | 1875 |         return $userdatestr;
 | 
        
           |  |  | 1876 |     }
 | 
        
           |  |  | 1877 |     $machinedate = new DateTime();
 | 
        
           |  |  | 1878 |     $machinedate->setTimestamp(intval($date));
 | 
        
           |  |  | 1879 |     $machinedate->setTimezone(core_date::get_user_timezone_object());
 | 
        
           |  |  | 1880 |   | 
        
           |  |  | 1881 |     return html_writer::tag('time', $userdatestr, ['datetime' => $machinedate->format(DateTime::W3C)]);
 | 
        
           |  |  | 1882 | }
 | 
        
           |  |  | 1883 |   | 
        
           |  |  | 1884 | /**
 | 
        
           |  |  | 1885 |  * Returns a formatted date ensuring it is UTF-8.
 | 
        
           |  |  | 1886 |  *
 | 
        
           |  |  | 1887 |  * If we are running under Windows convert to Windows encoding and then back to UTF-8
 | 
        
           |  |  | 1888 |  * (because it's impossible to specify UTF-8 to fetch locale info in Win32).
 | 
        
           |  |  | 1889 |  *
 | 
        
           |  |  | 1890 |  * @param int $date the timestamp - since Moodle 2.9 this is a real UTC timestamp
 | 
        
           |  |  | 1891 |  * @param string $format strftime format.
 | 
        
           |  |  | 1892 |  * @param int|float|string $tz the user timezone
 | 
        
           |  |  | 1893 |  * @return string the formatted date/time.
 | 
        
           |  |  | 1894 |  * @since Moodle 2.3.3
 | 
        
           |  |  | 1895 |  */
 | 
        
           | 1326 | ariadna | 1896 | function date_format_string($date, $format, $tz = 99)
 | 
        
           |  |  | 1897 | {
 | 
        
           | 1 | efrain | 1898 |   | 
        
           |  |  | 1899 |     date_default_timezone_set(core_date::get_user_timezone($tz));
 | 
        
           |  |  | 1900 |   | 
        
           |  |  | 1901 |     if (date('A', 0) === date('A', HOURSECS * 18)) {
 | 
        
           |  |  | 1902 |         $datearray = getdate($date);
 | 
        
           |  |  | 1903 |         $format = str_replace([
 | 
        
           |  |  | 1904 |             '%P',
 | 
        
           |  |  | 1905 |             '%p',
 | 
        
           |  |  | 1906 |         ], [
 | 
        
           |  |  | 1907 |             $datearray['hours'] < 12 ? get_string('am', 'langconfig') : get_string('pm', 'langconfig'),
 | 
        
           |  |  | 1908 |             $datearray['hours'] < 12 ? get_string('amcaps', 'langconfig') : get_string('pmcaps', 'langconfig'),
 | 
        
           |  |  | 1909 |         ], $format);
 | 
        
           |  |  | 1910 |     }
 | 
        
           |  |  | 1911 |   | 
        
           |  |  | 1912 |     $datestring = core_date::strftime($format, $date);
 | 
        
           |  |  | 1913 |     core_date::set_default_server_timezone();
 | 
        
           |  |  | 1914 |   | 
        
           |  |  | 1915 |     return $datestring;
 | 
        
           |  |  | 1916 | }
 | 
        
           |  |  | 1917 |   | 
        
           |  |  | 1918 | /**
 | 
        
           |  |  | 1919 |  * Given a $time timestamp in GMT (seconds since epoch),
 | 
        
           |  |  | 1920 |  * returns an array that represents the Gregorian date in user time
 | 
        
           |  |  | 1921 |  *
 | 
        
           |  |  | 1922 |  * @package core
 | 
        
           |  |  | 1923 |  * @category time
 | 
        
           |  |  | 1924 |  * @param int $time Timestamp in GMT
 | 
        
           |  |  | 1925 |  * @param float|int|string $timezone user timezone
 | 
        
           |  |  | 1926 |  * @return array An array that represents the date in user time
 | 
        
           |  |  | 1927 |  */
 | 
        
           | 1326 | ariadna | 1928 | function usergetdate($time, $timezone = 99)
 | 
        
           |  |  | 1929 | {
 | 
        
           | 1 | efrain | 1930 |     if ($time === null) {
 | 
        
           |  |  | 1931 |         // PHP8 and PHP7 return different results when getdate(null) is called.
 | 
        
           |  |  | 1932 |         // Display warning and cast to 0 to make sure the usergetdate() behaves consistently on all versions of PHP.
 | 
        
           |  |  | 1933 |         // In the future versions of Moodle we may consider adding a strict typehint.
 | 
        
           |  |  | 1934 |         debugging('usergetdate() expects parameter $time to be int, null given', DEBUG_DEVELOPER);
 | 
        
           |  |  | 1935 |         $time = 0;
 | 
        
           |  |  | 1936 |     }
 | 
        
           |  |  | 1937 |   | 
        
           |  |  | 1938 |     date_default_timezone_set(core_date::get_user_timezone($timezone));
 | 
        
           |  |  | 1939 |     $result = getdate($time);
 | 
        
           |  |  | 1940 |     core_date::set_default_server_timezone();
 | 
        
           |  |  | 1941 |   | 
        
           |  |  | 1942 |     return $result;
 | 
        
           |  |  | 1943 | }
 | 
        
           |  |  | 1944 |   | 
        
           |  |  | 1945 | /**
 | 
        
           |  |  | 1946 |  * Given a GMT timestamp (seconds since epoch), offsets it by
 | 
        
           |  |  | 1947 |  * the timezone.  eg 3pm in India is 3pm GMT - 7 * 3600 seconds
 | 
        
           |  |  | 1948 |  *
 | 
        
           |  |  | 1949 |  * NOTE: this function does not include DST properly,
 | 
        
           |  |  | 1950 |  *       you should use the PHP date stuff instead!
 | 
        
           |  |  | 1951 |  *
 | 
        
           |  |  | 1952 |  * @package core
 | 
        
           |  |  | 1953 |  * @category time
 | 
        
           |  |  | 1954 |  * @param int $date Timestamp in GMT
 | 
        
           |  |  | 1955 |  * @param float|int|string $timezone user timezone
 | 
        
           |  |  | 1956 |  * @return int
 | 
        
           |  |  | 1957 |  */
 | 
        
           | 1326 | ariadna | 1958 | function usertime($date, $timezone = 99)
 | 
        
           |  |  | 1959 | {
 | 
        
           | 1 | efrain | 1960 |     $userdate = new DateTime('@' . $date);
 | 
        
           |  |  | 1961 |     $userdate->setTimezone(core_date::get_user_timezone_object($timezone));
 | 
        
           |  |  | 1962 |     $dst = dst_offset_on($date, $timezone);
 | 
        
           |  |  | 1963 |   | 
        
           |  |  | 1964 |     return $date - $userdate->getOffset() + $dst;
 | 
        
           |  |  | 1965 | }
 | 
        
           |  |  | 1966 |   | 
        
           |  |  | 1967 | /**
 | 
        
           |  |  | 1968 |  * Get a formatted string representation of an interval between two unix timestamps.
 | 
        
           |  |  | 1969 |  *
 | 
        
           |  |  | 1970 |  * E.g.
 | 
        
           |  |  | 1971 |  * $intervalstring = get_time_interval_string(12345600, 12345660);
 | 
        
           |  |  | 1972 |  * Will produce the string:
 | 
        
           |  |  | 1973 |  * '0d 0h 1m'
 | 
        
           |  |  | 1974 |  *
 | 
        
           |  |  | 1975 |  * @param int $time1 unix timestamp
 | 
        
           |  |  | 1976 |  * @param int $time2 unix timestamp
 | 
        
           |  |  | 1977 |  * @param string $format string (can be lang string) containing format chars: https://www.php.net/manual/en/dateinterval.format.php.
 | 
        
           |  |  | 1978 |  * @param bool $dropzeroes If format is not provided and this is set to true, do not include zero time units.
 | 
        
           |  |  | 1979 |  *                         e.g. a duration of 3 days and 2 hours will be displayed as '3d 2h' instead of '3d 2h 0s'
 | 
        
           |  |  | 1980 |  * @param bool $fullformat If format is not provided and this is set to true, display time units in full format.
 | 
        
           |  |  | 1981 |  *                         e.g. instead of showing "3d", "3 days" will be returned.
 | 
        
           |  |  | 1982 |  * @return string the formatted string describing the time difference, e.g. '10d 11h 45m'.
 | 
        
           |  |  | 1983 |  */
 | 
        
           | 1326 | ariadna | 1984 | function get_time_interval_string(
 | 
        
           |  |  | 1985 |     int $time1,
 | 
        
           |  |  | 1986 |     int $time2,
 | 
        
           |  |  | 1987 |     string $format = '',
 | 
        
           |  |  | 1988 |     bool $dropzeroes = false,
 | 
        
           |  |  | 1989 |     bool $fullformat = false
 | 
        
           |  |  | 1990 | ): string {
 | 
        
           | 1 | efrain | 1991 |     $dtdate = new DateTime();
 | 
        
           |  |  | 1992 |     $dtdate->setTimeStamp($time1);
 | 
        
           |  |  | 1993 |     $dtdate2 = new DateTime();
 | 
        
           |  |  | 1994 |     $dtdate2->setTimeStamp($time2);
 | 
        
           |  |  | 1995 |     $interval = $dtdate2->diff($dtdate);
 | 
        
           |  |  | 1996 |   | 
        
           |  |  | 1997 |     if (empty(trim($format))) {
 | 
        
           |  |  | 1998 |         // Default to this key.
 | 
        
           |  |  | 1999 |         $formatkey = 'dateintervaldayhrmin';
 | 
        
           |  |  | 2000 |   | 
        
           |  |  | 2001 |         if ($dropzeroes) {
 | 
        
           |  |  | 2002 |             $units = [
 | 
        
           |  |  | 2003 |                 'y' => 'yr',
 | 
        
           |  |  | 2004 |                 'm' => 'mo',
 | 
        
           |  |  | 2005 |                 'd' => 'day',
 | 
        
           |  |  | 2006 |                 'h' => 'hr',
 | 
        
           |  |  | 2007 |                 'i' => 'min',
 | 
        
           |  |  | 2008 |                 's' => 'sec',
 | 
        
           |  |  | 2009 |             ];
 | 
        
           |  |  | 2010 |             $formatunits = [];
 | 
        
           |  |  | 2011 |             foreach ($units as $key => $unit) {
 | 
        
           |  |  | 2012 |                 if (empty($interval->$key)) {
 | 
        
           |  |  | 2013 |                     continue;
 | 
        
           |  |  | 2014 |                 }
 | 
        
           |  |  | 2015 |                 $formatunits[] = $unit;
 | 
        
           |  |  | 2016 |             }
 | 
        
           |  |  | 2017 |             if (!empty($formatunits)) {
 | 
        
           |  |  | 2018 |                 $formatkey = 'dateinterval' . implode("", $formatunits);
 | 
        
           |  |  | 2019 |             }
 | 
        
           |  |  | 2020 |         }
 | 
        
           |  |  | 2021 |   | 
        
           |  |  | 2022 |         if ($fullformat) {
 | 
        
           |  |  | 2023 |             $formatkey .= 'full';
 | 
        
           |  |  | 2024 |         }
 | 
        
           |  |  | 2025 |         $format = get_string($formatkey, 'langconfig');
 | 
        
           |  |  | 2026 |     }
 | 
        
           |  |  | 2027 |     return $interval->format($format);
 | 
        
           |  |  | 2028 | }
 | 
        
           |  |  | 2029 |   | 
        
           |  |  | 2030 | /**
 | 
        
           |  |  | 2031 |  * Given a time, return the GMT timestamp of the most recent midnight
 | 
        
           |  |  | 2032 |  * for the current user.
 | 
        
           |  |  | 2033 |  *
 | 
        
           |  |  | 2034 |  * @package core
 | 
        
           |  |  | 2035 |  * @category time
 | 
        
           |  |  | 2036 |  * @param int $date Timestamp in GMT
 | 
        
           |  |  | 2037 |  * @param float|int|string $timezone user timezone
 | 
        
           |  |  | 2038 |  * @return int Returns a GMT timestamp
 | 
        
           |  |  | 2039 |  */
 | 
        
           | 1326 | ariadna | 2040 | function usergetmidnight($date, $timezone = 99)
 | 
        
           |  |  | 2041 | {
 | 
        
           | 1 | efrain | 2042 |   | 
        
           |  |  | 2043 |     $userdate = usergetdate($date, $timezone);
 | 
        
           |  |  | 2044 |   | 
        
           |  |  | 2045 |     // Time of midnight of this user's day, in GMT.
 | 
        
           |  |  | 2046 |     return make_timestamp($userdate['year'], $userdate['mon'], $userdate['mday'], 0, 0, 0, $timezone);
 | 
        
           |  |  | 2047 | }
 | 
        
           |  |  | 2048 |   | 
        
           |  |  | 2049 | /**
 | 
        
           |  |  | 2050 |  * Returns a string that prints the user's timezone
 | 
        
           |  |  | 2051 |  *
 | 
        
           |  |  | 2052 |  * @package core
 | 
        
           |  |  | 2053 |  * @category time
 | 
        
           |  |  | 2054 |  * @param float|int|string $timezone user timezone
 | 
        
           |  |  | 2055 |  * @return string
 | 
        
           |  |  | 2056 |  */
 | 
        
           | 1326 | ariadna | 2057 | function usertimezone($timezone = 99)
 | 
        
           |  |  | 2058 | {
 | 
        
           | 1 | efrain | 2059 |     $tz = core_date::get_user_timezone($timezone);
 | 
        
           |  |  | 2060 |     return core_date::get_localised_timezone($tz);
 | 
        
           |  |  | 2061 | }
 | 
        
           |  |  | 2062 |   | 
        
           |  |  | 2063 | /**
 | 
        
           |  |  | 2064 |  * Returns a float or a string which denotes the user's timezone
 | 
        
           |  |  | 2065 |  * A float value means that a simple offset from GMT is used, while a string (it will be the name of a timezone in the database)
 | 
        
           |  |  | 2066 |  * means that for this timezone there are also DST rules to be taken into account
 | 
        
           |  |  | 2067 |  * Checks various settings and picks the most dominant of those which have a value
 | 
        
           |  |  | 2068 |  *
 | 
        
           |  |  | 2069 |  * @package core
 | 
        
           |  |  | 2070 |  * @category time
 | 
        
           |  |  | 2071 |  * @param float|int|string $tz timezone to calculate GMT time offset before
 | 
        
           |  |  | 2072 |  *        calculating user timezone, 99 is default user timezone
 | 
        
           |  |  | 2073 |  *        {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
 | 
        
           |  |  | 2074 |  * @return float|string
 | 
        
           |  |  | 2075 |  */
 | 
        
           | 1326 | ariadna | 2076 | function get_user_timezone($tz = 99)
 | 
        
           |  |  | 2077 | {
 | 
        
           | 1 | efrain | 2078 |     global $USER, $CFG;
 | 
        
           |  |  | 2079 |   | 
        
           |  |  | 2080 |     $timezones = array(
 | 
        
           |  |  | 2081 |         $tz,
 | 
        
           |  |  | 2082 |         isset($CFG->forcetimezone) ? $CFG->forcetimezone : 99,
 | 
        
           |  |  | 2083 |         isset($USER->timezone) ? $USER->timezone : 99,
 | 
        
           |  |  | 2084 |         isset($CFG->timezone) ? $CFG->timezone : 99,
 | 
        
           | 1326 | ariadna | 2085 |     );
 | 
        
           | 1 | efrain | 2086 |   | 
        
           |  |  | 2087 |     $tz = 99;
 | 
        
           |  |  | 2088 |   | 
        
           |  |  | 2089 |     // Loop while $tz is, empty but not zero, or 99, and there is another timezone is the array.
 | 
        
           |  |  | 2090 |     foreach ($timezones as $nextvalue) {
 | 
        
           |  |  | 2091 |         if ((empty($tz) && !is_numeric($tz)) || $tz == 99) {
 | 
        
           |  |  | 2092 |             $tz = $nextvalue;
 | 
        
           |  |  | 2093 |         }
 | 
        
           |  |  | 2094 |     }
 | 
        
           |  |  | 2095 |     return is_numeric($tz) ? (float) $tz : $tz;
 | 
        
           |  |  | 2096 | }
 | 
        
           |  |  | 2097 |   | 
        
           |  |  | 2098 | /**
 | 
        
           |  |  | 2099 |  * Calculates the Daylight Saving Offset for a given date/time (timestamp)
 | 
        
           |  |  | 2100 |  * - Note: Daylight saving only works for string timezones and not for float.
 | 
        
           |  |  | 2101 |  *
 | 
        
           |  |  | 2102 |  * @package core
 | 
        
           |  |  | 2103 |  * @category time
 | 
        
           |  |  | 2104 |  * @param int $time must NOT be compensated at all, it has to be a pure timestamp
 | 
        
           |  |  | 2105 |  * @param int|float|string $strtimezone user timezone
 | 
        
           |  |  | 2106 |  * @return int
 | 
        
           |  |  | 2107 |  */
 | 
        
           | 1326 | ariadna | 2108 | function dst_offset_on($time, $strtimezone = null)
 | 
        
           |  |  | 2109 | {
 | 
        
           | 1 | efrain | 2110 |     $tz = core_date::get_user_timezone($strtimezone);
 | 
        
           |  |  | 2111 |     $date = new DateTime('@' . $time);
 | 
        
           |  |  | 2112 |     $date->setTimezone(new DateTimeZone($tz));
 | 
        
           |  |  | 2113 |     if ($date->format('I') == '1') {
 | 
        
           |  |  | 2114 |         if ($tz === 'Australia/Lord_Howe') {
 | 
        
           |  |  | 2115 |             return 1800;
 | 
        
           |  |  | 2116 |         }
 | 
        
           |  |  | 2117 |         return 3600;
 | 
        
           |  |  | 2118 |     }
 | 
        
           |  |  | 2119 |     return 0;
 | 
        
           |  |  | 2120 | }
 | 
        
           |  |  | 2121 |   | 
        
           |  |  | 2122 | /**
 | 
        
           |  |  | 2123 |  * Calculates when the day appears in specific month
 | 
        
           |  |  | 2124 |  *
 | 
        
           |  |  | 2125 |  * @package core
 | 
        
           |  |  | 2126 |  * @category time
 | 
        
           |  |  | 2127 |  * @param int $startday starting day of the month
 | 
        
           |  |  | 2128 |  * @param int $weekday The day when week starts (normally taken from user preferences)
 | 
        
           |  |  | 2129 |  * @param int $month The month whose day is sought
 | 
        
           |  |  | 2130 |  * @param int $year The year of the month whose day is sought
 | 
        
           |  |  | 2131 |  * @return int
 | 
        
           |  |  | 2132 |  */
 | 
        
           | 1326 | ariadna | 2133 | function find_day_in_month($startday, $weekday, $month, $year)
 | 
        
           |  |  | 2134 | {
 | 
        
           | 1 | efrain | 2135 |     $calendartype = \core_calendar\type_factory::get_calendar_instance();
 | 
        
           |  |  | 2136 |   | 
        
           |  |  | 2137 |     $daysinmonth = days_in_month($month, $year);
 | 
        
           |  |  | 2138 |     $daysinweek = count($calendartype->get_weekdays());
 | 
        
           |  |  | 2139 |   | 
        
           |  |  | 2140 |     if ($weekday == -1) {
 | 
        
           |  |  | 2141 |         // Don't care about weekday, so return:
 | 
        
           |  |  | 2142 |         //    abs($startday) if $startday != -1
 | 
        
           |  |  | 2143 |         //    $daysinmonth otherwise.
 | 
        
           |  |  | 2144 |         return ($startday == -1) ? $daysinmonth : abs($startday);
 | 
        
           |  |  | 2145 |     }
 | 
        
           |  |  | 2146 |   | 
        
           |  |  | 2147 |     // From now on we 're looking for a specific weekday.
 | 
        
           |  |  | 2148 |     // Give "end of month" its actual value, since we know it.
 | 
        
           |  |  | 2149 |     if ($startday == -1) {
 | 
        
           |  |  | 2150 |         $startday = -1 * $daysinmonth;
 | 
        
           |  |  | 2151 |     }
 | 
        
           |  |  | 2152 |   | 
        
           |  |  | 2153 |     // Starting from day $startday, the sign is the direction.
 | 
        
           |  |  | 2154 |     if ($startday < 1) {
 | 
        
           |  |  | 2155 |         $startday = abs($startday);
 | 
        
           |  |  | 2156 |         $lastmonthweekday = dayofweek($daysinmonth, $month, $year);
 | 
        
           |  |  | 2157 |   | 
        
           |  |  | 2158 |         // This is the last such weekday of the month.
 | 
        
           |  |  | 2159 |         $lastinmonth = $daysinmonth + $weekday - $lastmonthweekday;
 | 
        
           |  |  | 2160 |         if ($lastinmonth > $daysinmonth) {
 | 
        
           |  |  | 2161 |             $lastinmonth -= $daysinweek;
 | 
        
           |  |  | 2162 |         }
 | 
        
           |  |  | 2163 |   | 
        
           |  |  | 2164 |         // Find the first such weekday <= $startday.
 | 
        
           |  |  | 2165 |         while ($lastinmonth > $startday) {
 | 
        
           |  |  | 2166 |             $lastinmonth -= $daysinweek;
 | 
        
           |  |  | 2167 |         }
 | 
        
           |  |  | 2168 |   | 
        
           |  |  | 2169 |         return $lastinmonth;
 | 
        
           |  |  | 2170 |     } else {
 | 
        
           |  |  | 2171 |         $indexweekday = dayofweek($startday, $month, $year);
 | 
        
           |  |  | 2172 |   | 
        
           |  |  | 2173 |         $diff = $weekday - $indexweekday;
 | 
        
           |  |  | 2174 |         if ($diff < 0) {
 | 
        
           |  |  | 2175 |             $diff += $daysinweek;
 | 
        
           |  |  | 2176 |         }
 | 
        
           |  |  | 2177 |   | 
        
           |  |  | 2178 |         // This is the first such weekday of the month equal to or after $startday.
 | 
        
           |  |  | 2179 |         $firstfromindex = $startday + $diff;
 | 
        
           |  |  | 2180 |   | 
        
           |  |  | 2181 |         return $firstfromindex;
 | 
        
           |  |  | 2182 |     }
 | 
        
           |  |  | 2183 | }
 | 
        
           |  |  | 2184 |   | 
        
           |  |  | 2185 | /**
 | 
        
           |  |  | 2186 |  * Calculate the number of days in a given month
 | 
        
           |  |  | 2187 |  *
 | 
        
           |  |  | 2188 |  * @package core
 | 
        
           |  |  | 2189 |  * @category time
 | 
        
           |  |  | 2190 |  * @param int $month The month whose day count is sought
 | 
        
           |  |  | 2191 |  * @param int $year The year of the month whose day count is sought
 | 
        
           |  |  | 2192 |  * @return int
 | 
        
           |  |  | 2193 |  */
 | 
        
           | 1326 | ariadna | 2194 | function days_in_month($month, $year)
 | 
        
           |  |  | 2195 | {
 | 
        
           | 1 | efrain | 2196 |     $calendartype = \core_calendar\type_factory::get_calendar_instance();
 | 
        
           |  |  | 2197 |     return $calendartype->get_num_days_in_month($year, $month);
 | 
        
           |  |  | 2198 | }
 | 
        
           |  |  | 2199 |   | 
        
           |  |  | 2200 | /**
 | 
        
           |  |  | 2201 |  * Calculate the position in the week of a specific calendar day
 | 
        
           |  |  | 2202 |  *
 | 
        
           |  |  | 2203 |  * @package core
 | 
        
           |  |  | 2204 |  * @category time
 | 
        
           |  |  | 2205 |  * @param int $day The day of the date whose position in the week is sought
 | 
        
           |  |  | 2206 |  * @param int $month The month of the date whose position in the week is sought
 | 
        
           |  |  | 2207 |  * @param int $year The year of the date whose position in the week is sought
 | 
        
           |  |  | 2208 |  * @return int
 | 
        
           |  |  | 2209 |  */
 | 
        
           | 1326 | ariadna | 2210 | function dayofweek($day, $month, $year)
 | 
        
           |  |  | 2211 | {
 | 
        
           | 1 | efrain | 2212 |     $calendartype = \core_calendar\type_factory::get_calendar_instance();
 | 
        
           |  |  | 2213 |     return $calendartype->get_weekday($year, $month, $day);
 | 
        
           |  |  | 2214 | }
 | 
        
           |  |  | 2215 |   | 
        
           |  |  | 2216 | // USER AUTHENTICATION AND LOGIN.
 | 
        
           |  |  | 2217 |   | 
        
           |  |  | 2218 | /**
 | 
        
           |  |  | 2219 |  * Returns full login url.
 | 
        
           |  |  | 2220 |  *
 | 
        
           |  |  | 2221 |  * Any form submissions for authentication to this URL must include username,
 | 
        
           |  |  | 2222 |  * password as well as a logintoken generated by \core\session\manager::get_login_token().
 | 
        
           |  |  | 2223 |  *
 | 
        
           |  |  | 2224 |  * @return string login url
 | 
        
           |  |  | 2225 |  */
 | 
        
           | 1326 | ariadna | 2226 | function get_login_url()
 | 
        
           |  |  | 2227 | {
 | 
        
           | 1 | efrain | 2228 |     global $CFG;
 | 
        
           |  |  | 2229 |   | 
        
           |  |  | 2230 |     return "$CFG->wwwroot/login/index.php";
 | 
        
           |  |  | 2231 | }
 | 
        
           |  |  | 2232 |   | 
        
           |  |  | 2233 | /**
 | 
        
           |  |  | 2234 |  * This function checks that the current user is logged in and has the
 | 
        
           |  |  | 2235 |  * required privileges
 | 
        
           |  |  | 2236 |  *
 | 
        
           |  |  | 2237 |  * This function checks that the current user is logged in, and optionally
 | 
        
           |  |  | 2238 |  * whether they are allowed to be in a particular course and view a particular
 | 
        
           |  |  | 2239 |  * course module.
 | 
        
           |  |  | 2240 |  * If they are not logged in, then it redirects them to the site login unless
 | 
        
           |  |  | 2241 |  * $autologinguest is set and {@link $CFG}->autologinguests is set to 1 in which
 | 
        
           |  |  | 2242 |  * case they are automatically logged in as guests.
 | 
        
           |  |  | 2243 |  * If $courseid is given and the user is not enrolled in that course then the
 | 
        
           |  |  | 2244 |  * user is redirected to the course enrolment page.
 | 
        
           |  |  | 2245 |  * If $cm is given and the course module is hidden and the user is not a teacher
 | 
        
           |  |  | 2246 |  * in the course then the user is redirected to the course home page.
 | 
        
           |  |  | 2247 |  *
 | 
        
           |  |  | 2248 |  * When $cm parameter specified, this function sets page layout to 'module'.
 | 
        
           |  |  | 2249 |  * You need to change it manually later if some other layout needed.
 | 
        
           |  |  | 2250 |  *
 | 
        
           |  |  | 2251 |  * @package    core_access
 | 
        
           |  |  | 2252 |  * @category   access
 | 
        
           |  |  | 2253 |  *
 | 
        
           |  |  | 2254 |  * @param mixed $courseorid id of the course or course object
 | 
        
           |  |  | 2255 |  * @param bool $autologinguest default true
 | 
        
           |  |  | 2256 |  * @param object $cm course module object
 | 
        
           |  |  | 2257 |  * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
 | 
        
           |  |  | 2258 |  *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
 | 
        
           |  |  | 2259 |  *             in order to keep redirects working properly. MDL-14495
 | 
        
           |  |  | 2260 |  * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
 | 
        
           |  |  | 2261 |  * @return mixed Void, exit, and die depending on path
 | 
        
           |  |  | 2262 |  * @throws coding_exception
 | 
        
           |  |  | 2263 |  * @throws require_login_exception
 | 
        
           |  |  | 2264 |  * @throws moodle_exception
 | 
        
           |  |  | 2265 |  */
 | 
        
           | 1326 | ariadna | 2266 | function require_login($courseorid = null, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false)
 | 
        
           |  |  | 2267 | {
 | 
        
           | 1 | efrain | 2268 |     global $CFG, $SESSION, $USER, $PAGE, $SITE, $DB, $OUTPUT;
 | 
        
           |  |  | 2269 |   | 
        
           |  |  | 2270 |     // Must not redirect when byteserving already started.
 | 
        
           |  |  | 2271 |     if (!empty($_SERVER['HTTP_RANGE'])) {
 | 
        
           |  |  | 2272 |         $preventredirect = true;
 | 
        
           |  |  | 2273 |     }
 | 
        
           |  |  | 2274 |   | 
        
           |  |  | 2275 |     if (AJAX_SCRIPT) {
 | 
        
           |  |  | 2276 |         // We cannot redirect for AJAX scripts either.
 | 
        
           |  |  | 2277 |         $preventredirect = true;
 | 
        
           |  |  | 2278 |     }
 | 
        
           |  |  | 2279 |   | 
        
           |  |  | 2280 |     // Setup global $COURSE, themes, language and locale.
 | 
        
           |  |  | 2281 |     if (!empty($courseorid)) {
 | 
        
           |  |  | 2282 |         if (is_object($courseorid)) {
 | 
        
           |  |  | 2283 |             $course = $courseorid;
 | 
        
           |  |  | 2284 |         } else if ($courseorid == SITEID) {
 | 
        
           | 1326 | ariadna | 2285 |             $course = clone ($SITE);
 | 
        
           | 1 | efrain | 2286 |         } else {
 | 
        
           |  |  | 2287 |             $course = $DB->get_record('course', array('id' => $courseorid), '*', MUST_EXIST);
 | 
        
           |  |  | 2288 |         }
 | 
        
           |  |  | 2289 |         if ($cm) {
 | 
        
           |  |  | 2290 |             if ($cm->course != $course->id) {
 | 
        
           |  |  | 2291 |                 throw new coding_exception('course and cm parameters in require_login() call do not match!!');
 | 
        
           |  |  | 2292 |             }
 | 
        
           |  |  | 2293 |             // Make sure we have a $cm from get_fast_modinfo as this contains activity access details.
 | 
        
           |  |  | 2294 |             if (!($cm instanceof cm_info)) {
 | 
        
           |  |  | 2295 |                 // Note: nearly all pages call get_fast_modinfo anyway and it does not make any
 | 
        
           |  |  | 2296 |                 // db queries so this is not really a performance concern, however it is obviously
 | 
        
           |  |  | 2297 |                 // better if you use get_fast_modinfo to get the cm before calling this.
 | 
        
           |  |  | 2298 |                 $modinfo = get_fast_modinfo($course);
 | 
        
           |  |  | 2299 |                 $cm = $modinfo->get_cm($cm->id);
 | 
        
           |  |  | 2300 |             }
 | 
        
           |  |  | 2301 |         }
 | 
        
           |  |  | 2302 |     } else {
 | 
        
           |  |  | 2303 |         // Do not touch global $COURSE via $PAGE->set_course(),
 | 
        
           |  |  | 2304 |         // the reasons is we need to be able to call require_login() at any time!!
 | 
        
           |  |  | 2305 |         $course = $SITE;
 | 
        
           |  |  | 2306 |         if ($cm) {
 | 
        
           |  |  | 2307 |             throw new coding_exception('cm parameter in require_login() requires valid course parameter!');
 | 
        
           |  |  | 2308 |         }
 | 
        
           |  |  | 2309 |     }
 | 
        
           |  |  | 2310 |   | 
        
           |  |  | 2311 |     // If this is an AJAX request and $setwantsurltome is true then we need to override it and set it to false.
 | 
        
           |  |  | 2312 |     // Otherwise the AJAX request URL will be set to $SESSION->wantsurl and events such as self enrolment in the future
 | 
        
           |  |  | 2313 |     // risk leading the user back to the AJAX request URL.
 | 
        
           |  |  | 2314 |     if ($setwantsurltome && defined('AJAX_SCRIPT') && AJAX_SCRIPT) {
 | 
        
           |  |  | 2315 |         $setwantsurltome = false;
 | 
        
           |  |  | 2316 |     }
 | 
        
           |  |  | 2317 |   | 
        
           |  |  | 2318 |     // Redirect to the login page if session has expired, only with dbsessions enabled (MDL-35029) to maintain current behaviour.
 | 
        
           |  |  | 2319 |     if ((!isloggedin() or isguestuser()) && !empty($SESSION->has_timed_out) && !empty($CFG->dbsessions)) {
 | 
        
           |  |  | 2320 |         if ($preventredirect) {
 | 
        
           |  |  | 2321 |             throw new require_login_session_timeout_exception();
 | 
        
           |  |  | 2322 |         } else {
 | 
        
           |  |  | 2323 |             if ($setwantsurltome) {
 | 
        
           |  |  | 2324 |                 $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2325 |             }
 | 
        
           |  |  | 2326 |             redirect(get_login_url());
 | 
        
           |  |  | 2327 |         }
 | 
        
           |  |  | 2328 |     }
 | 
        
           |  |  | 2329 |   | 
        
           |  |  | 2330 |     // If the user is not even logged in yet then make sure they are.
 | 
        
           |  |  | 2331 |     if (!isloggedin()) {
 | 
        
           |  |  | 2332 |         if ($autologinguest && !empty($CFG->autologinguests)) {
 | 
        
           |  |  | 2333 |             if (!$guest = get_complete_user_data('id', $CFG->siteguest)) {
 | 
        
           |  |  | 2334 |                 // Misconfigured site guest, just redirect to login page.
 | 
        
           |  |  | 2335 |                 redirect(get_login_url());
 | 
        
           |  |  | 2336 |                 exit; // Never reached.
 | 
        
           |  |  | 2337 |             }
 | 
        
           |  |  | 2338 |             $lang = isset($SESSION->lang) ? $SESSION->lang : $CFG->lang;
 | 
        
           |  |  | 2339 |             complete_user_login($guest);
 | 
        
           |  |  | 2340 |             $USER->autologinguest = true;
 | 
        
           |  |  | 2341 |             $SESSION->lang = $lang;
 | 
        
           |  |  | 2342 |         } else {
 | 
        
           |  |  | 2343 |             // NOTE: $USER->site check was obsoleted by session test cookie, $USER->confirmed test is in login/index.php.
 | 
        
           |  |  | 2344 |             if ($preventredirect) {
 | 
        
           |  |  | 2345 |                 throw new require_login_exception('You are not logged in');
 | 
        
           |  |  | 2346 |             }
 | 
        
           |  |  | 2347 |   | 
        
           |  |  | 2348 |             if ($setwantsurltome) {
 | 
        
           |  |  | 2349 |                 $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2350 |             }
 | 
        
           |  |  | 2351 |   | 
        
           |  |  | 2352 |             // Give auth plugins an opportunity to authenticate or redirect to an external login page
 | 
        
           |  |  | 2353 |             $authsequence = get_enabled_auth_plugins(); // Auths, in sequence.
 | 
        
           | 1326 | ariadna | 2354 |             foreach ($authsequence as $authname) {
 | 
        
           | 1 | efrain | 2355 |                 $authplugin = get_auth_plugin($authname);
 | 
        
           |  |  | 2356 |                 $authplugin->pre_loginpage_hook();
 | 
        
           |  |  | 2357 |                 if (isloggedin()) {
 | 
        
           |  |  | 2358 |                     if ($cm) {
 | 
        
           |  |  | 2359 |                         $modinfo = get_fast_modinfo($course);
 | 
        
           |  |  | 2360 |                         $cm = $modinfo->get_cm($cm->id);
 | 
        
           |  |  | 2361 |                     }
 | 
        
           |  |  | 2362 |                     set_access_log_user();
 | 
        
           |  |  | 2363 |                     break;
 | 
        
           |  |  | 2364 |                 }
 | 
        
           |  |  | 2365 |             }
 | 
        
           |  |  | 2366 |   | 
        
           |  |  | 2367 |             // If we're still not logged in then go to the login page
 | 
        
           |  |  | 2368 |             if (!isloggedin()) {
 | 
        
           |  |  | 2369 |                 redirect(get_login_url());
 | 
        
           |  |  | 2370 |                 exit; // Never reached.
 | 
        
           |  |  | 2371 |             }
 | 
        
           |  |  | 2372 |         }
 | 
        
           |  |  | 2373 |     }
 | 
        
           |  |  | 2374 |   | 
        
           |  |  | 2375 |     // Loginas as redirection if needed.
 | 
        
           |  |  | 2376 |     if ($course->id != SITEID and \core\session\manager::is_loggedinas()) {
 | 
        
           |  |  | 2377 |         if ($USER->loginascontext->contextlevel == CONTEXT_COURSE) {
 | 
        
           |  |  | 2378 |             if ($USER->loginascontext->instanceid != $course->id) {
 | 
        
           | 1326 | ariadna | 2379 |                 throw new \moodle_exception(
 | 
        
           |  |  | 2380 |                     'loginasonecourse',
 | 
        
           |  |  | 2381 |                     '',
 | 
        
           |  |  | 2382 |                     $CFG->wwwroot . '/course/view.php?id=' . $USER->loginascontext->instanceid
 | 
        
           |  |  | 2383 |                 );
 | 
        
           | 1 | efrain | 2384 |             }
 | 
        
           |  |  | 2385 |         }
 | 
        
           |  |  | 2386 |     }
 | 
        
           |  |  | 2387 |   | 
        
           |  |  | 2388 |     // Check whether the user should be changing password (but only if it is REALLY them).
 | 
        
           |  |  | 2389 |     if (get_user_preferences('auth_forcepasswordchange') && !\core\session\manager::is_loggedinas()) {
 | 
        
           |  |  | 2390 |         $userauth = get_auth_plugin($USER->auth);
 | 
        
           |  |  | 2391 |         if ($userauth->can_change_password() and !$preventredirect) {
 | 
        
           |  |  | 2392 |             if ($setwantsurltome) {
 | 
        
           |  |  | 2393 |                 $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2394 |             }
 | 
        
           |  |  | 2395 |             if ($changeurl = $userauth->change_password_url()) {
 | 
        
           |  |  | 2396 |                 // Use plugin custom url.
 | 
        
           |  |  | 2397 |                 redirect($changeurl);
 | 
        
           |  |  | 2398 |             } else {
 | 
        
           |  |  | 2399 |                 // Use moodle internal method.
 | 
        
           | 1326 | ariadna | 2400 |                 redirect($CFG->wwwroot . '/login/change_password.php');
 | 
        
           | 1 | efrain | 2401 |             }
 | 
        
           |  |  | 2402 |         } else if ($userauth->can_change_password()) {
 | 
        
           |  |  | 2403 |             throw new moodle_exception('forcepasswordchangenotice');
 | 
        
           |  |  | 2404 |         } else {
 | 
        
           |  |  | 2405 |             throw new moodle_exception('nopasswordchangeforced', 'auth');
 | 
        
           |  |  | 2406 |         }
 | 
        
           |  |  | 2407 |     }
 | 
        
           |  |  | 2408 |   | 
        
           |  |  | 2409 |     // Check that the user account is properly set up. If we can't redirect to
 | 
        
           |  |  | 2410 |     // edit their profile and this is not a WS request, perform just the lax check.
 | 
        
           |  |  | 2411 |     // It will allow them to use filepicker on the profile edit page.
 | 
        
           |  |  | 2412 |   | 
        
           |  |  | 2413 |     if ($preventredirect && !WS_SERVER) {
 | 
        
           |  |  | 2414 |         $usernotfullysetup = user_not_fully_set_up($USER, false);
 | 
        
           |  |  | 2415 |     } else {
 | 
        
           |  |  | 2416 |         $usernotfullysetup = user_not_fully_set_up($USER, true);
 | 
        
           |  |  | 2417 |     }
 | 
        
           |  |  | 2418 |   | 
        
           |  |  | 2419 |     if ($usernotfullysetup) {
 | 
        
           |  |  | 2420 |         if ($preventredirect) {
 | 
        
           |  |  | 2421 |             throw new moodle_exception('usernotfullysetup');
 | 
        
           |  |  | 2422 |         }
 | 
        
           |  |  | 2423 |         if ($setwantsurltome) {
 | 
        
           |  |  | 2424 |             $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2425 |         }
 | 
        
           | 1326 | ariadna | 2426 |         redirect($CFG->wwwroot . '/user/edit.php?id=' . $USER->id . '&course=' . SITEID);
 | 
        
           | 1 | efrain | 2427 |     }
 | 
        
           |  |  | 2428 |   | 
        
           |  |  | 2429 |     // Make sure the USER has a sesskey set up. Used for CSRF protection.
 | 
        
           |  |  | 2430 |     sesskey();
 | 
        
           |  |  | 2431 |   | 
        
           |  |  | 2432 |     if (\core\session\manager::is_loggedinas()) {
 | 
        
           |  |  | 2433 |         // During a "logged in as" session we should force all content to be cleaned because the
 | 
        
           |  |  | 2434 |         // logged in user will be viewing potentially malicious user generated content.
 | 
        
           |  |  | 2435 |         // See MDL-63786 for more details.
 | 
        
           |  |  | 2436 |         $CFG->forceclean = true;
 | 
        
           |  |  | 2437 |     }
 | 
        
           |  |  | 2438 |   | 
        
           |  |  | 2439 |     $afterlogins = get_plugins_with_function('after_require_login', 'lib.php');
 | 
        
           |  |  | 2440 |   | 
        
           |  |  | 2441 |     // Do not bother admins with any formalities, except for activities pending deletion.
 | 
        
           |  |  | 2442 |     if (is_siteadmin() && !($cm && $cm->deletioninprogress)) {
 | 
        
           |  |  | 2443 |         // Set the global $COURSE.
 | 
        
           |  |  | 2444 |         if ($cm) {
 | 
        
           |  |  | 2445 |             $PAGE->set_cm($cm, $course);
 | 
        
           |  |  | 2446 |             $PAGE->set_pagelayout('incourse');
 | 
        
           |  |  | 2447 |         } else if (!empty($courseorid)) {
 | 
        
           |  |  | 2448 |             $PAGE->set_course($course);
 | 
        
           |  |  | 2449 |         }
 | 
        
           |  |  | 2450 |         // Set accesstime or the user will appear offline which messes up messaging.
 | 
        
           |  |  | 2451 |         // Do not update access time for webservice or ajax requests.
 | 
        
           |  |  | 2452 |         if (!WS_SERVER && !AJAX_SCRIPT) {
 | 
        
           |  |  | 2453 |             user_accesstime_log($course->id);
 | 
        
           |  |  | 2454 |         }
 | 
        
           |  |  | 2455 |   | 
        
           |  |  | 2456 |         foreach ($afterlogins as $plugintype => $plugins) {
 | 
        
           |  |  | 2457 |             foreach ($plugins as $pluginfunction) {
 | 
        
           |  |  | 2458 |                 $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2459 |             }
 | 
        
           |  |  | 2460 |         }
 | 
        
           |  |  | 2461 |         return;
 | 
        
           |  |  | 2462 |     }
 | 
        
           |  |  | 2463 |   | 
        
           |  |  | 2464 |     // Scripts have a chance to declare that $USER->policyagreed should not be checked.
 | 
        
           |  |  | 2465 |     // This is mostly for places where users are actually accepting the policies, to avoid the redirect loop.
 | 
        
           |  |  | 2466 |     if (!defined('NO_SITEPOLICY_CHECK')) {
 | 
        
           |  |  | 2467 |         define('NO_SITEPOLICY_CHECK', false);
 | 
        
           |  |  | 2468 |     }
 | 
        
           |  |  | 2469 |   | 
        
           |  |  | 2470 |     // Check that the user has agreed to a site policy if there is one - do not test in case of admins.
 | 
        
           |  |  | 2471 |     // Do not test if the script explicitly asked for skipping the site policies check.
 | 
        
           |  |  | 2472 |     // Or if the user auth type is webservice.
 | 
        
           |  |  | 2473 |     if (!$USER->policyagreed && !is_siteadmin() && !NO_SITEPOLICY_CHECK && $USER->auth !== 'webservice') {
 | 
        
           |  |  | 2474 |         $manager = new \core_privacy\local\sitepolicy\manager();
 | 
        
           |  |  | 2475 |         if ($policyurl = $manager->get_redirect_url(isguestuser())) {
 | 
        
           |  |  | 2476 |             if ($preventredirect) {
 | 
        
           |  |  | 2477 |                 throw new moodle_exception('sitepolicynotagreed', 'error', '', $policyurl->out());
 | 
        
           |  |  | 2478 |             }
 | 
        
           |  |  | 2479 |             if ($setwantsurltome) {
 | 
        
           |  |  | 2480 |                 $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2481 |             }
 | 
        
           |  |  | 2482 |             redirect($policyurl);
 | 
        
           |  |  | 2483 |         }
 | 
        
           |  |  | 2484 |     }
 | 
        
           |  |  | 2485 |   | 
        
           |  |  | 2486 |     // Fetch the system context, the course context, and prefetch its child contexts.
 | 
        
           |  |  | 2487 |     $sysctx = context_system::instance();
 | 
        
           |  |  | 2488 |     $coursecontext = context_course::instance($course->id, MUST_EXIST);
 | 
        
           |  |  | 2489 |     if ($cm) {
 | 
        
           |  |  | 2490 |         $cmcontext = context_module::instance($cm->id, MUST_EXIST);
 | 
        
           |  |  | 2491 |     } else {
 | 
        
           |  |  | 2492 |         $cmcontext = null;
 | 
        
           |  |  | 2493 |     }
 | 
        
           |  |  | 2494 |   | 
        
           |  |  | 2495 |     // If the site is currently under maintenance, then print a message.
 | 
        
           |  |  | 2496 |     if (!empty($CFG->maintenance_enabled) and !has_capability('moodle/site:maintenanceaccess', $sysctx)) {
 | 
        
           |  |  | 2497 |         if ($preventredirect) {
 | 
        
           |  |  | 2498 |             throw new require_login_exception('Maintenance in progress');
 | 
        
           |  |  | 2499 |         }
 | 
        
           |  |  | 2500 |         $PAGE->set_context(null);
 | 
        
           |  |  | 2501 |         print_maintenance_message();
 | 
        
           |  |  | 2502 |     }
 | 
        
           |  |  | 2503 |   | 
        
           |  |  | 2504 |     // Make sure the course itself is not hidden.
 | 
        
           |  |  | 2505 |     if ($course->id == SITEID) {
 | 
        
           |  |  | 2506 |         // Frontpage can not be hidden.
 | 
        
           |  |  | 2507 |     } else {
 | 
        
           |  |  | 2508 |         if (is_role_switched($course->id)) {
 | 
        
           |  |  | 2509 |             // When switching roles ignore the hidden flag - user had to be in course to do the switch.
 | 
        
           |  |  | 2510 |         } else {
 | 
        
           |  |  | 2511 |             if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
 | 
        
           |  |  | 2512 |                 // Originally there was also test of parent category visibility, BUT is was very slow in complex queries
 | 
        
           |  |  | 2513 |                 // involving "my courses" now it is also possible to simply hide all courses user is not enrolled in :-).
 | 
        
           |  |  | 2514 |                 if ($preventredirect) {
 | 
        
           |  |  | 2515 |                     throw new require_login_exception('Course is hidden');
 | 
        
           |  |  | 2516 |                 }
 | 
        
           |  |  | 2517 |                 $PAGE->set_context(null);
 | 
        
           |  |  | 2518 |                 // We need to override the navigation URL as the course won't have been added to the navigation and thus
 | 
        
           |  |  | 2519 |                 // the navigation will mess up when trying to find it.
 | 
        
           |  |  | 2520 |                 navigation_node::override_active_url(new moodle_url('/'));
 | 
        
           | 1326 | ariadna | 2521 |                 notice(get_string('coursehidden'), $CFG->wwwroot . '/');
 | 
        
           | 1 | efrain | 2522 |             }
 | 
        
           |  |  | 2523 |         }
 | 
        
           |  |  | 2524 |     }
 | 
        
           |  |  | 2525 |   | 
        
           |  |  | 2526 |     // Is the user enrolled?
 | 
        
           |  |  | 2527 |     if ($course->id == SITEID) {
 | 
        
           |  |  | 2528 |         // Everybody is enrolled on the frontpage.
 | 
        
           |  |  | 2529 |     } else {
 | 
        
           |  |  | 2530 |         if (\core\session\manager::is_loggedinas()) {
 | 
        
           |  |  | 2531 |             // Make sure the REAL person can access this course first.
 | 
        
           |  |  | 2532 |             $realuser = \core\session\manager::get_realuser();
 | 
        
           | 1326 | ariadna | 2533 |             if (
 | 
        
           |  |  | 2534 |                 !is_enrolled($coursecontext, $realuser->id, '', true) and
 | 
        
           |  |  | 2535 |                 !is_viewing($coursecontext, $realuser->id) and !is_siteadmin($realuser->id)
 | 
        
           |  |  | 2536 |             ) {
 | 
        
           | 1 | efrain | 2537 |                 if ($preventredirect) {
 | 
        
           |  |  | 2538 |                     throw new require_login_exception('Invalid course login-as access');
 | 
        
           |  |  | 2539 |                 }
 | 
        
           |  |  | 2540 |                 $PAGE->set_context(null);
 | 
        
           |  |  | 2541 |                 echo $OUTPUT->header();
 | 
        
           | 1326 | ariadna | 2542 |                 notice(get_string('studentnotallowed', '', fullname($USER, true)), $CFG->wwwroot . '/');
 | 
        
           | 1 | efrain | 2543 |             }
 | 
        
           |  |  | 2544 |         }
 | 
        
           |  |  | 2545 |   | 
        
           |  |  | 2546 |         $access = false;
 | 
        
           |  |  | 2547 |   | 
        
           |  |  | 2548 |         if (is_role_switched($course->id)) {
 | 
        
           |  |  | 2549 |             // Ok, user had to be inside this course before the switch.
 | 
        
           |  |  | 2550 |             $access = true;
 | 
        
           |  |  | 2551 |         } else if (is_viewing($coursecontext, $USER)) {
 | 
        
           |  |  | 2552 |             // Ok, no need to mess with enrol.
 | 
        
           |  |  | 2553 |             $access = true;
 | 
        
           |  |  | 2554 |         } else {
 | 
        
           |  |  | 2555 |             if (isset($USER->enrol['enrolled'][$course->id])) {
 | 
        
           |  |  | 2556 |                 if ($USER->enrol['enrolled'][$course->id] > time()) {
 | 
        
           |  |  | 2557 |                     $access = true;
 | 
        
           |  |  | 2558 |                     if (isset($USER->enrol['tempguest'][$course->id])) {
 | 
        
           |  |  | 2559 |                         unset($USER->enrol['tempguest'][$course->id]);
 | 
        
           |  |  | 2560 |                         remove_temp_course_roles($coursecontext);
 | 
        
           |  |  | 2561 |                     }
 | 
        
           |  |  | 2562 |                 } else {
 | 
        
           |  |  | 2563 |                     // Expired.
 | 
        
           |  |  | 2564 |                     unset($USER->enrol['enrolled'][$course->id]);
 | 
        
           |  |  | 2565 |                 }
 | 
        
           |  |  | 2566 |             }
 | 
        
           |  |  | 2567 |             if (isset($USER->enrol['tempguest'][$course->id])) {
 | 
        
           |  |  | 2568 |                 if ($USER->enrol['tempguest'][$course->id] == 0) {
 | 
        
           |  |  | 2569 |                     $access = true;
 | 
        
           |  |  | 2570 |                 } else if ($USER->enrol['tempguest'][$course->id] > time()) {
 | 
        
           |  |  | 2571 |                     $access = true;
 | 
        
           |  |  | 2572 |                 } else {
 | 
        
           |  |  | 2573 |                     // Expired.
 | 
        
           |  |  | 2574 |                     unset($USER->enrol['tempguest'][$course->id]);
 | 
        
           |  |  | 2575 |                     remove_temp_course_roles($coursecontext);
 | 
        
           |  |  | 2576 |                 }
 | 
        
           |  |  | 2577 |             }
 | 
        
           |  |  | 2578 |   | 
        
           |  |  | 2579 |             if (!$access) {
 | 
        
           |  |  | 2580 |                 // Cache not ok.
 | 
        
           |  |  | 2581 |                 $until = enrol_get_enrolment_end($coursecontext->instanceid, $USER->id);
 | 
        
           |  |  | 2582 |                 if ($until !== false) {
 | 
        
           |  |  | 2583 |                     // Active participants may always access, a timestamp in the future, 0 (always) or false.
 | 
        
           |  |  | 2584 |                     if ($until == 0) {
 | 
        
           |  |  | 2585 |                         $until = ENROL_MAX_TIMESTAMP;
 | 
        
           |  |  | 2586 |                     }
 | 
        
           |  |  | 2587 |                     $USER->enrol['enrolled'][$course->id] = $until;
 | 
        
           |  |  | 2588 |                     $access = true;
 | 
        
           |  |  | 2589 |                 } else if (core_course_category::can_view_course_info($course)) {
 | 
        
           |  |  | 2590 |                     $params = array('courseid' => $course->id, 'status' => ENROL_INSTANCE_ENABLED);
 | 
        
           |  |  | 2591 |                     $instances = $DB->get_records('enrol', $params, 'sortorder, id ASC');
 | 
        
           |  |  | 2592 |                     $enrols = enrol_get_plugins(true);
 | 
        
           |  |  | 2593 |                     // First ask all enabled enrol instances in course if they want to auto enrol user.
 | 
        
           |  |  | 2594 |                     foreach ($instances as $instance) {
 | 
        
           |  |  | 2595 |                         if (!isset($enrols[$instance->enrol])) {
 | 
        
           |  |  | 2596 |                             continue;
 | 
        
           |  |  | 2597 |                         }
 | 
        
           |  |  | 2598 |                         // Get a duration for the enrolment, a timestamp in the future, 0 (always) or false.
 | 
        
           |  |  | 2599 |                         $until = $enrols[$instance->enrol]->try_autoenrol($instance);
 | 
        
           |  |  | 2600 |                         if ($until !== false) {
 | 
        
           |  |  | 2601 |                             if ($until == 0) {
 | 
        
           |  |  | 2602 |                                 $until = ENROL_MAX_TIMESTAMP;
 | 
        
           |  |  | 2603 |                             }
 | 
        
           |  |  | 2604 |                             $USER->enrol['enrolled'][$course->id] = $until;
 | 
        
           |  |  | 2605 |                             $access = true;
 | 
        
           |  |  | 2606 |                             break;
 | 
        
           |  |  | 2607 |                         }
 | 
        
           |  |  | 2608 |                     }
 | 
        
           |  |  | 2609 |                     // If not enrolled yet try to gain temporary guest access.
 | 
        
           |  |  | 2610 |                     if (!$access) {
 | 
        
           |  |  | 2611 |                         foreach ($instances as $instance) {
 | 
        
           |  |  | 2612 |                             if (!isset($enrols[$instance->enrol])) {
 | 
        
           |  |  | 2613 |                                 continue;
 | 
        
           |  |  | 2614 |                             }
 | 
        
           |  |  | 2615 |                             // Get a duration for the guest access, a timestamp in the future or false.
 | 
        
           |  |  | 2616 |                             $until = $enrols[$instance->enrol]->try_guestaccess($instance);
 | 
        
           |  |  | 2617 |                             if ($until !== false and $until > time()) {
 | 
        
           |  |  | 2618 |                                 $USER->enrol['tempguest'][$course->id] = $until;
 | 
        
           |  |  | 2619 |                                 $access = true;
 | 
        
           |  |  | 2620 |                                 break;
 | 
        
           |  |  | 2621 |                             }
 | 
        
           |  |  | 2622 |                         }
 | 
        
           |  |  | 2623 |                     }
 | 
        
           |  |  | 2624 |                 } else {
 | 
        
           |  |  | 2625 |                     // User is not enrolled and is not allowed to browse courses here.
 | 
        
           |  |  | 2626 |                     if ($preventredirect) {
 | 
        
           |  |  | 2627 |                         throw new require_login_exception('Course is not available');
 | 
        
           |  |  | 2628 |                     }
 | 
        
           |  |  | 2629 |                     $PAGE->set_context(null);
 | 
        
           |  |  | 2630 |                     // We need to override the navigation URL as the course won't have been added to the navigation and thus
 | 
        
           |  |  | 2631 |                     // the navigation will mess up when trying to find it.
 | 
        
           |  |  | 2632 |                     navigation_node::override_active_url(new moodle_url('/'));
 | 
        
           | 1326 | ariadna | 2633 |                     notice(get_string('coursehidden'), $CFG->wwwroot . '/');
 | 
        
           | 1 | efrain | 2634 |                 }
 | 
        
           |  |  | 2635 |             }
 | 
        
           |  |  | 2636 |         }
 | 
        
           |  |  | 2637 |   | 
        
           |  |  | 2638 |         if (!$access) {
 | 
        
           |  |  | 2639 |             if ($preventredirect) {
 | 
        
           |  |  | 2640 |                 throw new require_login_exception('Not enrolled');
 | 
        
           |  |  | 2641 |             }
 | 
        
           |  |  | 2642 |             if ($setwantsurltome) {
 | 
        
           |  |  | 2643 |                 $SESSION->wantsurl = qualified_me();
 | 
        
           |  |  | 2644 |             }
 | 
        
           | 1326 | ariadna | 2645 |             redirect($CFG->wwwroot . '/enrol/index.php?id=' . $course->id);
 | 
        
           | 1 | efrain | 2646 |         }
 | 
        
           |  |  | 2647 |     }
 | 
        
           |  |  | 2648 |   | 
        
           |  |  | 2649 |     // Check whether the activity has been scheduled for deletion. If so, then deny access, even for admins.
 | 
        
           |  |  | 2650 |     if ($cm && $cm->deletioninprogress) {
 | 
        
           |  |  | 2651 |         if ($preventredirect) {
 | 
        
           |  |  | 2652 |             throw new moodle_exception('activityisscheduledfordeletion');
 | 
        
           |  |  | 2653 |         }
 | 
        
           |  |  | 2654 |         require_once($CFG->dirroot . '/course/lib.php');
 | 
        
           |  |  | 2655 |         redirect(course_get_url($course), get_string('activityisscheduledfordeletion', 'error'));
 | 
        
           |  |  | 2656 |     }
 | 
        
           |  |  | 2657 |   | 
        
           |  |  | 2658 |     // Check visibility of activity to current user; includes visible flag, conditional availability, etc.
 | 
        
           |  |  | 2659 |     if ($cm && !$cm->uservisible) {
 | 
        
           |  |  | 2660 |         if ($preventredirect) {
 | 
        
           |  |  | 2661 |             throw new require_login_exception('Activity is hidden');
 | 
        
           |  |  | 2662 |         }
 | 
        
           |  |  | 2663 |         // Get the error message that activity is not available and why (if explanation can be shown to the user).
 | 
        
           |  |  | 2664 |         $PAGE->set_course($course);
 | 
        
           |  |  | 2665 |         $renderer = $PAGE->get_renderer('course');
 | 
        
           |  |  | 2666 |         $message = $renderer->course_section_cm_unavailable_error_message($cm);
 | 
        
           |  |  | 2667 |         redirect(course_get_url($course), $message, null, \core\output\notification::NOTIFY_ERROR);
 | 
        
           |  |  | 2668 |     }
 | 
        
           |  |  | 2669 |   | 
        
           |  |  | 2670 |     // Set the global $COURSE.
 | 
        
           |  |  | 2671 |     if ($cm) {
 | 
        
           |  |  | 2672 |         $PAGE->set_cm($cm, $course);
 | 
        
           |  |  | 2673 |         $PAGE->set_pagelayout('incourse');
 | 
        
           |  |  | 2674 |     } else if (!empty($courseorid)) {
 | 
        
           |  |  | 2675 |         $PAGE->set_course($course);
 | 
        
           |  |  | 2676 |     }
 | 
        
           |  |  | 2677 |   | 
        
           |  |  | 2678 |     foreach ($afterlogins as $plugintype => $plugins) {
 | 
        
           |  |  | 2679 |         foreach ($plugins as $pluginfunction) {
 | 
        
           |  |  | 2680 |             $pluginfunction($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2681 |         }
 | 
        
           |  |  | 2682 |     }
 | 
        
           |  |  | 2683 |   | 
        
           |  |  | 2684 |     // Finally access granted, update lastaccess times.
 | 
        
           |  |  | 2685 |     // Do not update access time for webservice or ajax requests.
 | 
        
           |  |  | 2686 |     if (!WS_SERVER && !AJAX_SCRIPT) {
 | 
        
           |  |  | 2687 |         user_accesstime_log($course->id);
 | 
        
           |  |  | 2688 |     }
 | 
        
           |  |  | 2689 | }
 | 
        
           |  |  | 2690 |   | 
        
           |  |  | 2691 | /**
 | 
        
           |  |  | 2692 |  * A convenience function for where we must be logged in as admin
 | 
        
           |  |  | 2693 |  * @return void
 | 
        
           |  |  | 2694 |  */
 | 
        
           | 1326 | ariadna | 2695 | function require_admin()
 | 
        
           |  |  | 2696 | {
 | 
        
           | 1 | efrain | 2697 |     require_login(null, false);
 | 
        
           |  |  | 2698 |     require_capability('moodle/site:config', context_system::instance());
 | 
        
           |  |  | 2699 | }
 | 
        
           |  |  | 2700 |   | 
        
           |  |  | 2701 | /**
 | 
        
           |  |  | 2702 |  * This function just makes sure a user is logged out.
 | 
        
           |  |  | 2703 |  *
 | 
        
           |  |  | 2704 |  * @package    core_access
 | 
        
           |  |  | 2705 |  * @category   access
 | 
        
           |  |  | 2706 |  */
 | 
        
           | 1326 | ariadna | 2707 | function require_logout()
 | 
        
           |  |  | 2708 | {
 | 
        
           | 1 | efrain | 2709 |     global $USER, $DB;
 | 
        
           |  |  | 2710 |   | 
        
           |  |  | 2711 |     if (!isloggedin()) {
 | 
        
           |  |  | 2712 |         // This should not happen often, no need for hooks or events here.
 | 
        
           |  |  | 2713 |         \core\session\manager::terminate_current();
 | 
        
           |  |  | 2714 |         return;
 | 
        
           |  |  | 2715 |     }
 | 
        
           |  |  | 2716 |   | 
        
           |  |  | 2717 |     // Execute hooks before action.
 | 
        
           |  |  | 2718 |     $authplugins = array();
 | 
        
           |  |  | 2719 |     $authsequence = get_enabled_auth_plugins();
 | 
        
           |  |  | 2720 |     foreach ($authsequence as $authname) {
 | 
        
           |  |  | 2721 |         $authplugins[$authname] = get_auth_plugin($authname);
 | 
        
           |  |  | 2722 |         $authplugins[$authname]->prelogout_hook();
 | 
        
           |  |  | 2723 |     }
 | 
        
           |  |  | 2724 |   | 
        
           |  |  | 2725 |     // Store info that gets removed during logout.
 | 
        
           |  |  | 2726 |     $sid = session_id();
 | 
        
           |  |  | 2727 |     $event = \core\event\user_loggedout::create(
 | 
        
           |  |  | 2728 |         array(
 | 
        
           |  |  | 2729 |             'userid' => $USER->id,
 | 
        
           |  |  | 2730 |             'objectid' => $USER->id,
 | 
        
           |  |  | 2731 |             'other' => array('sessionid' => $sid),
 | 
        
           |  |  | 2732 |         )
 | 
        
           |  |  | 2733 |     );
 | 
        
           | 1326 | ariadna | 2734 |     if ($session = $DB->get_record('sessions', array('sid' => $sid))) {
 | 
        
           | 1 | efrain | 2735 |         $event->add_record_snapshot('sessions', $session);
 | 
        
           |  |  | 2736 |     }
 | 
        
           |  |  | 2737 |   | 
        
           |  |  | 2738 |     // Clone of $USER object to be used by auth plugins.
 | 
        
           |  |  | 2739 |     $user = fullclone($USER);
 | 
        
           |  |  | 2740 |   | 
        
           |  |  | 2741 |     // Delete session record and drop $_SESSION content.
 | 
        
           |  |  | 2742 |     \core\session\manager::terminate_current();
 | 
        
           |  |  | 2743 |   | 
        
           |  |  | 2744 |     // Trigger event AFTER action.
 | 
        
           |  |  | 2745 |     $event->trigger();
 | 
        
           |  |  | 2746 |   | 
        
           |  |  | 2747 |     // Hook to execute auth plugins redirection after event trigger.
 | 
        
           |  |  | 2748 |     foreach ($authplugins as $authplugin) {
 | 
        
           |  |  | 2749 |         $authplugin->postlogout_hook($user);
 | 
        
           |  |  | 2750 |     }
 | 
        
           |  |  | 2751 | }
 | 
        
           |  |  | 2752 |   | 
        
           |  |  | 2753 | /**
 | 
        
           |  |  | 2754 |  * Weaker version of require_login()
 | 
        
           |  |  | 2755 |  *
 | 
        
           |  |  | 2756 |  * This is a weaker version of {@link require_login()} which only requires login
 | 
        
           |  |  | 2757 |  * when called from within a course rather than the site page, unless
 | 
        
           |  |  | 2758 |  * the forcelogin option is turned on.
 | 
        
           |  |  | 2759 |  * @see require_login()
 | 
        
           |  |  | 2760 |  *
 | 
        
           |  |  | 2761 |  * @package    core_access
 | 
        
           |  |  | 2762 |  * @category   access
 | 
        
           |  |  | 2763 |  *
 | 
        
           |  |  | 2764 |  * @param mixed $courseorid The course object or id in question
 | 
        
           |  |  | 2765 |  * @param bool $autologinguest Allow autologin guests if that is wanted
 | 
        
           |  |  | 2766 |  * @param object $cm Course activity module if known
 | 
        
           |  |  | 2767 |  * @param bool $setwantsurltome Define if we want to set $SESSION->wantsurl, defaults to
 | 
        
           |  |  | 2768 |  *             true. Used to avoid (=false) some scripts (file.php...) to set that variable,
 | 
        
           |  |  | 2769 |  *             in order to keep redirects working properly. MDL-14495
 | 
        
           |  |  | 2770 |  * @param bool $preventredirect set to true in scripts that can not redirect (CLI, rss feeds, etc.), throws exceptions
 | 
        
           |  |  | 2771 |  * @return void
 | 
        
           |  |  | 2772 |  * @throws coding_exception
 | 
        
           |  |  | 2773 |  */
 | 
        
           | 1326 | ariadna | 2774 | function require_course_login($courseorid, $autologinguest = true, $cm = null, $setwantsurltome = true, $preventredirect = false)
 | 
        
           |  |  | 2775 | {
 | 
        
           | 1 | efrain | 2776 |     global $CFG, $PAGE, $SITE;
 | 
        
           |  |  | 2777 |     $issite = ((is_object($courseorid) and $courseorid->id == SITEID)
 | 
        
           | 1326 | ariadna | 2778 |         or (!is_object($courseorid) and $courseorid == SITEID));
 | 
        
           | 1 | efrain | 2779 |     if ($issite && !empty($cm) && !($cm instanceof cm_info)) {
 | 
        
           |  |  | 2780 |         // Note: nearly all pages call get_fast_modinfo anyway and it does not make any
 | 
        
           |  |  | 2781 |         // db queries so this is not really a performance concern, however it is obviously
 | 
        
           |  |  | 2782 |         // better if you use get_fast_modinfo to get the cm before calling this.
 | 
        
           |  |  | 2783 |         if (is_object($courseorid)) {
 | 
        
           |  |  | 2784 |             $course = $courseorid;
 | 
        
           |  |  | 2785 |         } else {
 | 
        
           | 1326 | ariadna | 2786 |             $course = clone ($SITE);
 | 
        
           | 1 | efrain | 2787 |         }
 | 
        
           |  |  | 2788 |         $modinfo = get_fast_modinfo($course);
 | 
        
           |  |  | 2789 |         $cm = $modinfo->get_cm($cm->id);
 | 
        
           |  |  | 2790 |     }
 | 
        
           |  |  | 2791 |     if (!empty($CFG->forcelogin)) {
 | 
        
           |  |  | 2792 |         // Login required for both SITE and courses.
 | 
        
           |  |  | 2793 |         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2794 |     } else if ($issite && !empty($cm) and !$cm->uservisible) {
 | 
        
           |  |  | 2795 |         // Always login for hidden activities.
 | 
        
           |  |  | 2796 |         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2797 |     } else if (isloggedin() && !isguestuser()) {
 | 
        
           |  |  | 2798 |         // User is already logged in. Make sure the login is complete (user is fully setup, policies agreed).
 | 
        
           |  |  | 2799 |         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2800 |     } else if ($issite) {
 | 
        
           |  |  | 2801 |         // Login for SITE not required.
 | 
        
           |  |  | 2802 |         // We still need to instatiate PAGE vars properly so that things that rely on it like navigation function correctly.
 | 
        
           |  |  | 2803 |         if (!empty($courseorid)) {
 | 
        
           |  |  | 2804 |             if (is_object($courseorid)) {
 | 
        
           |  |  | 2805 |                 $course = $courseorid;
 | 
        
           |  |  | 2806 |             } else {
 | 
        
           |  |  | 2807 |                 $course = clone $SITE;
 | 
        
           |  |  | 2808 |             }
 | 
        
           |  |  | 2809 |             if ($cm) {
 | 
        
           |  |  | 2810 |                 if ($cm->course != $course->id) {
 | 
        
           |  |  | 2811 |                     throw new coding_exception('course and cm parameters in require_course_login() call do not match!!');
 | 
        
           |  |  | 2812 |                 }
 | 
        
           |  |  | 2813 |                 $PAGE->set_cm($cm, $course);
 | 
        
           |  |  | 2814 |                 $PAGE->set_pagelayout('incourse');
 | 
        
           |  |  | 2815 |             } else {
 | 
        
           |  |  | 2816 |                 $PAGE->set_course($course);
 | 
        
           |  |  | 2817 |             }
 | 
        
           |  |  | 2818 |         } else {
 | 
        
           |  |  | 2819 |             // If $PAGE->course, and hence $PAGE->context, have not already been set up properly, set them up now.
 | 
        
           |  |  | 2820 |             $PAGE->set_course($PAGE->course);
 | 
        
           |  |  | 2821 |         }
 | 
        
           |  |  | 2822 |         // Do not update access time for webservice or ajax requests.
 | 
        
           |  |  | 2823 |         if (!WS_SERVER && !AJAX_SCRIPT) {
 | 
        
           |  |  | 2824 |             user_accesstime_log(SITEID);
 | 
        
           |  |  | 2825 |         }
 | 
        
           |  |  | 2826 |         return;
 | 
        
           |  |  | 2827 |     } else {
 | 
        
           |  |  | 2828 |         // Course login always required.
 | 
        
           |  |  | 2829 |         require_login($courseorid, $autologinguest, $cm, $setwantsurltome, $preventredirect);
 | 
        
           |  |  | 2830 |     }
 | 
        
           |  |  | 2831 | }
 | 
        
           |  |  | 2832 |   | 
        
           |  |  | 2833 | /**
 | 
        
           |  |  | 2834 |  * Validates a user key, checking if the key exists, is not expired and the remote ip is correct.
 | 
        
           |  |  | 2835 |  *
 | 
        
           |  |  | 2836 |  * @param  string $keyvalue the key value
 | 
        
           |  |  | 2837 |  * @param  string $script   unique script identifier
 | 
        
           |  |  | 2838 |  * @param  int $instance    instance id
 | 
        
           |  |  | 2839 |  * @return stdClass the key entry in the user_private_key table
 | 
        
           |  |  | 2840 |  * @since Moodle 3.2
 | 
        
           |  |  | 2841 |  * @throws moodle_exception
 | 
        
           |  |  | 2842 |  */
 | 
        
           | 1326 | ariadna | 2843 | function validate_user_key($keyvalue, $script, $instance)
 | 
        
           |  |  | 2844 | {
 | 
        
           | 1 | efrain | 2845 |     global $DB;
 | 
        
           |  |  | 2846 |   | 
        
           |  |  | 2847 |     if (!$key = $DB->get_record('user_private_key', array('script' => $script, 'value' => $keyvalue, 'instance' => $instance))) {
 | 
        
           |  |  | 2848 |         throw new \moodle_exception('invalidkey');
 | 
        
           |  |  | 2849 |     }
 | 
        
           |  |  | 2850 |   | 
        
           |  |  | 2851 |     if (!empty($key->validuntil) and $key->validuntil < time()) {
 | 
        
           |  |  | 2852 |         throw new \moodle_exception('expiredkey');
 | 
        
           |  |  | 2853 |     }
 | 
        
           |  |  | 2854 |   | 
        
           |  |  | 2855 |     if ($key->iprestriction) {
 | 
        
           |  |  | 2856 |         $remoteaddr = getremoteaddr(null);
 | 
        
           |  |  | 2857 |         if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) {
 | 
        
           |  |  | 2858 |             throw new \moodle_exception('ipmismatch');
 | 
        
           |  |  | 2859 |         }
 | 
        
           |  |  | 2860 |     }
 | 
        
           |  |  | 2861 |     return $key;
 | 
        
           |  |  | 2862 | }
 | 
        
           |  |  | 2863 |   | 
        
           |  |  | 2864 | /**
 | 
        
           |  |  | 2865 |  * Require key login. Function terminates with error if key not found or incorrect.
 | 
        
           |  |  | 2866 |  *
 | 
        
           |  |  | 2867 |  * @uses NO_MOODLE_COOKIES
 | 
        
           |  |  | 2868 |  * @uses PARAM_ALPHANUM
 | 
        
           |  |  | 2869 |  * @param string $script unique script identifier
 | 
        
           |  |  | 2870 |  * @param int $instance optional instance id
 | 
        
           |  |  | 2871 |  * @param string $keyvalue The key. If not supplied, this will be fetched from the current session.
 | 
        
           |  |  | 2872 |  * @return int Instance ID
 | 
        
           |  |  | 2873 |  */
 | 
        
           | 1326 | ariadna | 2874 | function require_user_key_login($script, $instance = null, $keyvalue = null)
 | 
        
           |  |  | 2875 | {
 | 
        
           | 1 | efrain | 2876 |     global $DB;
 | 
        
           |  |  | 2877 |   | 
        
           |  |  | 2878 |     if (!NO_MOODLE_COOKIES) {
 | 
        
           |  |  | 2879 |         throw new \moodle_exception('sessioncookiesdisable');
 | 
        
           |  |  | 2880 |     }
 | 
        
           |  |  | 2881 |   | 
        
           |  |  | 2882 |     // Extra safety.
 | 
        
           |  |  | 2883 |     \core\session\manager::write_close();
 | 
        
           |  |  | 2884 |   | 
        
           |  |  | 2885 |     if (null === $keyvalue) {
 | 
        
           |  |  | 2886 |         $keyvalue = required_param('key', PARAM_ALPHANUM);
 | 
        
           |  |  | 2887 |     }
 | 
        
           |  |  | 2888 |   | 
        
           |  |  | 2889 |     $key = validate_user_key($keyvalue, $script, $instance);
 | 
        
           |  |  | 2890 |   | 
        
           |  |  | 2891 |     if (!$user = $DB->get_record('user', array('id' => $key->userid))) {
 | 
        
           |  |  | 2892 |         throw new \moodle_exception('invaliduserid');
 | 
        
           |  |  | 2893 |     }
 | 
        
           |  |  | 2894 |   | 
        
           |  |  | 2895 |     core_user::require_active_user($user, true, true);
 | 
        
           |  |  | 2896 |   | 
        
           |  |  | 2897 |     // Emulate normal session.
 | 
        
           |  |  | 2898 |     enrol_check_plugins($user, false);
 | 
        
           |  |  | 2899 |     \core\session\manager::set_user($user);
 | 
        
           |  |  | 2900 |   | 
        
           |  |  | 2901 |     // Note we are not using normal login.
 | 
        
           |  |  | 2902 |     if (!defined('USER_KEY_LOGIN')) {
 | 
        
           |  |  | 2903 |         define('USER_KEY_LOGIN', true);
 | 
        
           |  |  | 2904 |     }
 | 
        
           |  |  | 2905 |   | 
        
           |  |  | 2906 |     // Return instance id - it might be empty.
 | 
        
           |  |  | 2907 |     return $key->instance;
 | 
        
           |  |  | 2908 | }
 | 
        
           |  |  | 2909 |   | 
        
           |  |  | 2910 | /**
 | 
        
           |  |  | 2911 |  * Creates a new private user access key.
 | 
        
           |  |  | 2912 |  *
 | 
        
           |  |  | 2913 |  * @param string $script unique target identifier
 | 
        
           |  |  | 2914 |  * @param int $userid
 | 
        
           |  |  | 2915 |  * @param int $instance optional instance id
 | 
        
           |  |  | 2916 |  * @param string $iprestriction optional ip restricted access
 | 
        
           |  |  | 2917 |  * @param int $validuntil key valid only until given data
 | 
        
           |  |  | 2918 |  * @return string access key value
 | 
        
           |  |  | 2919 |  */
 | 
        
           | 1326 | ariadna | 2920 | function create_user_key($script, $userid, $instance = null, $iprestriction = null, $validuntil = null)
 | 
        
           |  |  | 2921 | {
 | 
        
           | 1 | efrain | 2922 |     global $DB;
 | 
        
           |  |  | 2923 |   | 
        
           |  |  | 2924 |     $key = new stdClass();
 | 
        
           |  |  | 2925 |     $key->script        = $script;
 | 
        
           |  |  | 2926 |     $key->userid        = $userid;
 | 
        
           |  |  | 2927 |     $key->instance      = $instance;
 | 
        
           |  |  | 2928 |     $key->iprestriction = $iprestriction;
 | 
        
           |  |  | 2929 |     $key->validuntil    = $validuntil;
 | 
        
           |  |  | 2930 |     $key->timecreated   = time();
 | 
        
           |  |  | 2931 |   | 
        
           |  |  | 2932 |     // Something long and unique.
 | 
        
           | 1326 | ariadna | 2933 |     $key->value         = md5($userid . '_' . time() . random_string(40));
 | 
        
           | 1 | efrain | 2934 |     while ($DB->record_exists('user_private_key', array('value' => $key->value))) {
 | 
        
           |  |  | 2935 |         // Must be unique.
 | 
        
           | 1326 | ariadna | 2936 |         $key->value     = md5($userid . '_' . time() . random_string(40));
 | 
        
           | 1 | efrain | 2937 |     }
 | 
        
           |  |  | 2938 |     $DB->insert_record('user_private_key', $key);
 | 
        
           |  |  | 2939 |     return $key->value;
 | 
        
           |  |  | 2940 | }
 | 
        
           |  |  | 2941 |   | 
        
           |  |  | 2942 | /**
 | 
        
           |  |  | 2943 |  * Delete the user's new private user access keys for a particular script.
 | 
        
           |  |  | 2944 |  *
 | 
        
           |  |  | 2945 |  * @param string $script unique target identifier
 | 
        
           |  |  | 2946 |  * @param int $userid
 | 
        
           |  |  | 2947 |  * @return void
 | 
        
           |  |  | 2948 |  */
 | 
        
           | 1326 | ariadna | 2949 | function delete_user_key($script, $userid)
 | 
        
           |  |  | 2950 | {
 | 
        
           | 1 | efrain | 2951 |     global $DB;
 | 
        
           |  |  | 2952 |     $DB->delete_records('user_private_key', array('script' => $script, 'userid' => $userid));
 | 
        
           |  |  | 2953 | }
 | 
        
           |  |  | 2954 |   | 
        
           |  |  | 2955 | /**
 | 
        
           |  |  | 2956 |  * Gets a private user access key (and creates one if one doesn't exist).
 | 
        
           |  |  | 2957 |  *
 | 
        
           |  |  | 2958 |  * @param string $script unique target identifier
 | 
        
           |  |  | 2959 |  * @param int $userid
 | 
        
           |  |  | 2960 |  * @param int $instance optional instance id
 | 
        
           |  |  | 2961 |  * @param string $iprestriction optional ip restricted access
 | 
        
           |  |  | 2962 |  * @param int $validuntil key valid only until given date
 | 
        
           |  |  | 2963 |  * @return string access key value
 | 
        
           |  |  | 2964 |  */
 | 
        
           | 1326 | ariadna | 2965 | function get_user_key($script, $userid, $instance = null, $iprestriction = null, $validuntil = null)
 | 
        
           |  |  | 2966 | {
 | 
        
           | 1 | efrain | 2967 |     global $DB;
 | 
        
           |  |  | 2968 |   | 
        
           | 1326 | ariadna | 2969 |     if ($key = $DB->get_record('user_private_key', array(
 | 
        
           |  |  | 2970 |         'script' => $script,
 | 
        
           |  |  | 2971 |         'userid' => $userid,
 | 
        
           |  |  | 2972 |         'instance' => $instance,
 | 
        
           |  |  | 2973 |         'iprestriction' => $iprestriction,
 | 
        
           |  |  | 2974 |         'validuntil' => $validuntil
 | 
        
           |  |  | 2975 |     ))) {
 | 
        
           | 1 | efrain | 2976 |         return $key->value;
 | 
        
           |  |  | 2977 |     } else {
 | 
        
           |  |  | 2978 |         return create_user_key($script, $userid, $instance, $iprestriction, $validuntil);
 | 
        
           |  |  | 2979 |     }
 | 
        
           |  |  | 2980 | }
 | 
        
           |  |  | 2981 |   | 
        
           |  |  | 2982 |   | 
        
           |  |  | 2983 | /**
 | 
        
           |  |  | 2984 |  * Modify the user table by setting the currently logged in user's last login to now.
 | 
        
           |  |  | 2985 |  *
 | 
        
           |  |  | 2986 |  * @return bool Always returns true
 | 
        
           |  |  | 2987 |  */
 | 
        
           | 1326 | ariadna | 2988 | function update_user_login_times()
 | 
        
           |  |  | 2989 | {
 | 
        
           | 1 | efrain | 2990 |     global $USER, $DB, $SESSION;
 | 
        
           |  |  | 2991 |   | 
        
           |  |  | 2992 |     if (isguestuser()) {
 | 
        
           |  |  | 2993 |         // Do not update guest access times/ips for performance.
 | 
        
           |  |  | 2994 |         return true;
 | 
        
           |  |  | 2995 |     }
 | 
        
           |  |  | 2996 |   | 
        
           |  |  | 2997 |     if (defined('USER_KEY_LOGIN') && USER_KEY_LOGIN === true) {
 | 
        
           |  |  | 2998 |         // Do not update user login time when using user key login.
 | 
        
           |  |  | 2999 |         return true;
 | 
        
           |  |  | 3000 |     }
 | 
        
           |  |  | 3001 |   | 
        
           |  |  | 3002 |     $now = time();
 | 
        
           |  |  | 3003 |   | 
        
           |  |  | 3004 |     $user = new stdClass();
 | 
        
           |  |  | 3005 |     $user->id = $USER->id;
 | 
        
           |  |  | 3006 |   | 
        
           |  |  | 3007 |     // Make sure all users that logged in have some firstaccess.
 | 
        
           |  |  | 3008 |     if ($USER->firstaccess == 0) {
 | 
        
           |  |  | 3009 |         $USER->firstaccess = $user->firstaccess = $now;
 | 
        
           |  |  | 3010 |     }
 | 
        
           |  |  | 3011 |   | 
        
           |  |  | 3012 |     // Store the previous current as lastlogin.
 | 
        
           |  |  | 3013 |     $USER->lastlogin = $user->lastlogin = $USER->currentlogin;
 | 
        
           |  |  | 3014 |   | 
        
           |  |  | 3015 |     $USER->currentlogin = $user->currentlogin = $now;
 | 
        
           |  |  | 3016 |   | 
        
           |  |  | 3017 |     // Function user_accesstime_log() may not update immediately, better do it here.
 | 
        
           |  |  | 3018 |     $USER->lastaccess = $user->lastaccess = $now;
 | 
        
           |  |  | 3019 |     $SESSION->userpreviousip = $USER->lastip;
 | 
        
           |  |  | 3020 |     $USER->lastip = $user->lastip = getremoteaddr();
 | 
        
           |  |  | 3021 |   | 
        
           |  |  | 3022 |     // Note: do not call user_update_user() here because this is part of the login process,
 | 
        
           |  |  | 3023 |     //       the login event means that these fields were updated.
 | 
        
           |  |  | 3024 |     $DB->update_record('user', $user);
 | 
        
           |  |  | 3025 |     return true;
 | 
        
           |  |  | 3026 | }
 | 
        
           |  |  | 3027 |   | 
        
           |  |  | 3028 | /**
 | 
        
           |  |  | 3029 |  * Determines if a user has completed setting up their account.
 | 
        
           |  |  | 3030 |  *
 | 
        
           |  |  | 3031 |  * The lax mode (with $strict = false) has been introduced for special cases
 | 
        
           |  |  | 3032 |  * only where we want to skip certain checks intentionally. This is valid in
 | 
        
           |  |  | 3033 |  * certain mnet or ajax scenarios when the user cannot / should not be
 | 
        
           |  |  | 3034 |  * redirected to edit their profile. In most cases, you should perform the
 | 
        
           |  |  | 3035 |  * strict check.
 | 
        
           |  |  | 3036 |  *
 | 
        
           |  |  | 3037 |  * @param stdClass $user A {@link $USER} object to test for the existence of a valid name and email
 | 
        
           |  |  | 3038 |  * @param bool $strict Be more strict and assert id and custom profile fields set, too
 | 
        
           |  |  | 3039 |  * @return bool
 | 
        
           |  |  | 3040 |  */
 | 
        
           | 1326 | ariadna | 3041 | function user_not_fully_set_up($user, $strict = true)
 | 
        
           |  |  | 3042 | {
 | 
        
           | 1 | efrain | 3043 |     global $CFG, $SESSION, $USER;
 | 
        
           | 1326 | ariadna | 3044 |     require_once($CFG->dirroot . '/user/profile/lib.php');
 | 
        
           | 1 | efrain | 3045 |   | 
        
           |  |  | 3046 |     // If the user is setup then store this in the session to avoid re-checking.
 | 
        
           |  |  | 3047 |     // Some edge cases are when the users email starts to bounce or the
 | 
        
           |  |  | 3048 |     // configuration for custom fields has changed while they are logged in so
 | 
        
           |  |  | 3049 |     // we re-check this fully every hour for the rare cases it has changed.
 | 
        
           | 1326 | ariadna | 3050 |     if (
 | 
        
           |  |  | 3051 |         isset($USER->id) && isset($user->id) && $USER->id === $user->id &&
 | 
        
           |  |  | 3052 |         isset($SESSION->fullysetupstrict) && (time() - $SESSION->fullysetupstrict) < HOURSECS
 | 
        
           |  |  | 3053 |     ) {
 | 
        
           | 1 | efrain | 3054 |         return false;
 | 
        
           |  |  | 3055 |     }
 | 
        
           |  |  | 3056 |   | 
        
           |  |  | 3057 |     if (isguestuser($user)) {
 | 
        
           |  |  | 3058 |         return false;
 | 
        
           |  |  | 3059 |     }
 | 
        
           |  |  | 3060 |   | 
        
           |  |  | 3061 |     if (empty($user->firstname) or empty($user->lastname) or empty($user->email) or over_bounce_threshold($user)) {
 | 
        
           |  |  | 3062 |         return true;
 | 
        
           |  |  | 3063 |     }
 | 
        
           |  |  | 3064 |   | 
        
           |  |  | 3065 |     if ($strict) {
 | 
        
           |  |  | 3066 |         if (empty($user->id)) {
 | 
        
           |  |  | 3067 |             // Strict mode can be used with existing accounts only.
 | 
        
           |  |  | 3068 |             return true;
 | 
        
           |  |  | 3069 |         }
 | 
        
           |  |  | 3070 |         if (!profile_has_required_custom_fields_set($user->id)) {
 | 
        
           |  |  | 3071 |             return true;
 | 
        
           |  |  | 3072 |         }
 | 
        
           |  |  | 3073 |         if (isset($USER->id) && isset($user->id) && $USER->id === $user->id) {
 | 
        
           |  |  | 3074 |             $SESSION->fullysetupstrict = time();
 | 
        
           |  |  | 3075 |         }
 | 
        
           |  |  | 3076 |     }
 | 
        
           |  |  | 3077 |   | 
        
           |  |  | 3078 |     return false;
 | 
        
           |  |  | 3079 | }
 | 
        
           |  |  | 3080 |   | 
        
           |  |  | 3081 | /**
 | 
        
           |  |  | 3082 |  * Check whether the user has exceeded the bounce threshold
 | 
        
           |  |  | 3083 |  *
 | 
        
           |  |  | 3084 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 3085 |  * @return bool true => User has exceeded bounce threshold
 | 
        
           |  |  | 3086 |  */
 | 
        
           | 1326 | ariadna | 3087 | function over_bounce_threshold($user)
 | 
        
           |  |  | 3088 | {
 | 
        
           | 1 | efrain | 3089 |     global $CFG, $DB;
 | 
        
           |  |  | 3090 |   | 
        
           |  |  | 3091 |     if (empty($CFG->handlebounces)) {
 | 
        
           |  |  | 3092 |         return false;
 | 
        
           |  |  | 3093 |     }
 | 
        
           |  |  | 3094 |   | 
        
           |  |  | 3095 |     if (empty($user->id)) {
 | 
        
           |  |  | 3096 |         // No real (DB) user, nothing to do here.
 | 
        
           |  |  | 3097 |         return false;
 | 
        
           |  |  | 3098 |     }
 | 
        
           |  |  | 3099 |   | 
        
           |  |  | 3100 |     // Set sensible defaults.
 | 
        
           |  |  | 3101 |     if (empty($CFG->minbounces)) {
 | 
        
           |  |  | 3102 |         $CFG->minbounces = 10;
 | 
        
           |  |  | 3103 |     }
 | 
        
           |  |  | 3104 |     if (empty($CFG->bounceratio)) {
 | 
        
           |  |  | 3105 |         $CFG->bounceratio = .20;
 | 
        
           |  |  | 3106 |     }
 | 
        
           |  |  | 3107 |     $bouncecount = 0;
 | 
        
           |  |  | 3108 |     $sendcount = 0;
 | 
        
           | 1326 | ariadna | 3109 |     if ($bounce = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_bounce_count'))) {
 | 
        
           | 1 | efrain | 3110 |         $bouncecount = $bounce->value;
 | 
        
           |  |  | 3111 |     }
 | 
        
           |  |  | 3112 |     if ($send = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
 | 
        
           |  |  | 3113 |         $sendcount = $send->value;
 | 
        
           |  |  | 3114 |     }
 | 
        
           | 1326 | ariadna | 3115 |     return ($bouncecount >= $CFG->minbounces && $bouncecount / $sendcount >= $CFG->bounceratio);
 | 
        
           | 1 | efrain | 3116 | }
 | 
        
           |  |  | 3117 |   | 
        
           |  |  | 3118 | /**
 | 
        
           |  |  | 3119 |  * Used to increment or reset email sent count
 | 
        
           |  |  | 3120 |  *
 | 
        
           |  |  | 3121 |  * @param stdClass $user object containing an id
 | 
        
           |  |  | 3122 |  * @param bool $reset will reset the count to 0
 | 
        
           |  |  | 3123 |  * @return void
 | 
        
           |  |  | 3124 |  */
 | 
        
           | 1326 | ariadna | 3125 | function set_send_count($user, $reset = false)
 | 
        
           |  |  | 3126 | {
 | 
        
           | 1 | efrain | 3127 |     global $DB;
 | 
        
           |  |  | 3128 |   | 
        
           |  |  | 3129 |     if (empty($user->id)) {
 | 
        
           |  |  | 3130 |         // No real (DB) user, nothing to do here.
 | 
        
           |  |  | 3131 |         return;
 | 
        
           |  |  | 3132 |     }
 | 
        
           |  |  | 3133 |   | 
        
           |  |  | 3134 |     if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_send_count'))) {
 | 
        
           | 1326 | ariadna | 3135 |         $pref->value = (!empty($reset)) ? 0 : $pref->value + 1;
 | 
        
           | 1 | efrain | 3136 |         $DB->update_record('user_preferences', $pref);
 | 
        
           |  |  | 3137 |     } else if (!empty($reset)) {
 | 
        
           |  |  | 3138 |         // If it's not there and we're resetting, don't bother. Make a new one.
 | 
        
           |  |  | 3139 |         $pref = new stdClass();
 | 
        
           |  |  | 3140 |         $pref->name   = 'email_send_count';
 | 
        
           |  |  | 3141 |         $pref->value  = 1;
 | 
        
           |  |  | 3142 |         $pref->userid = $user->id;
 | 
        
           |  |  | 3143 |         $DB->insert_record('user_preferences', $pref, false);
 | 
        
           |  |  | 3144 |     }
 | 
        
           |  |  | 3145 | }
 | 
        
           |  |  | 3146 |   | 
        
           |  |  | 3147 | /**
 | 
        
           |  |  | 3148 |  * Increment or reset user's email bounce count
 | 
        
           |  |  | 3149 |  *
 | 
        
           |  |  | 3150 |  * @param stdClass $user object containing an id
 | 
        
           |  |  | 3151 |  * @param bool $reset will reset the count to 0
 | 
        
           |  |  | 3152 |  */
 | 
        
           | 1326 | ariadna | 3153 | function set_bounce_count($user, $reset = false)
 | 
        
           |  |  | 3154 | {
 | 
        
           | 1 | efrain | 3155 |     global $DB;
 | 
        
           |  |  | 3156 |   | 
        
           |  |  | 3157 |     if ($pref = $DB->get_record('user_preferences', array('userid' => $user->id, 'name' => 'email_bounce_count'))) {
 | 
        
           | 1326 | ariadna | 3158 |         $pref->value = (!empty($reset)) ? 0 : $pref->value + 1;
 | 
        
           | 1 | efrain | 3159 |         $DB->update_record('user_preferences', $pref);
 | 
        
           |  |  | 3160 |     } else if (!empty($reset)) {
 | 
        
           |  |  | 3161 |         // If it's not there and we're resetting, don't bother. Make a new one.
 | 
        
           |  |  | 3162 |         $pref = new stdClass();
 | 
        
           |  |  | 3163 |         $pref->name   = 'email_bounce_count';
 | 
        
           |  |  | 3164 |         $pref->value  = 1;
 | 
        
           |  |  | 3165 |         $pref->userid = $user->id;
 | 
        
           |  |  | 3166 |         $DB->insert_record('user_preferences', $pref, false);
 | 
        
           |  |  | 3167 |     }
 | 
        
           |  |  | 3168 | }
 | 
        
           |  |  | 3169 |   | 
        
           |  |  | 3170 | /**
 | 
        
           |  |  | 3171 |  * Determines if the logged in user is currently moving an activity
 | 
        
           |  |  | 3172 |  *
 | 
        
           |  |  | 3173 |  * @param int $courseid The id of the course being tested
 | 
        
           |  |  | 3174 |  * @return bool
 | 
        
           |  |  | 3175 |  */
 | 
        
           | 1326 | ariadna | 3176 | function ismoving($courseid)
 | 
        
           |  |  | 3177 | {
 | 
        
           | 1 | efrain | 3178 |     global $USER;
 | 
        
           |  |  | 3179 |   | 
        
           |  |  | 3180 |     if (!empty($USER->activitycopy)) {
 | 
        
           |  |  | 3181 |         return ($USER->activitycopycourse == $courseid);
 | 
        
           |  |  | 3182 |     }
 | 
        
           |  |  | 3183 |     return false;
 | 
        
           |  |  | 3184 | }
 | 
        
           |  |  | 3185 |   | 
        
           |  |  | 3186 | /**
 | 
        
           |  |  | 3187 |  * Returns a persons full name
 | 
        
           |  |  | 3188 |  *
 | 
        
           |  |  | 3189 |  * Given an object containing all of the users name values, this function returns a string with the full name of the person.
 | 
        
           |  |  | 3190 |  * The result may depend on system settings or language. 'override' will force the alternativefullnameformat to be used. In
 | 
        
           |  |  | 3191 |  * English, fullname as well as alternativefullnameformat is set to 'firstname lastname' by default. But you could have
 | 
        
           |  |  | 3192 |  * fullname set to 'firstname lastname' and alternativefullnameformat set to 'firstname middlename alternatename lastname'.
 | 
        
           |  |  | 3193 |  *
 | 
        
           |  |  | 3194 |  * @param stdClass $user A {@link $USER} object to get full name of.
 | 
        
           |  |  | 3195 |  * @param bool $override If true then the alternativefullnameformat format rather than fullnamedisplay format will be used.
 | 
        
           |  |  | 3196 |  * @return string
 | 
        
           |  |  | 3197 |  */
 | 
        
           | 1326 | ariadna | 3198 | function fullname($user, $override = false)
 | 
        
           |  |  | 3199 | {
 | 
        
           | 1 | efrain | 3200 |     // Note: We do not intend to deprecate this function any time soon as it is too widely used at this time.
 | 
        
           |  |  | 3201 |     // Uses of it should be updated to use the new API and pass updated arguments.
 | 
        
           |  |  | 3202 |   | 
        
           |  |  | 3203 |     // Return an empty string if there is no user.
 | 
        
           |  |  | 3204 |     if (empty($user)) {
 | 
        
           |  |  | 3205 |         return '';
 | 
        
           |  |  | 3206 |     }
 | 
        
           |  |  | 3207 |   | 
        
           |  |  | 3208 |     $options = ['override' => $override];
 | 
        
           |  |  | 3209 |     return core_user::get_fullname($user, null, $options);
 | 
        
           |  |  | 3210 | }
 | 
        
           |  |  | 3211 |   | 
        
           |  |  | 3212 | /**
 | 
        
           |  |  | 3213 |  * Reduces lines of duplicated code for getting user name fields.
 | 
        
           |  |  | 3214 |  *
 | 
        
           |  |  | 3215 |  * See also {@link user_picture::unalias()}
 | 
        
           |  |  | 3216 |  *
 | 
        
           |  |  | 3217 |  * @param object $addtoobject Object to add user name fields to.
 | 
        
           |  |  | 3218 |  * @param object $secondobject Object that contains user name field information.
 | 
        
           |  |  | 3219 |  * @param string $prefix prefix to be added to all fields (including $additionalfields) e.g. authorfirstname.
 | 
        
           |  |  | 3220 |  * @param array $additionalfields Additional fields to be matched with data in the second object.
 | 
        
           |  |  | 3221 |  * The key can be set to the user table field name.
 | 
        
           |  |  | 3222 |  * @return object User name fields.
 | 
        
           |  |  | 3223 |  */
 | 
        
           | 1326 | ariadna | 3224 | function username_load_fields_from_object($addtoobject, $secondobject, $prefix = null, $additionalfields = null)
 | 
        
           |  |  | 3225 | {
 | 
        
           | 1 | efrain | 3226 |     $fields = [];
 | 
        
           |  |  | 3227 |     foreach (\core_user\fields::get_name_fields() as $field) {
 | 
        
           |  |  | 3228 |         $fields[$field] = $prefix . $field;
 | 
        
           |  |  | 3229 |     }
 | 
        
           |  |  | 3230 |     if ($additionalfields) {
 | 
        
           |  |  | 3231 |         // Additional fields can specify their own 'alias' such as 'id' => 'userid'. This checks to see if
 | 
        
           |  |  | 3232 |         // the key is a number and then sets the key to the array value.
 | 
        
           |  |  | 3233 |         foreach ($additionalfields as $key => $value) {
 | 
        
           |  |  | 3234 |             if (is_numeric($key)) {
 | 
        
           |  |  | 3235 |                 $additionalfields[$value] = $prefix . $value;
 | 
        
           |  |  | 3236 |                 unset($additionalfields[$key]);
 | 
        
           |  |  | 3237 |             } else {
 | 
        
           |  |  | 3238 |                 $additionalfields[$key] = $prefix . $value;
 | 
        
           |  |  | 3239 |             }
 | 
        
           |  |  | 3240 |         }
 | 
        
           |  |  | 3241 |         $fields = array_merge($fields, $additionalfields);
 | 
        
           |  |  | 3242 |     }
 | 
        
           |  |  | 3243 |     foreach ($fields as $key => $field) {
 | 
        
           |  |  | 3244 |         // Important that we have all of the user name fields present in the object that we are sending back.
 | 
        
           |  |  | 3245 |         $addtoobject->$key = '';
 | 
        
           |  |  | 3246 |         if (isset($secondobject->$field)) {
 | 
        
           |  |  | 3247 |             $addtoobject->$key = $secondobject->$field;
 | 
        
           |  |  | 3248 |         }
 | 
        
           |  |  | 3249 |     }
 | 
        
           |  |  | 3250 |     return $addtoobject;
 | 
        
           |  |  | 3251 | }
 | 
        
           |  |  | 3252 |   | 
        
           |  |  | 3253 | /**
 | 
        
           |  |  | 3254 |  * Returns an array of values in order of occurance in a provided string.
 | 
        
           |  |  | 3255 |  * The key in the result is the character postion in the string.
 | 
        
           |  |  | 3256 |  *
 | 
        
           |  |  | 3257 |  * @param array $values Values to be found in the string format
 | 
        
           |  |  | 3258 |  * @param string $stringformat The string which may contain values being searched for.
 | 
        
           |  |  | 3259 |  * @return array An array of values in order according to placement in the string format.
 | 
        
           |  |  | 3260 |  */
 | 
        
           | 1326 | ariadna | 3261 | function order_in_string($values, $stringformat)
 | 
        
           |  |  | 3262 | {
 | 
        
           | 1 | efrain | 3263 |     $valuearray = array();
 | 
        
           |  |  | 3264 |     foreach ($values as $value) {
 | 
        
           |  |  | 3265 |         $pattern = "/$value\b/";
 | 
        
           |  |  | 3266 |         // Using preg_match as strpos() may match values that are similar e.g. firstname and firstnamephonetic.
 | 
        
           |  |  | 3267 |         if (preg_match($pattern, $stringformat)) {
 | 
        
           |  |  | 3268 |             $replacement = "thing";
 | 
        
           |  |  | 3269 |             // Replace the value with something more unique to ensure we get the right position when using strpos().
 | 
        
           |  |  | 3270 |             $newformat = preg_replace($pattern, $replacement, $stringformat);
 | 
        
           |  |  | 3271 |             $position = strpos($newformat, $replacement);
 | 
        
           |  |  | 3272 |             $valuearray[$position] = $value;
 | 
        
           |  |  | 3273 |         }
 | 
        
           |  |  | 3274 |     }
 | 
        
           |  |  | 3275 |     ksort($valuearray);
 | 
        
           |  |  | 3276 |     return $valuearray;
 | 
        
           |  |  | 3277 | }
 | 
        
           |  |  | 3278 |   | 
        
           |  |  | 3279 | /**
 | 
        
           |  |  | 3280 |  * Returns whether a given authentication plugin exists.
 | 
        
           |  |  | 3281 |  *
 | 
        
           |  |  | 3282 |  * @param string $auth Form of authentication to check for. Defaults to the global setting in {@link $CFG}.
 | 
        
           |  |  | 3283 |  * @return boolean Whether the plugin is available.
 | 
        
           |  |  | 3284 |  */
 | 
        
           | 1326 | ariadna | 3285 | function exists_auth_plugin($auth)
 | 
        
           |  |  | 3286 | {
 | 
        
           | 1 | efrain | 3287 |     global $CFG;
 | 
        
           |  |  | 3288 |   | 
        
           |  |  | 3289 |     if (file_exists("{$CFG->dirroot}/auth/$auth/auth.php")) {
 | 
        
           |  |  | 3290 |         return is_readable("{$CFG->dirroot}/auth/$auth/auth.php");
 | 
        
           |  |  | 3291 |     }
 | 
        
           |  |  | 3292 |     return false;
 | 
        
           |  |  | 3293 | }
 | 
        
           |  |  | 3294 |   | 
        
           |  |  | 3295 | /**
 | 
        
           |  |  | 3296 |  * Checks if a given plugin is in the list of enabled authentication plugins.
 | 
        
           |  |  | 3297 |  *
 | 
        
           |  |  | 3298 |  * @param string $auth Authentication plugin.
 | 
        
           |  |  | 3299 |  * @return boolean Whether the plugin is enabled.
 | 
        
           |  |  | 3300 |  */
 | 
        
           | 1326 | ariadna | 3301 | function is_enabled_auth($auth)
 | 
        
           |  |  | 3302 | {
 | 
        
           | 1 | efrain | 3303 |     if (empty($auth)) {
 | 
        
           |  |  | 3304 |         return false;
 | 
        
           |  |  | 3305 |     }
 | 
        
           |  |  | 3306 |   | 
        
           |  |  | 3307 |     $enabled = get_enabled_auth_plugins();
 | 
        
           |  |  | 3308 |   | 
        
           |  |  | 3309 |     return in_array($auth, $enabled);
 | 
        
           |  |  | 3310 | }
 | 
        
           |  |  | 3311 |   | 
        
           |  |  | 3312 | /**
 | 
        
           |  |  | 3313 |  * Returns an authentication plugin instance.
 | 
        
           |  |  | 3314 |  *
 | 
        
           |  |  | 3315 |  * @param string $auth name of authentication plugin
 | 
        
           |  |  | 3316 |  * @return auth_plugin_base An instance of the required authentication plugin.
 | 
        
           |  |  | 3317 |  */
 | 
        
           | 1326 | ariadna | 3318 | function get_auth_plugin($auth)
 | 
        
           |  |  | 3319 | {
 | 
        
           | 1 | efrain | 3320 |     global $CFG;
 | 
        
           |  |  | 3321 |   | 
        
           |  |  | 3322 |     // Check the plugin exists first.
 | 
        
           |  |  | 3323 |     if (! exists_auth_plugin($auth)) {
 | 
        
           |  |  | 3324 |         throw new \moodle_exception('authpluginnotfound', 'debug', '', $auth);
 | 
        
           |  |  | 3325 |     }
 | 
        
           |  |  | 3326 |   | 
        
           |  |  | 3327 |     // Return auth plugin instance.
 | 
        
           |  |  | 3328 |     require_once("{$CFG->dirroot}/auth/$auth/auth.php");
 | 
        
           |  |  | 3329 |     $class = "auth_plugin_$auth";
 | 
        
           |  |  | 3330 |     return new $class;
 | 
        
           |  |  | 3331 | }
 | 
        
           |  |  | 3332 |   | 
        
           |  |  | 3333 | /**
 | 
        
           |  |  | 3334 |  * Returns array of active auth plugins.
 | 
        
           |  |  | 3335 |  *
 | 
        
           |  |  | 3336 |  * @param bool $fix fix $CFG->auth if needed. Only set if logged in as admin.
 | 
        
           |  |  | 3337 |  * @return array
 | 
        
           |  |  | 3338 |  */
 | 
        
           | 1326 | ariadna | 3339 | function get_enabled_auth_plugins($fix = false)
 | 
        
           |  |  | 3340 | {
 | 
        
           | 1 | efrain | 3341 |     global $CFG;
 | 
        
           |  |  | 3342 |   | 
        
           |  |  | 3343 |     $default = array('manual', 'nologin');
 | 
        
           |  |  | 3344 |   | 
        
           |  |  | 3345 |     if (empty($CFG->auth)) {
 | 
        
           |  |  | 3346 |         $auths = array();
 | 
        
           |  |  | 3347 |     } else {
 | 
        
           |  |  | 3348 |         $auths = explode(',', $CFG->auth);
 | 
        
           |  |  | 3349 |     }
 | 
        
           |  |  | 3350 |   | 
        
           |  |  | 3351 |     $auths = array_unique($auths);
 | 
        
           |  |  | 3352 |     $oldauthconfig = implode(',', $auths);
 | 
        
           |  |  | 3353 |     foreach ($auths as $k => $authname) {
 | 
        
           |  |  | 3354 |         if (in_array($authname, $default)) {
 | 
        
           |  |  | 3355 |             // The manual and nologin plugin never need to be stored.
 | 
        
           |  |  | 3356 |             unset($auths[$k]);
 | 
        
           |  |  | 3357 |         } else if (!exists_auth_plugin($authname)) {
 | 
        
           |  |  | 3358 |             debugging(get_string('authpluginnotfound', 'debug', $authname));
 | 
        
           |  |  | 3359 |             unset($auths[$k]);
 | 
        
           |  |  | 3360 |         }
 | 
        
           |  |  | 3361 |     }
 | 
        
           |  |  | 3362 |   | 
        
           |  |  | 3363 |     // Ideally only explicit interaction from a human admin should trigger a
 | 
        
           |  |  | 3364 |     // change in auth config, see MDL-70424 for details.
 | 
        
           |  |  | 3365 |     if ($fix) {
 | 
        
           |  |  | 3366 |         $newconfig = implode(',', $auths);
 | 
        
           |  |  | 3367 |         if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
 | 
        
           |  |  | 3368 |             add_to_config_log('auth', $oldauthconfig, $newconfig, 'core');
 | 
        
           |  |  | 3369 |             set_config('auth', $newconfig);
 | 
        
           |  |  | 3370 |         }
 | 
        
           |  |  | 3371 |     }
 | 
        
           |  |  | 3372 |   | 
        
           |  |  | 3373 |     return (array_merge($default, $auths));
 | 
        
           |  |  | 3374 | }
 | 
        
           |  |  | 3375 |   | 
        
           |  |  | 3376 | /**
 | 
        
           |  |  | 3377 |  * Returns true if an internal authentication method is being used.
 | 
        
           |  |  | 3378 |  * if method not specified then, global default is assumed
 | 
        
           |  |  | 3379 |  *
 | 
        
           |  |  | 3380 |  * @param string $auth Form of authentication required
 | 
        
           |  |  | 3381 |  * @return bool
 | 
        
           |  |  | 3382 |  */
 | 
        
           | 1326 | ariadna | 3383 | function is_internal_auth($auth)
 | 
        
           |  |  | 3384 | {
 | 
        
           | 1 | efrain | 3385 |     // Throws error if bad $auth.
 | 
        
           |  |  | 3386 |     $authplugin = get_auth_plugin($auth);
 | 
        
           |  |  | 3387 |     return $authplugin->is_internal();
 | 
        
           |  |  | 3388 | }
 | 
        
           |  |  | 3389 |   | 
        
           |  |  | 3390 | /**
 | 
        
           |  |  | 3391 |  * Returns true if the user is a 'restored' one.
 | 
        
           |  |  | 3392 |  *
 | 
        
           |  |  | 3393 |  * Used in the login process to inform the user and allow him/her to reset the password
 | 
        
           |  |  | 3394 |  *
 | 
        
           |  |  | 3395 |  * @param string $username username to be checked
 | 
        
           |  |  | 3396 |  * @return bool
 | 
        
           |  |  | 3397 |  */
 | 
        
           | 1326 | ariadna | 3398 | function is_restored_user($username)
 | 
        
           |  |  | 3399 | {
 | 
        
           | 1 | efrain | 3400 |     global $CFG, $DB;
 | 
        
           |  |  | 3401 |   | 
        
           |  |  | 3402 |     return $DB->record_exists('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'password' => 'restored'));
 | 
        
           |  |  | 3403 | }
 | 
        
           |  |  | 3404 |   | 
        
           |  |  | 3405 | /**
 | 
        
           |  |  | 3406 |  * Returns an array of user fields
 | 
        
           |  |  | 3407 |  *
 | 
        
           |  |  | 3408 |  * @return array User field/column names
 | 
        
           |  |  | 3409 |  */
 | 
        
           | 1326 | ariadna | 3410 | function get_user_fieldnames()
 | 
        
           |  |  | 3411 | {
 | 
        
           | 1 | efrain | 3412 |     global $DB;
 | 
        
           |  |  | 3413 |   | 
        
           |  |  | 3414 |     $fieldarray = $DB->get_columns('user');
 | 
        
           |  |  | 3415 |     unset($fieldarray['id']);
 | 
        
           |  |  | 3416 |     $fieldarray = array_keys($fieldarray);
 | 
        
           |  |  | 3417 |   | 
        
           |  |  | 3418 |     return $fieldarray;
 | 
        
           |  |  | 3419 | }
 | 
        
           |  |  | 3420 |   | 
        
           |  |  | 3421 | /**
 | 
        
           |  |  | 3422 |  * Returns the string of the language for the new user.
 | 
        
           |  |  | 3423 |  *
 | 
        
           |  |  | 3424 |  * @return string language for the new user
 | 
        
           |  |  | 3425 |  */
 | 
        
           | 1326 | ariadna | 3426 | function get_newuser_language()
 | 
        
           |  |  | 3427 | {
 | 
        
           | 1 | efrain | 3428 |     global $CFG, $SESSION;
 | 
        
           |  |  | 3429 |     return (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang;
 | 
        
           |  |  | 3430 | }
 | 
        
           |  |  | 3431 |   | 
        
           |  |  | 3432 | /**
 | 
        
           |  |  | 3433 |  * Creates a bare-bones user record
 | 
        
           |  |  | 3434 |  *
 | 
        
           |  |  | 3435 |  * @todo Outline auth types and provide code example
 | 
        
           |  |  | 3436 |  *
 | 
        
           |  |  | 3437 |  * @param string $username New user's username to add to record
 | 
        
           |  |  | 3438 |  * @param string $password New user's password to add to record
 | 
        
           |  |  | 3439 |  * @param string $auth Form of authentication required
 | 
        
           |  |  | 3440 |  * @return stdClass A complete user object
 | 
        
           |  |  | 3441 |  */
 | 
        
           | 1326 | ariadna | 3442 | function create_user_record($username, $password, $auth = 'manual')
 | 
        
           |  |  | 3443 | {
 | 
        
           | 1 | efrain | 3444 |     global $CFG, $DB, $SESSION;
 | 
        
           | 1326 | ariadna | 3445 |     require_once($CFG->dirroot . '/user/profile/lib.php');
 | 
        
           |  |  | 3446 |     require_once($CFG->dirroot . '/user/lib.php');
 | 
        
           | 1 | efrain | 3447 |   | 
        
           |  |  | 3448 |     // Just in case check text case.
 | 
        
           |  |  | 3449 |     $username = trim(core_text::strtolower($username));
 | 
        
           |  |  | 3450 |   | 
        
           |  |  | 3451 |     $authplugin = get_auth_plugin($auth);
 | 
        
           |  |  | 3452 |     $customfields = $authplugin->get_custom_user_profile_fields();
 | 
        
           |  |  | 3453 |     $newuser = new stdClass();
 | 
        
           |  |  | 3454 |     if ($newinfo = $authplugin->get_userinfo($username)) {
 | 
        
           |  |  | 3455 |         $newinfo = truncate_userinfo($newinfo);
 | 
        
           |  |  | 3456 |         foreach ($newinfo as $key => $value) {
 | 
        
           |  |  | 3457 |             if (in_array($key, $authplugin->userfields) || (in_array($key, $customfields))) {
 | 
        
           |  |  | 3458 |                 $newuser->$key = $value;
 | 
        
           |  |  | 3459 |             }
 | 
        
           |  |  | 3460 |         }
 | 
        
           |  |  | 3461 |     }
 | 
        
           |  |  | 3462 |   | 
        
           |  |  | 3463 |     if (!empty($newuser->email)) {
 | 
        
           |  |  | 3464 |         if (email_is_not_allowed($newuser->email)) {
 | 
        
           |  |  | 3465 |             unset($newuser->email);
 | 
        
           |  |  | 3466 |         }
 | 
        
           |  |  | 3467 |     }
 | 
        
           |  |  | 3468 |   | 
        
           |  |  | 3469 |     $newuser->auth = $auth;
 | 
        
           |  |  | 3470 |     $newuser->username = $username;
 | 
        
           |  |  | 3471 |   | 
        
           |  |  | 3472 |     // Fix for MDL-8480
 | 
        
           |  |  | 3473 |     // user CFG lang for user if $newuser->lang is empty
 | 
        
           |  |  | 3474 |     // or $user->lang is not an installed language.
 | 
        
           |  |  | 3475 |     if (empty($newuser->lang) || !get_string_manager()->translation_exists($newuser->lang)) {
 | 
        
           |  |  | 3476 |         $newuser->lang = get_newuser_language();
 | 
        
           |  |  | 3477 |     }
 | 
        
           |  |  | 3478 |     $newuser->confirmed = 1;
 | 
        
           |  |  | 3479 |     $newuser->lastip = getremoteaddr();
 | 
        
           |  |  | 3480 |     $newuser->timecreated = time();
 | 
        
           |  |  | 3481 |     $newuser->timemodified = $newuser->timecreated;
 | 
        
           |  |  | 3482 |     $newuser->mnethostid = $CFG->mnet_localhost_id;
 | 
        
           |  |  | 3483 |   | 
        
           |  |  | 3484 |     $newuser->id = user_create_user($newuser, false, false);
 | 
        
           |  |  | 3485 |   | 
        
           |  |  | 3486 |     // Save user profile data.
 | 
        
           |  |  | 3487 |     profile_save_data($newuser);
 | 
        
           |  |  | 3488 |   | 
        
           |  |  | 3489 |     $user = get_complete_user_data('id', $newuser->id);
 | 
        
           | 1326 | ariadna | 3490 |     if (!empty($CFG->{'auth_' . $newuser->auth . '_forcechangepassword'})) {
 | 
        
           | 1 | efrain | 3491 |         set_user_preference('auth_forcepasswordchange', 1, $user);
 | 
        
           |  |  | 3492 |     }
 | 
        
           |  |  | 3493 |     // Set the password.
 | 
        
           |  |  | 3494 |     update_internal_user_password($user, $password);
 | 
        
           |  |  | 3495 |   | 
        
           |  |  | 3496 |     // Trigger event.
 | 
        
           |  |  | 3497 |     \core\event\user_created::create_from_userid($newuser->id)->trigger();
 | 
        
           |  |  | 3498 |   | 
        
           |  |  | 3499 |     return $user;
 | 
        
           |  |  | 3500 | }
 | 
        
           |  |  | 3501 |   | 
        
           |  |  | 3502 | /**
 | 
        
           |  |  | 3503 |  * Will update a local user record from an external source (MNET users can not be updated using this method!).
 | 
        
           |  |  | 3504 |  *
 | 
        
           |  |  | 3505 |  * @param string $username user's username to update the record
 | 
        
           |  |  | 3506 |  * @return stdClass A complete user object
 | 
        
           |  |  | 3507 |  */
 | 
        
           | 1326 | ariadna | 3508 | function update_user_record($username)
 | 
        
           |  |  | 3509 | {
 | 
        
           | 1 | efrain | 3510 |     global $DB, $CFG;
 | 
        
           |  |  | 3511 |     // Just in case check text case.
 | 
        
           |  |  | 3512 |     $username = trim(core_text::strtolower($username));
 | 
        
           |  |  | 3513 |   | 
        
           |  |  | 3514 |     $oldinfo = $DB->get_record('user', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id), '*', MUST_EXIST);
 | 
        
           |  |  | 3515 |     return update_user_record_by_id($oldinfo->id);
 | 
        
           |  |  | 3516 | }
 | 
        
           |  |  | 3517 |   | 
        
           |  |  | 3518 | /**
 | 
        
           |  |  | 3519 |  * Will update a local user record from an external source (MNET users can not be updated using this method!).
 | 
        
           |  |  | 3520 |  *
 | 
        
           |  |  | 3521 |  * @param int $id user id
 | 
        
           |  |  | 3522 |  * @return stdClass A complete user object
 | 
        
           |  |  | 3523 |  */
 | 
        
           | 1326 | ariadna | 3524 | function update_user_record_by_id($id)
 | 
        
           |  |  | 3525 | {
 | 
        
           | 1 | efrain | 3526 |     global $DB, $CFG;
 | 
        
           | 1326 | ariadna | 3527 |     require_once($CFG->dirroot . "/user/profile/lib.php");
 | 
        
           |  |  | 3528 |     require_once($CFG->dirroot . '/user/lib.php');
 | 
        
           | 1 | efrain | 3529 |   | 
        
           |  |  | 3530 |     $params = array('mnethostid' => $CFG->mnet_localhost_id, 'id' => $id, 'deleted' => 0);
 | 
        
           |  |  | 3531 |     $oldinfo = $DB->get_record('user', $params, '*', MUST_EXIST);
 | 
        
           |  |  | 3532 |   | 
        
           |  |  | 3533 |     $newuser = array();
 | 
        
           |  |  | 3534 |     $userauth = get_auth_plugin($oldinfo->auth);
 | 
        
           |  |  | 3535 |   | 
        
           |  |  | 3536 |     if ($newinfo = $userauth->get_userinfo($oldinfo->username)) {
 | 
        
           |  |  | 3537 |         $newinfo = truncate_userinfo($newinfo);
 | 
        
           |  |  | 3538 |         $customfields = $userauth->get_custom_user_profile_fields();
 | 
        
           |  |  | 3539 |   | 
        
           |  |  | 3540 |         foreach ($newinfo as $key => $value) {
 | 
        
           |  |  | 3541 |             $iscustom = in_array($key, $customfields);
 | 
        
           |  |  | 3542 |             if (!$iscustom) {
 | 
        
           |  |  | 3543 |                 $key = strtolower($key);
 | 
        
           |  |  | 3544 |             }
 | 
        
           |  |  | 3545 |             if ((!property_exists($oldinfo, $key) && !$iscustom) or $key === 'username' or $key === 'id'
 | 
        
           | 1326 | ariadna | 3546 |                 or $key === 'auth' or $key === 'mnethostid' or $key === 'deleted'
 | 
        
           |  |  | 3547 |             ) {
 | 
        
           | 1 | efrain | 3548 |                 // Unknown or must not be changed.
 | 
        
           |  |  | 3549 |                 continue;
 | 
        
           |  |  | 3550 |             }
 | 
        
           |  |  | 3551 |             if (empty($userauth->config->{'field_updatelocal_' . $key}) || empty($userauth->config->{'field_lock_' . $key})) {
 | 
        
           |  |  | 3552 |                 continue;
 | 
        
           |  |  | 3553 |             }
 | 
        
           |  |  | 3554 |             $confval = $userauth->config->{'field_updatelocal_' . $key};
 | 
        
           |  |  | 3555 |             $lockval = $userauth->config->{'field_lock_' . $key};
 | 
        
           |  |  | 3556 |             if ($confval === 'onlogin') {
 | 
        
           |  |  | 3557 |                 // MDL-4207 Don't overwrite modified user profile values with
 | 
        
           |  |  | 3558 |                 // empty LDAP values when 'unlocked if empty' is set. The purpose
 | 
        
           |  |  | 3559 |                 // of the setting 'unlocked if empty' is to allow the user to fill
 | 
        
           |  |  | 3560 |                 // in a value for the selected field _if LDAP is giving
 | 
        
           |  |  | 3561 |                 // nothing_ for this field. Thus it makes sense to let this value
 | 
        
           |  |  | 3562 |                 // stand in until LDAP is giving a value for this field.
 | 
        
           |  |  | 3563 |                 if (!(empty($value) && $lockval === 'unlockedifempty')) {
 | 
        
           |  |  | 3564 |                     if ($iscustom || (in_array($key, $userauth->userfields) &&
 | 
        
           | 1326 | ariadna | 3565 |                         ((string)$oldinfo->$key !== (string)$value))) {
 | 
        
           | 1 | efrain | 3566 |                         $newuser[$key] = (string)$value;
 | 
        
           |  |  | 3567 |                     }
 | 
        
           |  |  | 3568 |                 }
 | 
        
           |  |  | 3569 |             }
 | 
        
           |  |  | 3570 |         }
 | 
        
           |  |  | 3571 |         if ($newuser) {
 | 
        
           |  |  | 3572 |             $newuser['id'] = $oldinfo->id;
 | 
        
           |  |  | 3573 |             $newuser['timemodified'] = time();
 | 
        
           |  |  | 3574 |             user_update_user((object) $newuser, false, false);
 | 
        
           |  |  | 3575 |   | 
        
           |  |  | 3576 |             // Save user profile data.
 | 
        
           |  |  | 3577 |             profile_save_data((object) $newuser);
 | 
        
           |  |  | 3578 |   | 
        
           |  |  | 3579 |             // Trigger event.
 | 
        
           |  |  | 3580 |             \core\event\user_updated::create_from_userid($newuser['id'])->trigger();
 | 
        
           |  |  | 3581 |         }
 | 
        
           |  |  | 3582 |     }
 | 
        
           |  |  | 3583 |   | 
        
           |  |  | 3584 |     return get_complete_user_data('id', $oldinfo->id);
 | 
        
           |  |  | 3585 | }
 | 
        
           |  |  | 3586 |   | 
        
           |  |  | 3587 | /**
 | 
        
           |  |  | 3588 |  * Will truncate userinfo as it comes from auth_get_userinfo (from external auth) which may have large fields.
 | 
        
           |  |  | 3589 |  *
 | 
        
           |  |  | 3590 |  * @param array $info Array of user properties to truncate if needed
 | 
        
           |  |  | 3591 |  * @return array The now truncated information that was passed in
 | 
        
           |  |  | 3592 |  */
 | 
        
           | 1326 | ariadna | 3593 | function truncate_userinfo(array $info)
 | 
        
           |  |  | 3594 | {
 | 
        
           | 1 | efrain | 3595 |     // Define the limits.
 | 
        
           |  |  | 3596 |     $limit = array(
 | 
        
           |  |  | 3597 |         'username'    => 100,
 | 
        
           |  |  | 3598 |         'idnumber'    => 255,
 | 
        
           |  |  | 3599 |         'firstname'   => 100,
 | 
        
           |  |  | 3600 |         'lastname'    => 100,
 | 
        
           |  |  | 3601 |         'email'       => 100,
 | 
        
           |  |  | 3602 |         'phone1'      =>  20,
 | 
        
           |  |  | 3603 |         'phone2'      =>  20,
 | 
        
           |  |  | 3604 |         'institution' => 255,
 | 
        
           |  |  | 3605 |         'department'  => 255,
 | 
        
           |  |  | 3606 |         'address'     => 255,
 | 
        
           |  |  | 3607 |         'city'        => 120,
 | 
        
           |  |  | 3608 |         'country'     =>   2,
 | 
        
           |  |  | 3609 |     );
 | 
        
           |  |  | 3610 |   | 
        
           |  |  | 3611 |     // Apply where needed.
 | 
        
           |  |  | 3612 |     foreach (array_keys($info) as $key) {
 | 
        
           |  |  | 3613 |         if (!empty($limit[$key])) {
 | 
        
           |  |  | 3614 |             $info[$key] = trim(core_text::substr($info[$key], 0, $limit[$key]));
 | 
        
           |  |  | 3615 |         }
 | 
        
           |  |  | 3616 |     }
 | 
        
           |  |  | 3617 |   | 
        
           |  |  | 3618 |     return $info;
 | 
        
           |  |  | 3619 | }
 | 
        
           |  |  | 3620 |   | 
        
           |  |  | 3621 | /**
 | 
        
           |  |  | 3622 |  * Marks user deleted in internal user database and notifies the auth plugin.
 | 
        
           |  |  | 3623 |  * Also unenrols user from all roles and does other cleanup.
 | 
        
           |  |  | 3624 |  *
 | 
        
           |  |  | 3625 |  * Any plugin that needs to purge user data should register the 'user_deleted' event.
 | 
        
           |  |  | 3626 |  *
 | 
        
           |  |  | 3627 |  * @param stdClass $user full user object before delete
 | 
        
           |  |  | 3628 |  * @return boolean success
 | 
        
           |  |  | 3629 |  * @throws coding_exception if invalid $user parameter detected
 | 
        
           |  |  | 3630 |  */
 | 
        
           | 1326 | ariadna | 3631 | function delete_user(stdClass $user)
 | 
        
           |  |  | 3632 | {
 | 
        
           | 1 | efrain | 3633 |     global $CFG, $DB, $SESSION;
 | 
        
           | 1326 | ariadna | 3634 |     require_once($CFG->libdir . '/grouplib.php');
 | 
        
           |  |  | 3635 |     require_once($CFG->libdir . '/gradelib.php');
 | 
        
           |  |  | 3636 |     require_once($CFG->dirroot . '/message/lib.php');
 | 
        
           |  |  | 3637 |     require_once($CFG->dirroot . '/user/lib.php');
 | 
        
           | 1 | efrain | 3638 |   | 
        
           |  |  | 3639 |     // Make sure nobody sends bogus record type as parameter.
 | 
        
           |  |  | 3640 |     if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
 | 
        
           |  |  | 3641 |         throw new coding_exception('Invalid $user parameter in delete_user() detected');
 | 
        
           |  |  | 3642 |     }
 | 
        
           |  |  | 3643 |   | 
        
           |  |  | 3644 |     // Better not trust the parameter and fetch the latest info this will be very expensive anyway.
 | 
        
           |  |  | 3645 |     if (!$user = $DB->get_record('user', array('id' => $user->id))) {
 | 
        
           |  |  | 3646 |         debugging('Attempt to delete unknown user account.');
 | 
        
           |  |  | 3647 |         return false;
 | 
        
           |  |  | 3648 |     }
 | 
        
           |  |  | 3649 |   | 
        
           |  |  | 3650 |     // There must be always exactly one guest record, originally the guest account was identified by username only,
 | 
        
           |  |  | 3651 |     // now we use $CFG->siteguest for performance reasons.
 | 
        
           |  |  | 3652 |     if ($user->username === 'guest' or isguestuser($user)) {
 | 
        
           |  |  | 3653 |         debugging('Guest user account can not be deleted.');
 | 
        
           |  |  | 3654 |         return false;
 | 
        
           |  |  | 3655 |     }
 | 
        
           |  |  | 3656 |   | 
        
           |  |  | 3657 |     // Admin can be theoretically from different auth plugin, but we want to prevent deletion of internal accoutns only,
 | 
        
           |  |  | 3658 |     // if anything goes wrong ppl may force somebody to be admin via config.php setting $CFG->siteadmins.
 | 
        
           |  |  | 3659 |     if ($user->auth === 'manual' and is_siteadmin($user)) {
 | 
        
           |  |  | 3660 |         debugging('Local administrator accounts can not be deleted.');
 | 
        
           |  |  | 3661 |         return false;
 | 
        
           |  |  | 3662 |     }
 | 
        
           |  |  | 3663 |     // Allow plugins to use this user object before we completely delete it.
 | 
        
           |  |  | 3664 |     if ($pluginsfunction = get_plugins_with_function('pre_user_delete')) {
 | 
        
           |  |  | 3665 |         foreach ($pluginsfunction as $plugintype => $plugins) {
 | 
        
           |  |  | 3666 |             foreach ($plugins as $pluginfunction) {
 | 
        
           |  |  | 3667 |                 $pluginfunction($user);
 | 
        
           |  |  | 3668 |             }
 | 
        
           |  |  | 3669 |         }
 | 
        
           |  |  | 3670 |     }
 | 
        
           |  |  | 3671 |   | 
        
           |  |  | 3672 |     // Dispatch the hook for pre user update actions.
 | 
        
           |  |  | 3673 |     $hook = new \core_user\hook\before_user_deleted(
 | 
        
           |  |  | 3674 |         user: $user,
 | 
        
           |  |  | 3675 |     );
 | 
        
           |  |  | 3676 |     di::get(hook\manager::class)->dispatch($hook);
 | 
        
           |  |  | 3677 |   | 
        
           |  |  | 3678 |     // Keep user record before updating it, as we have to pass this to user_deleted event.
 | 
        
           |  |  | 3679 |     $olduser = clone $user;
 | 
        
           |  |  | 3680 |   | 
        
           |  |  | 3681 |     // Keep a copy of user context, we need it for event.
 | 
        
           |  |  | 3682 |     $usercontext = context_user::instance($user->id);
 | 
        
           |  |  | 3683 |   | 
        
           |  |  | 3684 |     // Delete all grades - backup is kept in grade_grades_history table.
 | 
        
           |  |  | 3685 |     grade_user_delete($user->id);
 | 
        
           |  |  | 3686 |   | 
        
           |  |  | 3687 |     // TODO: remove from cohorts using standard API here.
 | 
        
           |  |  | 3688 |   | 
        
           |  |  | 3689 |     // Remove user tags.
 | 
        
           |  |  | 3690 |     core_tag_tag::remove_all_item_tags('core', 'user', $user->id);
 | 
        
           |  |  | 3691 |   | 
        
           |  |  | 3692 |     // Unconditionally unenrol from all courses.
 | 
        
           |  |  | 3693 |     enrol_user_delete($user);
 | 
        
           |  |  | 3694 |   | 
        
           |  |  | 3695 |     // Unenrol from all roles in all contexts.
 | 
        
           |  |  | 3696 |     // This might be slow but it is really needed - modules might do some extra cleanup!
 | 
        
           |  |  | 3697 |     role_unassign_all(array('userid' => $user->id));
 | 
        
           |  |  | 3698 |   | 
        
           |  |  | 3699 |     // Notify the competency subsystem.
 | 
        
           |  |  | 3700 |     \core_competency\api::hook_user_deleted($user->id);
 | 
        
           |  |  | 3701 |   | 
        
           |  |  | 3702 |     // Now do a brute force cleanup.
 | 
        
           |  |  | 3703 |   | 
        
           |  |  | 3704 |     // Delete all user events and subscription events.
 | 
        
           |  |  | 3705 |     $DB->delete_records_select('event', 'userid = :userid AND subscriptionid IS NOT NULL', ['userid' => $user->id]);
 | 
        
           |  |  | 3706 |   | 
        
           |  |  | 3707 |     // Now, delete all calendar subscription from the user.
 | 
        
           |  |  | 3708 |     $DB->delete_records('event_subscriptions', ['userid' => $user->id]);
 | 
        
           |  |  | 3709 |   | 
        
           |  |  | 3710 |     // Remove from all cohorts.
 | 
        
           |  |  | 3711 |     $DB->delete_records('cohort_members', array('userid' => $user->id));
 | 
        
           |  |  | 3712 |   | 
        
           |  |  | 3713 |     // Remove from all groups.
 | 
        
           |  |  | 3714 |     $DB->delete_records('groups_members', array('userid' => $user->id));
 | 
        
           |  |  | 3715 |   | 
        
           |  |  | 3716 |     // Brute force unenrol from all courses.
 | 
        
           |  |  | 3717 |     $DB->delete_records('user_enrolments', array('userid' => $user->id));
 | 
        
           |  |  | 3718 |   | 
        
           |  |  | 3719 |     // Purge user preferences.
 | 
        
           |  |  | 3720 |     $DB->delete_records('user_preferences', array('userid' => $user->id));
 | 
        
           |  |  | 3721 |   | 
        
           |  |  | 3722 |     // Purge user extra profile info.
 | 
        
           |  |  | 3723 |     $DB->delete_records('user_info_data', array('userid' => $user->id));
 | 
        
           |  |  | 3724 |   | 
        
           |  |  | 3725 |     // Purge log of previous password hashes.
 | 
        
           |  |  | 3726 |     $DB->delete_records('user_password_history', array('userid' => $user->id));
 | 
        
           |  |  | 3727 |   | 
        
           |  |  | 3728 |     // Last course access not necessary either.
 | 
        
           |  |  | 3729 |     $DB->delete_records('user_lastaccess', array('userid' => $user->id));
 | 
        
           |  |  | 3730 |     // Remove all user tokens.
 | 
        
           |  |  | 3731 |     $DB->delete_records('external_tokens', array('userid' => $user->id));
 | 
        
           |  |  | 3732 |   | 
        
           |  |  | 3733 |     // Unauthorise the user for all services.
 | 
        
           |  |  | 3734 |     $DB->delete_records('external_services_users', array('userid' => $user->id));
 | 
        
           |  |  | 3735 |   | 
        
           |  |  | 3736 |     // Remove users private keys.
 | 
        
           |  |  | 3737 |     $DB->delete_records('user_private_key', array('userid' => $user->id));
 | 
        
           |  |  | 3738 |   | 
        
           |  |  | 3739 |     // Remove users customised pages.
 | 
        
           |  |  | 3740 |     $DB->delete_records('my_pages', array('userid' => $user->id, 'private' => 1));
 | 
        
           |  |  | 3741 |   | 
        
           |  |  | 3742 |     // Remove user's oauth2 refresh tokens, if present.
 | 
        
           |  |  | 3743 |     $DB->delete_records('oauth2_refresh_token', array('userid' => $user->id));
 | 
        
           |  |  | 3744 |   | 
        
           |  |  | 3745 |     // Delete user from $SESSION->bulk_users.
 | 
        
           |  |  | 3746 |     if (isset($SESSION->bulk_users[$user->id])) {
 | 
        
           |  |  | 3747 |         unset($SESSION->bulk_users[$user->id]);
 | 
        
           |  |  | 3748 |     }
 | 
        
           |  |  | 3749 |   | 
        
           |  |  | 3750 |     // Force logout - may fail if file based sessions used, sorry.
 | 
        
           |  |  | 3751 |     \core\session\manager::kill_user_sessions($user->id);
 | 
        
           |  |  | 3752 |   | 
        
           |  |  | 3753 |     // Generate username from email address, or a fake email.
 | 
        
           |  |  | 3754 |     $delemail = !empty($user->email) ? $user->email : $user->username . '.' . $user->id . '@unknownemail.invalid';
 | 
        
           |  |  | 3755 |   | 
        
           |  |  | 3756 |     $deltime = time();
 | 
        
           |  |  | 3757 |   | 
        
           |  |  | 3758 |     // Max username length is 100 chars. Select up to limit - (length of current time + 1 [period character]) from users email.
 | 
        
           |  |  | 3759 |     $delnameprefix = clean_param($delemail, PARAM_USERNAME);
 | 
        
           |  |  | 3760 |     $delnamesuffix = $deltime;
 | 
        
           |  |  | 3761 |     $delnamesuffixlength = 10;
 | 
        
           |  |  | 3762 |     do {
 | 
        
           |  |  | 3763 |         // Workaround for bulk deletes of users with the same email address.
 | 
        
           |  |  | 3764 |         $delname = sprintf(
 | 
        
           |  |  | 3765 |             "%s.%10d",
 | 
        
           |  |  | 3766 |             core_text::substr(
 | 
        
           |  |  | 3767 |                 $delnameprefix,
 | 
        
           |  |  | 3768 |                 0,
 | 
        
           |  |  | 3769 |                 // 100 Character maximum, with a '.' character, and a 10-digit timestamp.
 | 
        
           |  |  | 3770 |                 100 - 1 - $delnamesuffixlength,
 | 
        
           |  |  | 3771 |             ),
 | 
        
           |  |  | 3772 |             $delnamesuffix,
 | 
        
           |  |  | 3773 |         );
 | 
        
           |  |  | 3774 |         $delnamesuffix++;
 | 
        
           |  |  | 3775 |   | 
        
           |  |  | 3776 |         // No need to use mnethostid here.
 | 
        
           |  |  | 3777 |     } while ($DB->record_exists('user', ['username' => $delname]));
 | 
        
           |  |  | 3778 |   | 
        
           |  |  | 3779 |     // Mark internal user record as "deleted".
 | 
        
           |  |  | 3780 |     $updateuser = new stdClass();
 | 
        
           |  |  | 3781 |     $updateuser->id           = $user->id;
 | 
        
           |  |  | 3782 |     $updateuser->deleted      = 1;
 | 
        
           |  |  | 3783 |     $updateuser->username     = $delname;            // Remember it just in case.
 | 
        
           | 1326 | ariadna | 3784 |     $updateuser->email        = md5($user->username); // Store hash of username, useful importing/restoring users.
 | 
        
           | 1 | efrain | 3785 |     $updateuser->idnumber     = '';                  // Clear this field to free it up.
 | 
        
           |  |  | 3786 |     $updateuser->picture      = 0;
 | 
        
           |  |  | 3787 |     $updateuser->timemodified = $deltime;
 | 
        
           |  |  | 3788 |   | 
        
           |  |  | 3789 |     // Don't trigger update event, as user is being deleted.
 | 
        
           |  |  | 3790 |     user_update_user($updateuser, false, false);
 | 
        
           |  |  | 3791 |   | 
        
           |  |  | 3792 |     // Delete all content associated with the user context, but not the context itself.
 | 
        
           |  |  | 3793 |     $usercontext->delete_content();
 | 
        
           |  |  | 3794 |   | 
        
           |  |  | 3795 |     // Delete any search data.
 | 
        
           |  |  | 3796 |     \core_search\manager::context_deleted($usercontext);
 | 
        
           |  |  | 3797 |   | 
        
           |  |  | 3798 |     // Any plugin that needs to cleanup should register this event.
 | 
        
           |  |  | 3799 |     // Trigger event.
 | 
        
           |  |  | 3800 |     $event = \core\event\user_deleted::create(
 | 
        
           | 1326 | ariadna | 3801 |         array(
 | 
        
           |  |  | 3802 |             'objectid' => $user->id,
 | 
        
           |  |  | 3803 |             'relateduserid' => $user->id,
 | 
        
           |  |  | 3804 |             'context' => $usercontext,
 | 
        
           |  |  | 3805 |             'other' => array(
 | 
        
           |  |  | 3806 |                 'username' => $user->username,
 | 
        
           |  |  | 3807 |                 'email' => $user->email,
 | 
        
           |  |  | 3808 |                 'idnumber' => $user->idnumber,
 | 
        
           |  |  | 3809 |                 'picture' => $user->picture,
 | 
        
           |  |  | 3810 |                 'mnethostid' => $user->mnethostid
 | 
        
           |  |  | 3811 |             )
 | 
        
           |  |  | 3812 |         )
 | 
        
           |  |  | 3813 |     );
 | 
        
           | 1 | efrain | 3814 |     $event->add_record_snapshot('user', $olduser);
 | 
        
           |  |  | 3815 |     $event->trigger();
 | 
        
           |  |  | 3816 |   | 
        
           |  |  | 3817 |     // We will update the user's timemodified, as it will be passed to the user_deleted event, which
 | 
        
           |  |  | 3818 |     // should know about this updated property persisted to the user's table.
 | 
        
           |  |  | 3819 |     $user->timemodified = $updateuser->timemodified;
 | 
        
           |  |  | 3820 |   | 
        
           |  |  | 3821 |     // Notify auth plugin - do not block the delete even when plugin fails.
 | 
        
           |  |  | 3822 |     $authplugin = get_auth_plugin($user->auth);
 | 
        
           |  |  | 3823 |     $authplugin->user_delete($user);
 | 
        
           |  |  | 3824 |   | 
        
           |  |  | 3825 |     return true;
 | 
        
           |  |  | 3826 | }
 | 
        
           |  |  | 3827 |   | 
        
           |  |  | 3828 | /**
 | 
        
           |  |  | 3829 |  * Retrieve the guest user object.
 | 
        
           |  |  | 3830 |  *
 | 
        
           |  |  | 3831 |  * @return stdClass A {@link $USER} object
 | 
        
           |  |  | 3832 |  */
 | 
        
           | 1326 | ariadna | 3833 | function guest_user()
 | 
        
           |  |  | 3834 | {
 | 
        
           | 1 | efrain | 3835 |     global $CFG, $DB;
 | 
        
           |  |  | 3836 |   | 
        
           |  |  | 3837 |     if ($newuser = $DB->get_record('user', array('id' => $CFG->siteguest))) {
 | 
        
           |  |  | 3838 |         $newuser->confirmed = 1;
 | 
        
           |  |  | 3839 |         $newuser->lang = get_newuser_language();
 | 
        
           |  |  | 3840 |         $newuser->lastip = getremoteaddr();
 | 
        
           |  |  | 3841 |     }
 | 
        
           |  |  | 3842 |   | 
        
           |  |  | 3843 |     return $newuser;
 | 
        
           |  |  | 3844 | }
 | 
        
           |  |  | 3845 |   | 
        
           |  |  | 3846 | /**
 | 
        
           |  |  | 3847 |  * Authenticates a user against the chosen authentication mechanism
 | 
        
           |  |  | 3848 |  *
 | 
        
           |  |  | 3849 |  * Given a username and password, this function looks them
 | 
        
           |  |  | 3850 |  * up using the currently selected authentication mechanism,
 | 
        
           |  |  | 3851 |  * and if the authentication is successful, it returns a
 | 
        
           |  |  | 3852 |  * valid $user object from the 'user' table.
 | 
        
           |  |  | 3853 |  *
 | 
        
           |  |  | 3854 |  * Uses auth_ functions from the currently active auth module
 | 
        
           |  |  | 3855 |  *
 | 
        
           |  |  | 3856 |  * After authenticate_user_login() returns success, you will need to
 | 
        
           |  |  | 3857 |  * log that the user has logged in, and call complete_user_login() to set
 | 
        
           |  |  | 3858 |  * the session up.
 | 
        
           |  |  | 3859 |  *
 | 
        
           |  |  | 3860 |  * Note: this function works only with non-mnet accounts!
 | 
        
           |  |  | 3861 |  *
 | 
        
           |  |  | 3862 |  * @param string $username  User's username (or also email if $CFG->authloginviaemail enabled)
 | 
        
           |  |  | 3863 |  * @param string $password  User's password
 | 
        
           |  |  | 3864 |  * @param bool $ignorelockout useful when guessing is prevented by other mechanism such as captcha or SSO
 | 
        
           |  |  | 3865 |  * @param int $failurereason login failure reason, can be used in renderers (it may disclose if account exists)
 | 
        
           |  |  | 3866 |  * @param string|bool $logintoken If this is set to a string it is validated against the login token for the session.
 | 
        
           |  |  | 3867 |  * @param string|bool $loginrecaptcha If this is set to a string it is validated against Google reCaptcha.
 | 
        
           |  |  | 3868 |  * @return stdClass|false A {@link $USER} object or false if error
 | 
        
           |  |  | 3869 |  */
 | 
        
           |  |  | 3870 | function authenticate_user_login(
 | 
        
           |  |  | 3871 |     $username,
 | 
        
           |  |  | 3872 |     $password,
 | 
        
           |  |  | 3873 |     $ignorelockout = false,
 | 
        
           |  |  | 3874 |     &$failurereason = null,
 | 
        
           |  |  | 3875 |     $logintoken = false,
 | 
        
           |  |  | 3876 |     string|bool $loginrecaptcha = false,
 | 
        
           |  |  | 3877 | ) {
 | 
        
           |  |  | 3878 |     global $CFG, $DB, $PAGE, $SESSION;
 | 
        
           |  |  | 3879 |     require_once("$CFG->libdir/authlib.php");
 | 
        
           |  |  | 3880 |   | 
        
           |  |  | 3881 |     if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
 | 
        
           |  |  | 3882 |         // we have found the user
 | 
        
           |  |  | 3883 |   | 
        
           |  |  | 3884 |     } else if (!empty($CFG->authloginviaemail)) {
 | 
        
           |  |  | 3885 |         if ($email = clean_param($username, PARAM_EMAIL)) {
 | 
        
           |  |  | 3886 |             $select = "mnethostid = :mnethostid AND LOWER(email) = LOWER(:email) AND deleted = 0";
 | 
        
           |  |  | 3887 |             $params = array('mnethostid' => $CFG->mnet_localhost_id, 'email' => $email);
 | 
        
           |  |  | 3888 |             $users = $DB->get_records_select('user', $select, $params, 'id', 'id', 0, 2);
 | 
        
           |  |  | 3889 |             if (count($users) === 1) {
 | 
        
           |  |  | 3890 |                 // Use email for login only if unique.
 | 
        
           |  |  | 3891 |                 $user = reset($users);
 | 
        
           |  |  | 3892 |                 $user = get_complete_user_data('id', $user->id);
 | 
        
           |  |  | 3893 |                 $username = $user->username;
 | 
        
           |  |  | 3894 |             }
 | 
        
           |  |  | 3895 |             unset($users);
 | 
        
           |  |  | 3896 |         }
 | 
        
           |  |  | 3897 |     }
 | 
        
           |  |  | 3898 |   | 
        
           |  |  | 3899 |     // Make sure this request came from the login form.
 | 
        
           |  |  | 3900 |     if (!\core\session\manager::validate_login_token($logintoken)) {
 | 
        
           |  |  | 3901 |         $failurereason = AUTH_LOGIN_FAILED;
 | 
        
           |  |  | 3902 |   | 
        
           |  |  | 3903 |         // Trigger login failed event (specifying the ID of the found user, if available).
 | 
        
           |  |  | 3904 |         \core\event\user_login_failed::create([
 | 
        
           |  |  | 3905 |             'userid' => ($user->id ?? 0),
 | 
        
           |  |  | 3906 |             'other' => [
 | 
        
           |  |  | 3907 |                 'username' => $username,
 | 
        
           |  |  | 3908 |                 'reason' => $failurereason,
 | 
        
           |  |  | 3909 |             ],
 | 
        
           |  |  | 3910 |         ])->trigger();
 | 
        
           |  |  | 3911 |   | 
        
           | 1326 | ariadna | 3912 |         error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Invalid Login Token:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 3913 |         return false;
 | 
        
           |  |  | 3914 |     }
 | 
        
           |  |  | 3915 |   | 
        
           |  |  | 3916 |     // Login reCaptcha.
 | 
        
           |  |  | 3917 |     if (login_captcha_enabled() && !validate_login_captcha($loginrecaptcha)) {
 | 
        
           |  |  | 3918 |         $failurereason = AUTH_LOGIN_FAILED_RECAPTCHA;
 | 
        
           |  |  | 3919 |         // Trigger login failed event (specifying the ID of the found user, if available).
 | 
        
           |  |  | 3920 |         \core\event\user_login_failed::create([
 | 
        
           |  |  | 3921 |             'userid' => ($user->id ?? 0),
 | 
        
           |  |  | 3922 |             'other' => [
 | 
        
           |  |  | 3923 |                 'username' => $username,
 | 
        
           |  |  | 3924 |                 'reason' => $failurereason,
 | 
        
           |  |  | 3925 |             ],
 | 
        
           |  |  | 3926 |         ])->trigger();
 | 
        
           |  |  | 3927 |         return false;
 | 
        
           |  |  | 3928 |     }
 | 
        
           |  |  | 3929 |   | 
        
           |  |  | 3930 |     $authsenabled = get_enabled_auth_plugins();
 | 
        
           |  |  | 3931 |   | 
        
           |  |  | 3932 |     if ($user) {
 | 
        
           |  |  | 3933 |         // Use manual if auth not set.
 | 
        
           |  |  | 3934 |         $auth = empty($user->auth) ? 'manual' : $user->auth;
 | 
        
           |  |  | 3935 |   | 
        
           |  |  | 3936 |         if (in_array($user->auth, $authsenabled)) {
 | 
        
           |  |  | 3937 |             $authplugin = get_auth_plugin($user->auth);
 | 
        
           |  |  | 3938 |             $authplugin->pre_user_login_hook($user);
 | 
        
           |  |  | 3939 |         }
 | 
        
           |  |  | 3940 |   | 
        
           |  |  | 3941 |         if (!empty($user->suspended)) {
 | 
        
           |  |  | 3942 |             $failurereason = AUTH_LOGIN_SUSPENDED;
 | 
        
           |  |  | 3943 |   | 
        
           |  |  | 3944 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 3945 |             $event = \core\event\user_login_failed::create(array(
 | 
        
           |  |  | 3946 |                 'userid' => $user->id,
 | 
        
           |  |  | 3947 |                 'other' => array('username' => $username, 'reason' => $failurereason)
 | 
        
           |  |  | 3948 |             ));
 | 
        
           | 1 | efrain | 3949 |             $event->trigger();
 | 
        
           | 1326 | ariadna | 3950 |             error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Suspended Login:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 3951 |             return false;
 | 
        
           |  |  | 3952 |         }
 | 
        
           | 1326 | ariadna | 3953 |         if ($auth == 'nologin' or !is_enabled_auth($auth)) {
 | 
        
           | 1 | efrain | 3954 |             // Legacy way to suspend user.
 | 
        
           |  |  | 3955 |             $failurereason = AUTH_LOGIN_SUSPENDED;
 | 
        
           |  |  | 3956 |   | 
        
           |  |  | 3957 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 3958 |             $event = \core\event\user_login_failed::create(array(
 | 
        
           |  |  | 3959 |                 'userid' => $user->id,
 | 
        
           |  |  | 3960 |                 'other' => array('username' => $username, 'reason' => $failurereason)
 | 
        
           |  |  | 3961 |             ));
 | 
        
           | 1 | efrain | 3962 |             $event->trigger();
 | 
        
           | 1326 | ariadna | 3963 |             error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Disabled Login:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 3964 |             return false;
 | 
        
           |  |  | 3965 |         }
 | 
        
           |  |  | 3966 |         $auths = array($auth);
 | 
        
           |  |  | 3967 |     } else {
 | 
        
           |  |  | 3968 |         // Check if there's a deleted record (cheaply), this should not happen because we mangle usernames in delete_user().
 | 
        
           |  |  | 3969 |         if ($DB->get_field('user', 'id', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id,  'deleted' => 1))) {
 | 
        
           |  |  | 3970 |             $failurereason = AUTH_LOGIN_NOUSER;
 | 
        
           |  |  | 3971 |   | 
        
           |  |  | 3972 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 3973 |             $event = \core\event\user_login_failed::create(array('other' => array(
 | 
        
           |  |  | 3974 |                 'username' => $username,
 | 
        
           |  |  | 3975 |                 'reason' => $failurereason
 | 
        
           |  |  | 3976 |             )));
 | 
        
           | 1 | efrain | 3977 |             $event->trigger();
 | 
        
           | 1326 | ariadna | 3978 |             error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Deleted Login:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 3979 |             return false;
 | 
        
           |  |  | 3980 |         }
 | 
        
           |  |  | 3981 |   | 
        
           |  |  | 3982 |         // User does not exist.
 | 
        
           |  |  | 3983 |         $auths = $authsenabled;
 | 
        
           |  |  | 3984 |         $user = new stdClass();
 | 
        
           |  |  | 3985 |         $user->id = 0;
 | 
        
           |  |  | 3986 |     }
 | 
        
           |  |  | 3987 |   | 
        
           |  |  | 3988 |     if ($ignorelockout) {
 | 
        
           |  |  | 3989 |         // Some other mechanism protects against brute force password guessing, for example login form might include reCAPTCHA
 | 
        
           |  |  | 3990 |         // or this function is called from a SSO script.
 | 
        
           |  |  | 3991 |     } else if ($user->id) {
 | 
        
           |  |  | 3992 |         // Verify login lockout after other ways that may prevent user login.
 | 
        
           |  |  | 3993 |         if (login_is_lockedout($user)) {
 | 
        
           |  |  | 3994 |             $failurereason = AUTH_LOGIN_LOCKOUT;
 | 
        
           |  |  | 3995 |   | 
        
           |  |  | 3996 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 3997 |             $event = \core\event\user_login_failed::create(array(
 | 
        
           |  |  | 3998 |                 'userid' => $user->id,
 | 
        
           |  |  | 3999 |                 'other' => array('username' => $username, 'reason' => $failurereason)
 | 
        
           |  |  | 4000 |             ));
 | 
        
           | 1 | efrain | 4001 |             $event->trigger();
 | 
        
           |  |  | 4002 |   | 
        
           | 1326 | ariadna | 4003 |             error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Login lockout:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 4004 |             $SESSION->loginerrormsg = get_string('accountlocked', 'admin');
 | 
        
           |  |  | 4005 |   | 
        
           |  |  | 4006 |             return false;
 | 
        
           |  |  | 4007 |         }
 | 
        
           |  |  | 4008 |     } else {
 | 
        
           |  |  | 4009 |         // We can not lockout non-existing accounts.
 | 
        
           |  |  | 4010 |     }
 | 
        
           |  |  | 4011 |   | 
        
           |  |  | 4012 |     foreach ($auths as $auth) {
 | 
        
           |  |  | 4013 |         $authplugin = get_auth_plugin($auth);
 | 
        
           |  |  | 4014 |   | 
        
           |  |  | 4015 |         // On auth fail fall through to the next plugin.
 | 
        
           |  |  | 4016 |         if (!$authplugin->user_login($username, $password)) {
 | 
        
           |  |  | 4017 |             continue;
 | 
        
           |  |  | 4018 |         }
 | 
        
           |  |  | 4019 |   | 
        
           |  |  | 4020 |         // Before performing login actions, check if user still passes password policy, if admin setting is enabled.
 | 
        
           |  |  | 4021 |         if (!empty($CFG->passwordpolicycheckonlogin)) {
 | 
        
           |  |  | 4022 |             $errmsg = '';
 | 
        
           |  |  | 4023 |             $passed = check_password_policy($password, $errmsg, $user);
 | 
        
           |  |  | 4024 |             if (!$passed) {
 | 
        
           |  |  | 4025 |                 // First trigger event for failure.
 | 
        
           |  |  | 4026 |                 $failedevent = \core\event\user_password_policy_failed::create_from_user($user);
 | 
        
           |  |  | 4027 |                 $failedevent->trigger();
 | 
        
           |  |  | 4028 |   | 
        
           |  |  | 4029 |                 // If able to change password, set flag and move on.
 | 
        
           |  |  | 4030 |                 if ($authplugin->can_change_password()) {
 | 
        
           |  |  | 4031 |                     // Check if we are on internal change password page, or service is external, don't show notification.
 | 
        
           |  |  | 4032 |                     $internalchangeurl = new moodle_url('/login/change_password.php');
 | 
        
           |  |  | 4033 |                     if (!($PAGE->has_set_url() && $internalchangeurl->compare($PAGE->url)) && $authplugin->is_internal()) {
 | 
        
           |  |  | 4034 |                         \core\notification::error(get_string('passwordpolicynomatch', '', $errmsg));
 | 
        
           |  |  | 4035 |                     }
 | 
        
           |  |  | 4036 |                     set_user_preference('auth_forcepasswordchange', 1, $user);
 | 
        
           |  |  | 4037 |                 } else if ($authplugin->can_reset_password()) {
 | 
        
           |  |  | 4038 |                     // Else force a reset if possible.
 | 
        
           |  |  | 4039 |                     \core\notification::error(get_string('forcepasswordresetnotice', '', $errmsg));
 | 
        
           |  |  | 4040 |                     redirect(new moodle_url('/login/forgot_password.php'));
 | 
        
           |  |  | 4041 |                 } else {
 | 
        
           |  |  | 4042 |                     $notifymsg = get_string('forcepasswordresetfailurenotice', '', $errmsg);
 | 
        
           |  |  | 4043 |                     // If support page is set, add link for help.
 | 
        
           |  |  | 4044 |                     if (!empty($CFG->supportpage)) {
 | 
        
           |  |  | 4045 |                         $link = \html_writer::link($CFG->supportpage, $CFG->supportpage);
 | 
        
           |  |  | 4046 |                         $link = \html_writer::tag('p', $link);
 | 
        
           |  |  | 4047 |                         $notifymsg .= $link;
 | 
        
           |  |  | 4048 |                     }
 | 
        
           |  |  | 4049 |   | 
        
           |  |  | 4050 |                     // If no change or reset is possible, add a notification for user.
 | 
        
           |  |  | 4051 |                     \core\notification::error($notifymsg);
 | 
        
           |  |  | 4052 |                 }
 | 
        
           |  |  | 4053 |             }
 | 
        
           |  |  | 4054 |         }
 | 
        
           |  |  | 4055 |   | 
        
           |  |  | 4056 |         // Successful authentication.
 | 
        
           |  |  | 4057 |         if ($user->id) {
 | 
        
           |  |  | 4058 |             // User already exists in database.
 | 
        
           |  |  | 4059 |             if (empty($user->auth)) {
 | 
        
           |  |  | 4060 |                 // For some reason auth isn't set yet.
 | 
        
           |  |  | 4061 |                 $DB->set_field('user', 'auth', $auth, array('id' => $user->id));
 | 
        
           |  |  | 4062 |                 $user->auth = $auth;
 | 
        
           |  |  | 4063 |             }
 | 
        
           |  |  | 4064 |   | 
        
           |  |  | 4065 |             // If the existing hash is using an out-of-date algorithm (or the legacy md5 algorithm), then we should update to
 | 
        
           |  |  | 4066 |             // the current hash algorithm while we have access to the user's password.
 | 
        
           |  |  | 4067 |             update_internal_user_password($user, $password);
 | 
        
           |  |  | 4068 |   | 
        
           |  |  | 4069 |             if ($authplugin->is_synchronised_with_external()) {
 | 
        
           |  |  | 4070 |                 // Update user record from external DB.
 | 
        
           |  |  | 4071 |                 $user = update_user_record_by_id($user->id);
 | 
        
           |  |  | 4072 |             }
 | 
        
           |  |  | 4073 |         } else {
 | 
        
           |  |  | 4074 |             // The user is authenticated but user creation may be disabled.
 | 
        
           |  |  | 4075 |             if (!empty($CFG->authpreventaccountcreation)) {
 | 
        
           |  |  | 4076 |                 $failurereason = AUTH_LOGIN_UNAUTHORISED;
 | 
        
           |  |  | 4077 |   | 
        
           |  |  | 4078 |                 // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 4079 |                 $event = \core\event\user_login_failed::create(array('other' => array(
 | 
        
           |  |  | 4080 |                     'username' => $username,
 | 
        
           |  |  | 4081 |                     'reason' => $failurereason
 | 
        
           |  |  | 4082 |                 )));
 | 
        
           | 1 | efrain | 4083 |                 $event->trigger();
 | 
        
           |  |  | 4084 |   | 
        
           | 1326 | ariadna | 4085 |                 error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Unknown user, can not create new accounts:  $username  " .
 | 
        
           |  |  | 4086 |                     $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 4087 |                 return false;
 | 
        
           |  |  | 4088 |             } else {
 | 
        
           |  |  | 4089 |                 $user = create_user_record($username, $password, $auth);
 | 
        
           |  |  | 4090 |             }
 | 
        
           |  |  | 4091 |         }
 | 
        
           |  |  | 4092 |   | 
        
           |  |  | 4093 |         $authplugin->sync_roles($user);
 | 
        
           |  |  | 4094 |   | 
        
           |  |  | 4095 |         foreach ($authsenabled as $hau) {
 | 
        
           |  |  | 4096 |             $hauth = get_auth_plugin($hau);
 | 
        
           |  |  | 4097 |             $hauth->user_authenticated_hook($user, $username, $password);
 | 
        
           |  |  | 4098 |         }
 | 
        
           |  |  | 4099 |   | 
        
           |  |  | 4100 |         if (empty($user->id)) {
 | 
        
           |  |  | 4101 |             $failurereason = AUTH_LOGIN_NOUSER;
 | 
        
           |  |  | 4102 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 4103 |             $event = \core\event\user_login_failed::create(array('other' => array(
 | 
        
           |  |  | 4104 |                 'username' => $username,
 | 
        
           |  |  | 4105 |                 'reason' => $failurereason
 | 
        
           |  |  | 4106 |             )));
 | 
        
           | 1 | efrain | 4107 |             $event->trigger();
 | 
        
           |  |  | 4108 |             return false;
 | 
        
           |  |  | 4109 |         }
 | 
        
           |  |  | 4110 |   | 
        
           |  |  | 4111 |         if (!empty($user->suspended)) {
 | 
        
           |  |  | 4112 |             // Just in case some auth plugin suspended account.
 | 
        
           |  |  | 4113 |             $failurereason = AUTH_LOGIN_SUSPENDED;
 | 
        
           |  |  | 4114 |             // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 4115 |             $event = \core\event\user_login_failed::create(array(
 | 
        
           |  |  | 4116 |                 'userid' => $user->id,
 | 
        
           |  |  | 4117 |                 'other' => array('username' => $username, 'reason' => $failurereason)
 | 
        
           |  |  | 4118 |             ));
 | 
        
           | 1 | efrain | 4119 |             $event->trigger();
 | 
        
           | 1326 | ariadna | 4120 |             error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Suspended Login:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 4121 |             return false;
 | 
        
           |  |  | 4122 |         }
 | 
        
           |  |  | 4123 |   | 
        
           |  |  | 4124 |         login_attempt_valid($user);
 | 
        
           |  |  | 4125 |         $failurereason = AUTH_LOGIN_OK;
 | 
        
           |  |  | 4126 |         return $user;
 | 
        
           |  |  | 4127 |     }
 | 
        
           |  |  | 4128 |   | 
        
           |  |  | 4129 |     // Failed if all the plugins have failed.
 | 
        
           |  |  | 4130 |     if (debugging('', DEBUG_ALL)) {
 | 
        
           | 1326 | ariadna | 4131 |         error_log('[client ' . getremoteaddr() . "]  $CFG->wwwroot  Failed Login:  $username  " . $_SERVER['HTTP_USER_AGENT']);
 | 
        
           | 1 | efrain | 4132 |     }
 | 
        
           |  |  | 4133 |   | 
        
           |  |  | 4134 |     if ($user->id) {
 | 
        
           |  |  | 4135 |         login_attempt_failed($user);
 | 
        
           |  |  | 4136 |         $failurereason = AUTH_LOGIN_FAILED;
 | 
        
           |  |  | 4137 |         // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 4138 |         $event = \core\event\user_login_failed::create(array(
 | 
        
           |  |  | 4139 |             'userid' => $user->id,
 | 
        
           |  |  | 4140 |             'other' => array('username' => $username, 'reason' => $failurereason)
 | 
        
           |  |  | 4141 |         ));
 | 
        
           | 1 | efrain | 4142 |         $event->trigger();
 | 
        
           |  |  | 4143 |     } else {
 | 
        
           |  |  | 4144 |         $failurereason = AUTH_LOGIN_NOUSER;
 | 
        
           |  |  | 4145 |         // Trigger login failed event.
 | 
        
           | 1326 | ariadna | 4146 |         $event = \core\event\user_login_failed::create(array('other' => array(
 | 
        
           |  |  | 4147 |             'username' => $username,
 | 
        
           |  |  | 4148 |             'reason' => $failurereason
 | 
        
           |  |  | 4149 |         )));
 | 
        
           | 1 | efrain | 4150 |         $event->trigger();
 | 
        
           |  |  | 4151 |     }
 | 
        
           |  |  | 4152 |   | 
        
           |  |  | 4153 |     return false;
 | 
        
           |  |  | 4154 | }
 | 
        
           |  |  | 4155 |   | 
        
           |  |  | 4156 | /**
 | 
        
           |  |  | 4157 |  * Call to complete the user login process after authenticate_user_login()
 | 
        
           |  |  | 4158 |  * has succeeded. It will setup the $USER variable and other required bits
 | 
        
           |  |  | 4159 |  * and pieces.
 | 
        
           |  |  | 4160 |  *
 | 
        
           |  |  | 4161 |  * NOTE:
 | 
        
           |  |  | 4162 |  * - It will NOT log anything -- up to the caller to decide what to log.
 | 
        
           |  |  | 4163 |  * - this function does not set any cookies any more!
 | 
        
           |  |  | 4164 |  *
 | 
        
           |  |  | 4165 |  * @param stdClass $user
 | 
        
           |  |  | 4166 |  * @param array $extrauserinfo
 | 
        
           |  |  | 4167 |  * @return stdClass A {@link $USER} object - BC only, do not use
 | 
        
           |  |  | 4168 |  */
 | 
        
           | 1326 | ariadna | 4169 | function complete_user_login($user, array $extrauserinfo = [])
 | 
        
           |  |  | 4170 | {
 | 
        
           | 1 | efrain | 4171 |     global $CFG, $DB, $USER, $SESSION;
 | 
        
           |  |  | 4172 |   | 
        
           |  |  | 4173 |     \core\session\manager::login_user($user);
 | 
        
           |  |  | 4174 |   | 
        
           |  |  | 4175 |     // Reload preferences from DB.
 | 
        
           |  |  | 4176 |     unset($USER->preference);
 | 
        
           |  |  | 4177 |     check_user_preferences_loaded($USER);
 | 
        
           |  |  | 4178 |   | 
        
           |  |  | 4179 |     // Update login times.
 | 
        
           |  |  | 4180 |     update_user_login_times();
 | 
        
           |  |  | 4181 |   | 
        
           |  |  | 4182 |     // Extra session prefs init.
 | 
        
           |  |  | 4183 |     set_login_session_preferences();
 | 
        
           |  |  | 4184 |   | 
        
           |  |  | 4185 |     // Trigger login event.
 | 
        
           |  |  | 4186 |     $event = \core\event\user_loggedin::create(
 | 
        
           |  |  | 4187 |         array(
 | 
        
           |  |  | 4188 |             'userid' => $USER->id,
 | 
        
           |  |  | 4189 |             'objectid' => $USER->id,
 | 
        
           |  |  | 4190 |             'other' => [
 | 
        
           |  |  | 4191 |                 'username' => $USER->username,
 | 
        
           |  |  | 4192 |                 'extrauserinfo' => $extrauserinfo
 | 
        
           |  |  | 4193 |             ]
 | 
        
           |  |  | 4194 |         )
 | 
        
           |  |  | 4195 |     );
 | 
        
           |  |  | 4196 |     $event->trigger();
 | 
        
           |  |  | 4197 |   | 
        
           |  |  | 4198 |     // Allow plugins to callback as soon possible after user has completed login.
 | 
        
           |  |  | 4199 |     di::get(\core\hook\manager::class)->dispatch(new \core_user\hook\after_login_completed());
 | 
        
           |  |  | 4200 |   | 
        
           |  |  | 4201 |     // Check if the user is using a new browser or session (a new MoodleSession cookie is set in that case).
 | 
        
           |  |  | 4202 |     // If the user is accessing from the same IP, ignore everything (most of the time will be a new session in the same browser).
 | 
        
           |  |  | 4203 |     // Skip Web Service requests, CLI scripts, AJAX scripts, and request from the mobile app itself.
 | 
        
           |  |  | 4204 |     $loginip = getremoteaddr();
 | 
        
           |  |  | 4205 |     $isnewip = isset($SESSION->userpreviousip) && $SESSION->userpreviousip != $loginip;
 | 
        
           |  |  | 4206 |     $isvalidenv = (!WS_SERVER && !CLI_SCRIPT && !NO_MOODLE_COOKIES) || PHPUNIT_TEST;
 | 
        
           |  |  | 4207 |   | 
        
           |  |  | 4208 |     if (!empty($SESSION->isnewsessioncookie) && $isnewip && $isvalidenv && !\core_useragent::is_moodle_app()) {
 | 
        
           |  |  | 4209 |   | 
        
           |  |  | 4210 |         $logintime = time();
 | 
        
           |  |  | 4211 |         $ismoodleapp = false;
 | 
        
           |  |  | 4212 |         $useragent = \core_useragent::get_user_agent_string();
 | 
        
           |  |  | 4213 |   | 
        
           |  |  | 4214 |         $sitepreferences = get_message_output_default_preferences();
 | 
        
           |  |  | 4215 |         // Check if new login notification is disabled at system level.
 | 
        
           |  |  | 4216 |         $newlogindisabled = $sitepreferences->moodle_newlogin_disable ?? 0;
 | 
        
           |  |  | 4217 |         // Check if message providers (web, email, mobile) are enabled at system level.
 | 
        
           |  |  | 4218 |         $msgproviderenabled = isset($sitepreferences->message_provider_moodle_newlogin_enabled);
 | 
        
           |  |  | 4219 |         // Get message providers enabled for a user.
 | 
        
           |  |  | 4220 |         $userpreferences = get_user_preferences('message_provider_moodle_newlogin_enabled');
 | 
        
           |  |  | 4221 |         // Check if notification processor plugins (web, email, mobile) are enabled at system level.
 | 
        
           |  |  | 4222 |         $msgprocessorsready = !empty(get_message_processors(true));
 | 
        
           |  |  | 4223 |         // If new login notification is enabled at system level then go for other conditions check.
 | 
        
           |  |  | 4224 |         $newloginenabled = $newlogindisabled ? 0 : ($userpreferences != 'none' && $msgproviderenabled);
 | 
        
           |  |  | 4225 |   | 
        
           |  |  | 4226 |         if ($newloginenabled && $msgprocessorsready) {
 | 
        
           |  |  | 4227 |             // Schedule adhoc task to send a login notification to the user.
 | 
        
           |  |  | 4228 |             $task = new \core\task\send_login_notifications();
 | 
        
           |  |  | 4229 |             $task->set_userid($USER->id);
 | 
        
           |  |  | 4230 |             $task->set_custom_data(compact('ismoodleapp', 'useragent', 'loginip', 'logintime'));
 | 
        
           |  |  | 4231 |             $task->set_component('core');
 | 
        
           |  |  | 4232 |             \core\task\manager::queue_adhoc_task($task);
 | 
        
           |  |  | 4233 |         }
 | 
        
           |  |  | 4234 |     }
 | 
        
           |  |  | 4235 |   | 
        
           |  |  | 4236 |     // Queue migrating the messaging data, if we need to.
 | 
        
           |  |  | 4237 |     if (!get_user_preferences('core_message_migrate_data', false, $USER->id)) {
 | 
        
           |  |  | 4238 |         // Check if there are any legacy messages to migrate.
 | 
        
           |  |  | 4239 |         if (\core_message\helper::legacy_messages_exist($USER->id)) {
 | 
        
           |  |  | 4240 |             \core_message\task\migrate_message_data::queue_task($USER->id);
 | 
        
           |  |  | 4241 |         } else {
 | 
        
           |  |  | 4242 |             set_user_preference('core_message_migrate_data', true, $USER->id);
 | 
        
           |  |  | 4243 |         }
 | 
        
           |  |  | 4244 |     }
 | 
        
           |  |  | 4245 |   | 
        
           |  |  | 4246 |     if (isguestuser()) {
 | 
        
           |  |  | 4247 |         // No need to continue when user is THE guest.
 | 
        
           |  |  | 4248 |         return $USER;
 | 
        
           |  |  | 4249 |     }
 | 
        
           |  |  | 4250 |   | 
        
           |  |  | 4251 |     if (CLI_SCRIPT) {
 | 
        
           |  |  | 4252 |         // We can redirect to password change URL only in browser.
 | 
        
           |  |  | 4253 |         return $USER;
 | 
        
           |  |  | 4254 |     }
 | 
        
           |  |  | 4255 |   | 
        
           |  |  | 4256 |     // Select password change url.
 | 
        
           |  |  | 4257 |     $userauth = get_auth_plugin($USER->auth);
 | 
        
           |  |  | 4258 |   | 
        
           |  |  | 4259 |     // Check whether the user should be changing password.
 | 
        
           |  |  | 4260 |     if (get_user_preferences('auth_forcepasswordchange', false)) {
 | 
        
           |  |  | 4261 |         if ($userauth->can_change_password()) {
 | 
        
           |  |  | 4262 |             if ($changeurl = $userauth->change_password_url()) {
 | 
        
           |  |  | 4263 |                 redirect($changeurl);
 | 
        
           |  |  | 4264 |             } else {
 | 
        
           |  |  | 4265 |                 require_once($CFG->dirroot . '/login/lib.php');
 | 
        
           |  |  | 4266 |                 $SESSION->wantsurl = core_login_get_return_url();
 | 
        
           | 1326 | ariadna | 4267 |                 redirect($CFG->wwwroot . '/login/change_password.php');
 | 
        
           | 1 | efrain | 4268 |             }
 | 
        
           |  |  | 4269 |         } else {
 | 
        
           |  |  | 4270 |             throw new \moodle_exception('nopasswordchangeforced', 'auth');
 | 
        
           |  |  | 4271 |         }
 | 
        
           |  |  | 4272 |     }
 | 
        
           |  |  | 4273 |     return $USER;
 | 
        
           |  |  | 4274 | }
 | 
        
           |  |  | 4275 |   | 
        
           |  |  | 4276 | /**
 | 
        
           |  |  | 4277 |  * Check a password hash to see if it was hashed using the legacy hash algorithm (bcrypt).
 | 
        
           |  |  | 4278 |  *
 | 
        
           |  |  | 4279 |  * @param string $password String to check.
 | 
        
           |  |  | 4280 |  * @return bool True if the $password matches the format of a bcrypt hash.
 | 
        
           |  |  | 4281 |  */
 | 
        
           | 1326 | ariadna | 4282 | function password_is_legacy_hash(#[\SensitiveParameter] string $password): bool
 | 
        
           |  |  | 4283 | {
 | 
        
           | 1 | efrain | 4284 |     return (bool) preg_match('/^\$2y\$[\d]{2}\$[A-Za-z0-9\.\/]{53}$/', $password);
 | 
        
           |  |  | 4285 | }
 | 
        
           |  |  | 4286 |   | 
        
           |  |  | 4287 | /**
 | 
        
           |  |  | 4288 |  * Calculate the Shannon entropy of a string.
 | 
        
           |  |  | 4289 |  *
 | 
        
           |  |  | 4290 |  * @param string $pepper The pepper to calculate the entropy of.
 | 
        
           |  |  | 4291 |  * @return float The Shannon entropy of the string.
 | 
        
           |  |  | 4292 |  */
 | 
        
           | 1326 | ariadna | 4293 | function calculate_entropy(#[\SensitiveParameter] string $pepper): float
 | 
        
           |  |  | 4294 | {
 | 
        
           | 1 | efrain | 4295 |     // Initialize entropy.
 | 
        
           |  |  | 4296 |     $h = 0;
 | 
        
           |  |  | 4297 |   | 
        
           |  |  | 4298 |     // Calculate the length of the string.
 | 
        
           |  |  | 4299 |     $size = strlen($pepper);
 | 
        
           |  |  | 4300 |   | 
        
           |  |  | 4301 |     // For each unique character in the string.
 | 
        
           |  |  | 4302 |     foreach (count_chars($pepper, 1) as $v) {
 | 
        
           |  |  | 4303 |         // Calculate the probability of the character.
 | 
        
           |  |  | 4304 |         $p = $v / $size;
 | 
        
           |  |  | 4305 |   | 
        
           |  |  | 4306 |         // Add the character's contribution to the total entropy.
 | 
        
           |  |  | 4307 |         // This uses the formula for the entropy of a discrete random variable.
 | 
        
           |  |  | 4308 |         $h -= $p * log($p) / log(2);
 | 
        
           |  |  | 4309 |     }
 | 
        
           |  |  | 4310 |   | 
        
           |  |  | 4311 |     // Instead of returning the average entropy per symbol (Shannon entropy),
 | 
        
           |  |  | 4312 |     // we multiply by the length of the string to get total entropy.
 | 
        
           |  |  | 4313 |     return $h * $size;
 | 
        
           |  |  | 4314 | }
 | 
        
           |  |  | 4315 |   | 
        
           |  |  | 4316 | /**
 | 
        
           |  |  | 4317 |  * Get the available password peppers.
 | 
        
           |  |  | 4318 |  * The latest pepper is checked for minimum entropy as part of this function.
 | 
        
           |  |  | 4319 |  * We only calculate the entropy of the most recent pepper,
 | 
        
           |  |  | 4320 |  * because passwords are always updated to the latest pepper,
 | 
        
           |  |  | 4321 |  * and in the past we may have enforced a lower minimum entropy.
 | 
        
           |  |  | 4322 |  * Also, we allow the latest pepper to be empty, to allow admins to migrate off peppers.
 | 
        
           |  |  | 4323 |  *
 | 
        
           |  |  | 4324 |  * @return array The password peppers.
 | 
        
           |  |  | 4325 |  * @throws coding_exception If the entropy of the password pepper is less than the recommended minimum.
 | 
        
           |  |  | 4326 |  */
 | 
        
           | 1326 | ariadna | 4327 | function get_password_peppers(): array
 | 
        
           |  |  | 4328 | {
 | 
        
           | 1 | efrain | 4329 |     global $CFG;
 | 
        
           |  |  | 4330 |   | 
        
           |  |  | 4331 |     // Get all available peppers.
 | 
        
           |  |  | 4332 |     if (isset($CFG->passwordpeppers) && is_array($CFG->passwordpeppers)) {
 | 
        
           |  |  | 4333 |         // Sort the array in descending order of keys (numerical).
 | 
        
           |  |  | 4334 |         $peppers = $CFG->passwordpeppers;
 | 
        
           |  |  | 4335 |         krsort($peppers, SORT_NUMERIC);
 | 
        
           |  |  | 4336 |     } else {
 | 
        
           |  |  | 4337 |         $peppers = [];  // Set an empty array if no peppers are found.
 | 
        
           |  |  | 4338 |     }
 | 
        
           |  |  | 4339 |   | 
        
           |  |  | 4340 |     // Check if the entropy of the most recent pepper is less than the minimum.
 | 
        
           |  |  | 4341 |     // Also, we allow the most recent pepper to be empty, to allow admins to migrate off peppers.
 | 
        
           |  |  | 4342 |     $lastpepper = reset($peppers);
 | 
        
           |  |  | 4343 |     if (!empty($peppers) && $lastpepper !== '' && calculate_entropy($lastpepper) < PEPPER_ENTROPY) {
 | 
        
           |  |  | 4344 |         throw new coding_exception(
 | 
        
           | 1326 | ariadna | 4345 |             'password pepper below minimum',
 | 
        
           |  |  | 4346 |             'The entropy of the password pepper is less than the recommended minimum.'
 | 
        
           |  |  | 4347 |         );
 | 
        
           | 1 | efrain | 4348 |     }
 | 
        
           |  |  | 4349 |     return $peppers;
 | 
        
           |  |  | 4350 | }
 | 
        
           |  |  | 4351 |   | 
        
           |  |  | 4352 | /**
 | 
        
           |  |  | 4353 |  * Compare password against hash stored in user object to determine if it is valid.
 | 
        
           |  |  | 4354 |  *
 | 
        
           |  |  | 4355 |  * If necessary it also updates the stored hash to the current format.
 | 
        
           |  |  | 4356 |  *
 | 
        
           |  |  | 4357 |  * @param stdClass $user (Password property may be updated).
 | 
        
           |  |  | 4358 |  * @param string $password Plain text password.
 | 
        
           |  |  | 4359 |  * @return bool True if password is valid.
 | 
        
           |  |  | 4360 |  */
 | 
        
           | 1326 | ariadna | 4361 | function validate_internal_user_password(stdClass $user, #[\SensitiveParameter] string $password): bool
 | 
        
           |  |  | 4362 | {
 | 
        
           | 1 | efrain | 4363 |   | 
        
           |  |  | 4364 |     if (exceeds_password_length($password)) {
 | 
        
           |  |  | 4365 |         // Password cannot be more than MAX_PASSWORD_CHARACTERS characters.
 | 
        
           |  |  | 4366 |         return false;
 | 
        
           |  |  | 4367 |     }
 | 
        
           |  |  | 4368 |   | 
        
           |  |  | 4369 |     if ($user->password === AUTH_PASSWORD_NOT_CACHED) {
 | 
        
           |  |  | 4370 |         // Internal password is not used at all, it can not validate.
 | 
        
           |  |  | 4371 |         return false;
 | 
        
           |  |  | 4372 |     }
 | 
        
           |  |  | 4373 |   | 
        
           |  |  | 4374 |     $peppers = get_password_peppers(); // Get the array of available peppers.
 | 
        
           |  |  | 4375 |     $islegacy = password_is_legacy_hash($user->password); // Check if the password is a legacy bcrypt hash.
 | 
        
           |  |  | 4376 |   | 
        
           |  |  | 4377 |     // If the password is a legacy hash, no peppers were used, so verify and update directly.
 | 
        
           |  |  | 4378 |     if ($islegacy && password_verify($password, $user->password)) {
 | 
        
           |  |  | 4379 |         update_internal_user_password($user, $password);
 | 
        
           |  |  | 4380 |         return true;
 | 
        
           |  |  | 4381 |     }
 | 
        
           |  |  | 4382 |   | 
        
           |  |  | 4383 |     // If the password is not a legacy hash, iterate through the peppers.
 | 
        
           |  |  | 4384 |     $latestpepper = reset($peppers);
 | 
        
           |  |  | 4385 |     // Add an empty pepper to the beginning of the array. To make it easier to check if the password matches without any pepper.
 | 
        
           |  |  | 4386 |     $peppers = [-1 => ''] + $peppers;
 | 
        
           |  |  | 4387 |     foreach ($peppers as $pepper) {
 | 
        
           |  |  | 4388 |         $pepperedpassword = $password . $pepper;
 | 
        
           |  |  | 4389 |   | 
        
           |  |  | 4390 |         // If the peppered password is correct, update (if necessary) and return true.
 | 
        
           |  |  | 4391 |         if (password_verify($pepperedpassword, $user->password)) {
 | 
        
           |  |  | 4392 |             // If the pepper used is not the latest one, update the password.
 | 
        
           |  |  | 4393 |             if ($pepper !== $latestpepper) {
 | 
        
           |  |  | 4394 |                 update_internal_user_password($user, $password);
 | 
        
           |  |  | 4395 |             }
 | 
        
           |  |  | 4396 |             return true;
 | 
        
           |  |  | 4397 |         }
 | 
        
           |  |  | 4398 |     }
 | 
        
           |  |  | 4399 |   | 
        
           |  |  | 4400 |     // If no peppered password was correct, the password is wrong.
 | 
        
           |  |  | 4401 |     return false;
 | 
        
           |  |  | 4402 | }
 | 
        
           |  |  | 4403 |   | 
        
           |  |  | 4404 | /**
 | 
        
           |  |  | 4405 |  * Calculate hash for a plain text password.
 | 
        
           |  |  | 4406 |  *
 | 
        
           |  |  | 4407 |  * @param string $password Plain text password to be hashed.
 | 
        
           |  |  | 4408 |  * @param bool $fasthash If true, use a low number of rounds when generating the hash
 | 
        
           |  |  | 4409 |  *                       This is faster to generate but makes the hash less secure.
 | 
        
           |  |  | 4410 |  *                       It is used when lots of hashes need to be generated quickly.
 | 
        
           |  |  | 4411 |  * @param int $pepperlength Lenght of the peppers
 | 
        
           |  |  | 4412 |  * @return string The hashed password.
 | 
        
           |  |  | 4413 |  *
 | 
        
           |  |  | 4414 |  * @throws moodle_exception If a problem occurs while generating the hash.
 | 
        
           |  |  | 4415 |  */
 | 
        
           | 1326 | ariadna | 4416 | function hash_internal_user_password(#[\SensitiveParameter] string $password, $fasthash = false, $pepperlength = 0): string
 | 
        
           |  |  | 4417 | {
 | 
        
           | 1 | efrain | 4418 |     if (exceeds_password_length($password, $pepperlength)) {
 | 
        
           |  |  | 4419 |         // Password cannot be more than MAX_PASSWORD_CHARACTERS.
 | 
        
           |  |  | 4420 |         throw new \moodle_exception(get_string("passwordexceeded", 'error', MAX_PASSWORD_CHARACTERS));
 | 
        
           |  |  | 4421 |     }
 | 
        
           |  |  | 4422 |   | 
        
           |  |  | 4423 |     // Set the cost factor to 5000 for fast hashing, otherwise use default cost.
 | 
        
           |  |  | 4424 |     $rounds = $fasthash ? 5000 : 10000;
 | 
        
           |  |  | 4425 |   | 
        
           |  |  | 4426 |     // First generate a cryptographically suitable salt.
 | 
        
           |  |  | 4427 |     $randombytes = random_bytes(16);
 | 
        
           |  |  | 4428 |     $salt = substr(strtr(base64_encode($randombytes), '+', '.'), 0, 16);
 | 
        
           |  |  | 4429 |   | 
        
           |  |  | 4430 |     // Now construct the password string with the salt and number of rounds.
 | 
        
           |  |  | 4431 |     // The password string is in the format $algorithm$rounds$salt$hash. ($6 is the SHA512 algorithm).
 | 
        
           |  |  | 4432 |     $generatedhash = crypt($password, implode('$', [
 | 
        
           |  |  | 4433 |         '',
 | 
        
           |  |  | 4434 |         // The SHA512 Algorithm
 | 
        
           |  |  | 4435 |         '6',
 | 
        
           |  |  | 4436 |         "rounds={$rounds}",
 | 
        
           |  |  | 4437 |         $salt,
 | 
        
           |  |  | 4438 |         '',
 | 
        
           |  |  | 4439 |     ]));
 | 
        
           |  |  | 4440 |   | 
        
           |  |  | 4441 |     if ($generatedhash === false || $generatedhash === null) {
 | 
        
           |  |  | 4442 |         throw new moodle_exception('Failed to generate password hash.');
 | 
        
           |  |  | 4443 |     }
 | 
        
           |  |  | 4444 |   | 
        
           |  |  | 4445 |     return $generatedhash;
 | 
        
           |  |  | 4446 | }
 | 
        
           |  |  | 4447 |   | 
        
           |  |  | 4448 | /**
 | 
        
           |  |  | 4449 |  * Update password hash in user object (if necessary).
 | 
        
           |  |  | 4450 |  *
 | 
        
           |  |  | 4451 |  * The password is updated if:
 | 
        
           |  |  | 4452 |  * 1. The password has changed (the hash of $user->password is different
 | 
        
           |  |  | 4453 |  *    to the hash of $password).
 | 
        
           |  |  | 4454 |  * 2. The existing hash is using an out-of-date algorithm (or the legacy
 | 
        
           |  |  | 4455 |  *    md5 algorithm).
 | 
        
           |  |  | 4456 |  *
 | 
        
           |  |  | 4457 |  * The password is peppered with the latest pepper before hashing,
 | 
        
           |  |  | 4458 |  * if peppers are available.
 | 
        
           |  |  | 4459 |  * Updating the password will modify the $user object and the database
 | 
        
           |  |  | 4460 |  * record to use the current hashing algorithm.
 | 
        
           |  |  | 4461 |  * It will remove Web Services user tokens too.
 | 
        
           |  |  | 4462 |  *
 | 
        
           |  |  | 4463 |  * @param stdClass $user User object (password property may be updated).
 | 
        
           | 11 | efrain | 4464 |  * @param string|null $password Plain text password.
 | 
        
           | 1 | efrain | 4465 |  * @param bool $fasthash If true, use a low cost factor when generating the hash
 | 
        
           |  |  | 4466 |  *                       This is much faster to generate but makes the hash
 | 
        
           |  |  | 4467 |  *                       less secure. It is used when lots of hashes need to
 | 
        
           |  |  | 4468 |  *                       be generated quickly.
 | 
        
           |  |  | 4469 |  * @return bool Always returns true.
 | 
        
           |  |  | 4470 |  */
 | 
        
           |  |  | 4471 | function update_internal_user_password(
 | 
        
           | 1326 | ariadna | 4472 |     stdClass $user,
 | 
        
           |  |  | 4473 |     #[\SensitiveParameter] ?string $password,
 | 
        
           |  |  | 4474 |     bool $fasthash = false
 | 
        
           | 1 | efrain | 4475 | ): bool {
 | 
        
           |  |  | 4476 |     global $CFG, $DB;
 | 
        
           |  |  | 4477 |   | 
        
           |  |  | 4478 |     // Add the latest password pepper to the password before further processing.
 | 
        
           |  |  | 4479 |     $peppers = get_password_peppers();
 | 
        
           |  |  | 4480 |     if (!empty($peppers)) {
 | 
        
           |  |  | 4481 |         $password = $password . reset($peppers);
 | 
        
           |  |  | 4482 |     }
 | 
        
           |  |  | 4483 |   | 
        
           |  |  | 4484 |     // Figure out what the hashed password should be.
 | 
        
           |  |  | 4485 |     if (!isset($user->auth)) {
 | 
        
           | 1326 | ariadna | 4486 |         debugging(
 | 
        
           |  |  | 4487 |             'User record in update_internal_user_password() must include field auth',
 | 
        
           |  |  | 4488 |             DEBUG_DEVELOPER
 | 
        
           |  |  | 4489 |         );
 | 
        
           | 1 | efrain | 4490 |         $user->auth = $DB->get_field('user', 'auth', array('id' => $user->id));
 | 
        
           |  |  | 4491 |     }
 | 
        
           |  |  | 4492 |     $authplugin = get_auth_plugin($user->auth);
 | 
        
           |  |  | 4493 |     if ($authplugin->prevent_local_passwords()) {
 | 
        
           |  |  | 4494 |         $hashedpassword = AUTH_PASSWORD_NOT_CACHED;
 | 
        
           |  |  | 4495 |     } else {
 | 
        
           |  |  | 4496 |         $hashedpassword = hash_internal_user_password($password, $fasthash);
 | 
        
           |  |  | 4497 |     }
 | 
        
           |  |  | 4498 |   | 
        
           |  |  | 4499 |     $algorithmchanged = false;
 | 
        
           |  |  | 4500 |   | 
        
           |  |  | 4501 |     if ($hashedpassword === AUTH_PASSWORD_NOT_CACHED) {
 | 
        
           |  |  | 4502 |         // Password is not cached, update it if not set to AUTH_PASSWORD_NOT_CACHED.
 | 
        
           |  |  | 4503 |         $passwordchanged = ($user->password !== $hashedpassword);
 | 
        
           |  |  | 4504 |     } else if (isset($user->password)) {
 | 
        
           |  |  | 4505 |         // If verification fails then it means the password has changed.
 | 
        
           |  |  | 4506 |         $passwordchanged = !password_verify($password, $user->password);
 | 
        
           |  |  | 4507 |         $algorithmchanged = password_is_legacy_hash($user->password);
 | 
        
           |  |  | 4508 |     } else {
 | 
        
           |  |  | 4509 |         // While creating new user, password in unset in $user object, to avoid
 | 
        
           |  |  | 4510 |         // saving it with user_create()
 | 
        
           |  |  | 4511 |         $passwordchanged = true;
 | 
        
           |  |  | 4512 |     }
 | 
        
           |  |  | 4513 |   | 
        
           |  |  | 4514 |     if ($passwordchanged || $algorithmchanged) {
 | 
        
           |  |  | 4515 |         $DB->set_field('user', 'password',  $hashedpassword, array('id' => $user->id));
 | 
        
           |  |  | 4516 |         $user->password = $hashedpassword;
 | 
        
           |  |  | 4517 |   | 
        
           |  |  | 4518 |         // Trigger event.
 | 
        
           |  |  | 4519 |         $user = $DB->get_record('user', array('id' => $user->id));
 | 
        
           |  |  | 4520 |         \core\event\user_password_updated::create_from_user($user)->trigger();
 | 
        
           |  |  | 4521 |   | 
        
           |  |  | 4522 |         // Remove WS user tokens.
 | 
        
           |  |  | 4523 |         if (!empty($CFG->passwordchangetokendeletion)) {
 | 
        
           | 1326 | ariadna | 4524 |             require_once($CFG->dirroot . '/webservice/lib.php');
 | 
        
           | 1 | efrain | 4525 |             webservice::delete_user_ws_tokens($user->id);
 | 
        
           |  |  | 4526 |         }
 | 
        
           |  |  | 4527 |     }
 | 
        
           |  |  | 4528 |   | 
        
           |  |  | 4529 |     return true;
 | 
        
           |  |  | 4530 | }
 | 
        
           |  |  | 4531 |   | 
        
           |  |  | 4532 | /**
 | 
        
           |  |  | 4533 |  * Get a complete user record, which includes all the info in the user record.
 | 
        
           |  |  | 4534 |  *
 | 
        
           |  |  | 4535 |  * Intended for setting as $USER session variable
 | 
        
           |  |  | 4536 |  *
 | 
        
           |  |  | 4537 |  * @param string $field The user field to be checked for a given value.
 | 
        
           |  |  | 4538 |  * @param string $value The value to match for $field.
 | 
        
           |  |  | 4539 |  * @param int $mnethostid
 | 
        
           |  |  | 4540 |  * @param bool $throwexception If true, it will throw an exception when there's no record found or when there are multiple records
 | 
        
           |  |  | 4541 |  *                              found. Otherwise, it will just return false.
 | 
        
           |  |  | 4542 |  * @return mixed False, or A {@link $USER} object.
 | 
        
           |  |  | 4543 |  */
 | 
        
           | 1326 | ariadna | 4544 | function get_complete_user_data($field, $value, $mnethostid = null, $throwexception = false)
 | 
        
           |  |  | 4545 | {
 | 
        
           | 1 | efrain | 4546 |     global $CFG, $DB;
 | 
        
           |  |  | 4547 |   | 
        
           |  |  | 4548 |     if (!$field || !$value) {
 | 
        
           |  |  | 4549 |         return false;
 | 
        
           |  |  | 4550 |     }
 | 
        
           |  |  | 4551 |   | 
        
           |  |  | 4552 |     // Change the field to lowercase.
 | 
        
           |  |  | 4553 |     $field = core_text::strtolower($field);
 | 
        
           |  |  | 4554 |   | 
        
           |  |  | 4555 |     // List of case insensitive fields.
 | 
        
           |  |  | 4556 |     $caseinsensitivefields = ['email'];
 | 
        
           |  |  | 4557 |   | 
        
           |  |  | 4558 |     // Username input is forced to lowercase and should be case sensitive.
 | 
        
           |  |  | 4559 |     if ($field == 'username') {
 | 
        
           |  |  | 4560 |         $value = core_text::strtolower($value);
 | 
        
           |  |  | 4561 |     }
 | 
        
           |  |  | 4562 |   | 
        
           |  |  | 4563 |     // Build the WHERE clause for an SQL query.
 | 
        
           |  |  | 4564 |     $params = array('fieldval' => $value);
 | 
        
           |  |  | 4565 |   | 
        
           |  |  | 4566 |     // Do a case-insensitive query, if necessary. These are generally very expensive. The performance can be improved on some DBs
 | 
        
           |  |  | 4567 |     // such as MySQL by pre-filtering users with accent-insensitive subselect.
 | 
        
           |  |  | 4568 |     if (in_array($field, $caseinsensitivefields)) {
 | 
        
           |  |  | 4569 |         $fieldselect = $DB->sql_equal($field, ':fieldval', false);
 | 
        
           |  |  | 4570 |         $idsubselect = $DB->sql_equal($field, ':fieldval2', false, false);
 | 
        
           |  |  | 4571 |         $params['fieldval2'] = $value;
 | 
        
           |  |  | 4572 |     } else {
 | 
        
           |  |  | 4573 |         $fieldselect = "$field = :fieldval";
 | 
        
           |  |  | 4574 |         $idsubselect = '';
 | 
        
           |  |  | 4575 |     }
 | 
        
           |  |  | 4576 |     $constraints = "$fieldselect AND deleted <> 1";
 | 
        
           |  |  | 4577 |   | 
        
           |  |  | 4578 |     // If we are loading user data based on anything other than id,
 | 
        
           |  |  | 4579 |     // we must also restrict our search based on mnet host.
 | 
        
           |  |  | 4580 |     if ($field != 'id') {
 | 
        
           |  |  | 4581 |         if (empty($mnethostid)) {
 | 
        
           |  |  | 4582 |             // If empty, we restrict to local users.
 | 
        
           |  |  | 4583 |             $mnethostid = $CFG->mnet_localhost_id;
 | 
        
           |  |  | 4584 |         }
 | 
        
           |  |  | 4585 |     }
 | 
        
           |  |  | 4586 |     if (!empty($mnethostid)) {
 | 
        
           |  |  | 4587 |         $params['mnethostid'] = $mnethostid;
 | 
        
           |  |  | 4588 |         $constraints .= " AND mnethostid = :mnethostid";
 | 
        
           |  |  | 4589 |     }
 | 
        
           |  |  | 4590 |   | 
        
           |  |  | 4591 |     if ($idsubselect) {
 | 
        
           |  |  | 4592 |         $constraints .= " AND id IN (SELECT id FROM {user} WHERE {$idsubselect})";
 | 
        
           |  |  | 4593 |     }
 | 
        
           |  |  | 4594 |   | 
        
           |  |  | 4595 |     // Get all the basic user data.
 | 
        
           |  |  | 4596 |     try {
 | 
        
           |  |  | 4597 |         // Make sure that there's only a single record that matches our query.
 | 
        
           |  |  | 4598 |         // For example, when fetching by email, multiple records might match the query as there's no guarantee that email addresses
 | 
        
           |  |  | 4599 |         // are unique. Therefore we can't reliably tell whether the user profile data that we're fetching is the correct one.
 | 
        
           |  |  | 4600 |         $user = $DB->get_record_select('user', $constraints, $params, '*', MUST_EXIST);
 | 
        
           |  |  | 4601 |     } catch (dml_exception $exception) {
 | 
        
           |  |  | 4602 |         if ($throwexception) {
 | 
        
           |  |  | 4603 |             throw $exception;
 | 
        
           |  |  | 4604 |         } else {
 | 
        
           |  |  | 4605 |             // Return false when no records or multiple records were found.
 | 
        
           |  |  | 4606 |             return false;
 | 
        
           |  |  | 4607 |         }
 | 
        
           |  |  | 4608 |     }
 | 
        
           |  |  | 4609 |   | 
        
           |  |  | 4610 |     // Get various settings and preferences.
 | 
        
           |  |  | 4611 |   | 
        
           |  |  | 4612 |     // Preload preference cache.
 | 
        
           |  |  | 4613 |     check_user_preferences_loaded($user);
 | 
        
           |  |  | 4614 |   | 
        
           |  |  | 4615 |     // Load course enrolment related stuff.
 | 
        
           |  |  | 4616 |     $user->lastcourseaccess    = array(); // During last session.
 | 
        
           |  |  | 4617 |     $user->currentcourseaccess = array(); // During current session.
 | 
        
           |  |  | 4618 |     if ($lastaccesses = $DB->get_records('user_lastaccess', array('userid' => $user->id))) {
 | 
        
           |  |  | 4619 |         foreach ($lastaccesses as $lastaccess) {
 | 
        
           |  |  | 4620 |             $user->lastcourseaccess[$lastaccess->courseid] = $lastaccess->timeaccess;
 | 
        
           |  |  | 4621 |         }
 | 
        
           |  |  | 4622 |     }
 | 
        
           |  |  | 4623 |   | 
        
           |  |  | 4624 |     // Add cohort theme.
 | 
        
           |  |  | 4625 |     if (!empty($CFG->allowcohortthemes)) {
 | 
        
           |  |  | 4626 |         require_once($CFG->dirroot . '/cohort/lib.php');
 | 
        
           |  |  | 4627 |         if ($cohorttheme = cohort_get_user_cohort_theme($user->id)) {
 | 
        
           |  |  | 4628 |             $user->cohorttheme = $cohorttheme;
 | 
        
           |  |  | 4629 |         }
 | 
        
           |  |  | 4630 |     }
 | 
        
           |  |  | 4631 |   | 
        
           |  |  | 4632 |     // Add the custom profile fields to the user record.
 | 
        
           |  |  | 4633 |     $user->profile = array();
 | 
        
           |  |  | 4634 |     if (!isguestuser($user)) {
 | 
        
           | 1326 | ariadna | 4635 |         require_once($CFG->dirroot . '/user/profile/lib.php');
 | 
        
           | 1 | efrain | 4636 |         profile_load_custom_fields($user);
 | 
        
           |  |  | 4637 |     }
 | 
        
           |  |  | 4638 |   | 
        
           |  |  | 4639 |     // Rewrite some variables if necessary.
 | 
        
           |  |  | 4640 |     if (!empty($user->description)) {
 | 
        
           |  |  | 4641 |         // No need to cart all of it around.
 | 
        
           |  |  | 4642 |         $user->description = true;
 | 
        
           |  |  | 4643 |     }
 | 
        
           |  |  | 4644 |     if (isguestuser($user)) {
 | 
        
           |  |  | 4645 |         // Guest language always same as site.
 | 
        
           |  |  | 4646 |         $user->lang = get_newuser_language();
 | 
        
           |  |  | 4647 |         // Name always in current language.
 | 
        
           |  |  | 4648 |         $user->firstname = get_string('guestuser');
 | 
        
           |  |  | 4649 |         $user->lastname = ' ';
 | 
        
           |  |  | 4650 |     }
 | 
        
           |  |  | 4651 |   | 
        
           |  |  | 4652 |     return $user;
 | 
        
           |  |  | 4653 | }
 | 
        
           |  |  | 4654 |   | 
        
           |  |  | 4655 | /**
 | 
        
           |  |  | 4656 |  * Validate a password against the configured password policy
 | 
        
           |  |  | 4657 |  *
 | 
        
           |  |  | 4658 |  * @param string $password the password to be checked against the password policy
 | 
        
           |  |  | 4659 |  * @param string|null $errmsg the error message to display when the password doesn't comply with the policy.
 | 
        
           |  |  | 4660 |  * @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
 | 
        
           |  |  | 4661 |  *
 | 
        
           |  |  | 4662 |  * @return bool true if the password is valid according to the policy. false otherwise.
 | 
        
           |  |  | 4663 |  */
 | 
        
           | 1326 | ariadna | 4664 | function check_password_policy(string $password, ?string &$errmsg, ?stdClass $user = null)
 | 
        
           |  |  | 4665 | {
 | 
        
           | 1 | efrain | 4666 |     global $CFG;
 | 
        
           |  |  | 4667 |     if (!empty($CFG->passwordpolicy) && !isguestuser($user)) {
 | 
        
           |  |  | 4668 |         $errors = get_password_policy_errors($password, $user);
 | 
        
           |  |  | 4669 |   | 
        
           |  |  | 4670 |         foreach ($errors as $error) {
 | 
        
           |  |  | 4671 |             $errmsg .= '<div>' . $error . '</div>';
 | 
        
           |  |  | 4672 |         }
 | 
        
           |  |  | 4673 |     }
 | 
        
           |  |  | 4674 |   | 
        
           |  |  | 4675 |     return $errmsg == '';
 | 
        
           |  |  | 4676 | }
 | 
        
           |  |  | 4677 |   | 
        
           |  |  | 4678 | /**
 | 
        
           |  |  | 4679 |  * Validate a password against the configured password policy.
 | 
        
           |  |  | 4680 |  * Note: This function is unaffected by whether the password policy is enabled or not.
 | 
        
           |  |  | 4681 |  *
 | 
        
           |  |  | 4682 |  * @param string $password the password to be checked against the password policy
 | 
        
           |  |  | 4683 |  * @param stdClass|null $user the user object to perform password validation against. Defaults to null if not provided.
 | 
        
           |  |  | 4684 |  *
 | 
        
           |  |  | 4685 |  * @return string[] Array of error messages.
 | 
        
           |  |  | 4686 |  */
 | 
        
           | 1326 | ariadna | 4687 | function get_password_policy_errors(string $password, ?stdClass $user = null): array
 | 
        
           |  |  | 4688 | {
 | 
        
           | 1 | efrain | 4689 |     global $CFG;
 | 
        
           |  |  | 4690 |   | 
        
           |  |  | 4691 |     $errors = [];
 | 
        
           |  |  | 4692 |   | 
        
           |  |  | 4693 |     if (core_text::strlen($password) < $CFG->minpasswordlength) {
 | 
        
           |  |  | 4694 |         $errors[] = get_string('errorminpasswordlength', 'auth', $CFG->minpasswordlength);
 | 
        
           |  |  | 4695 |     }
 | 
        
           |  |  | 4696 |     if (preg_match_all('/[[:digit:]]/u', $password, $matches) < $CFG->minpassworddigits) {
 | 
        
           |  |  | 4697 |         $errors[] = get_string('errorminpassworddigits', 'auth', $CFG->minpassworddigits);
 | 
        
           |  |  | 4698 |     }
 | 
        
           |  |  | 4699 |     if (preg_match_all('/[[:lower:]]/u', $password, $matches) < $CFG->minpasswordlower) {
 | 
        
           |  |  | 4700 |         $errors[] = get_string('errorminpasswordlower', 'auth', $CFG->minpasswordlower);
 | 
        
           |  |  | 4701 |     }
 | 
        
           |  |  | 4702 |     if (preg_match_all('/[[:upper:]]/u', $password, $matches) < $CFG->minpasswordupper) {
 | 
        
           |  |  | 4703 |         $errors[] = get_string('errorminpasswordupper', 'auth', $CFG->minpasswordupper);
 | 
        
           |  |  | 4704 |     }
 | 
        
           |  |  | 4705 |     if (preg_match_all('/[^[:upper:][:lower:][:digit:]]/u', $password, $matches) < $CFG->minpasswordnonalphanum) {
 | 
        
           |  |  | 4706 |         $errors[] = get_string('errorminpasswordnonalphanum', 'auth', $CFG->minpasswordnonalphanum);
 | 
        
           |  |  | 4707 |     }
 | 
        
           |  |  | 4708 |     if (!check_consecutive_identical_characters($password, $CFG->maxconsecutiveidentchars)) {
 | 
        
           |  |  | 4709 |         $errors[] = get_string('errormaxconsecutiveidentchars', 'auth', $CFG->maxconsecutiveidentchars);
 | 
        
           |  |  | 4710 |     }
 | 
        
           |  |  | 4711 |   | 
        
           |  |  | 4712 |     // Fire any additional password policy functions from plugins.
 | 
        
           |  |  | 4713 |     // Plugin functions should output an error message string or empty string for success.
 | 
        
           |  |  | 4714 |     $pluginsfunction = get_plugins_with_function('check_password_policy');
 | 
        
           |  |  | 4715 |     foreach ($pluginsfunction as $plugintype => $plugins) {
 | 
        
           |  |  | 4716 |         foreach ($plugins as $pluginfunction) {
 | 
        
           |  |  | 4717 |             $pluginerr = $pluginfunction($password, $user);
 | 
        
           |  |  | 4718 |             if ($pluginerr) {
 | 
        
           |  |  | 4719 |                 $errors[] = $pluginerr;
 | 
        
           |  |  | 4720 |             }
 | 
        
           |  |  | 4721 |         }
 | 
        
           |  |  | 4722 |     }
 | 
        
           |  |  | 4723 |   | 
        
           |  |  | 4724 |     return $errors;
 | 
        
           |  |  | 4725 | }
 | 
        
           |  |  | 4726 |   | 
        
           |  |  | 4727 | /**
 | 
        
           |  |  | 4728 |  * When logging in, this function is run to set certain preferences for the current SESSION.
 | 
        
           |  |  | 4729 |  */
 | 
        
           | 1326 | ariadna | 4730 | function set_login_session_preferences()
 | 
        
           |  |  | 4731 | {
 | 
        
           | 1 | efrain | 4732 |     global $SESSION;
 | 
        
           |  |  | 4733 |   | 
        
           |  |  | 4734 |     $SESSION->justloggedin = true;
 | 
        
           |  |  | 4735 |   | 
        
           |  |  | 4736 |     unset($SESSION->lang);
 | 
        
           |  |  | 4737 |     unset($SESSION->forcelang);
 | 
        
           |  |  | 4738 |     unset($SESSION->load_navigation_admin);
 | 
        
           |  |  | 4739 | }
 | 
        
           |  |  | 4740 |   | 
        
           |  |  | 4741 |   | 
        
           |  |  | 4742 | /**
 | 
        
           |  |  | 4743 |  * Delete a course, including all related data from the database, and any associated files.
 | 
        
           |  |  | 4744 |  *
 | 
        
           |  |  | 4745 |  * @param mixed $courseorid The id of the course or course object to delete.
 | 
        
           |  |  | 4746 |  * @param bool $showfeedback Whether to display notifications of each action the function performs.
 | 
        
           |  |  | 4747 |  * @return bool true if all the removals succeeded. false if there were any failures. If this
 | 
        
           |  |  | 4748 |  *             method returns false, some of the removals will probably have succeeded, and others
 | 
        
           |  |  | 4749 |  *             failed, but you have no way of knowing which.
 | 
        
           |  |  | 4750 |  */
 | 
        
           | 1326 | ariadna | 4751 | function delete_course($courseorid, $showfeedback = true)
 | 
        
           |  |  | 4752 | {
 | 
        
           | 1 | efrain | 4753 |     global $DB, $CFG;
 | 
        
           |  |  | 4754 |   | 
        
           |  |  | 4755 |     if (is_object($courseorid)) {
 | 
        
           |  |  | 4756 |         $courseid = $courseorid->id;
 | 
        
           |  |  | 4757 |         $course   = $courseorid;
 | 
        
           |  |  | 4758 |     } else {
 | 
        
           |  |  | 4759 |         $courseid = $courseorid;
 | 
        
           |  |  | 4760 |         if (!$course = $DB->get_record('course', array('id' => $courseid))) {
 | 
        
           |  |  | 4761 |             return false;
 | 
        
           |  |  | 4762 |         }
 | 
        
           |  |  | 4763 |     }
 | 
        
           |  |  | 4764 |     $context = context_course::instance($courseid);
 | 
        
           |  |  | 4765 |   | 
        
           |  |  | 4766 |     // Frontpage course can not be deleted!!
 | 
        
           |  |  | 4767 |     if ($courseid == SITEID) {
 | 
        
           |  |  | 4768 |         return false;
 | 
        
           |  |  | 4769 |     }
 | 
        
           |  |  | 4770 |   | 
        
           |  |  | 4771 |     // Allow plugins to use this course before we completely delete it.
 | 
        
           |  |  | 4772 |     if ($pluginsfunction = get_plugins_with_function('pre_course_delete')) {
 | 
        
           |  |  | 4773 |         foreach ($pluginsfunction as $plugintype => $plugins) {
 | 
        
           |  |  | 4774 |             foreach ($plugins as $pluginfunction) {
 | 
        
           |  |  | 4775 |                 $pluginfunction($course);
 | 
        
           |  |  | 4776 |             }
 | 
        
           |  |  | 4777 |         }
 | 
        
           |  |  | 4778 |     }
 | 
        
           |  |  | 4779 |   | 
        
           |  |  | 4780 |     // Dispatch the hook for pre course delete actions.
 | 
        
           |  |  | 4781 |     $hook = new \core_course\hook\before_course_deleted(
 | 
        
           |  |  | 4782 |         course: $course,
 | 
        
           |  |  | 4783 |     );
 | 
        
           |  |  | 4784 |     \core\di::get(\core\hook\manager::class)->dispatch($hook);
 | 
        
           |  |  | 4785 |   | 
        
           |  |  | 4786 |     // Tell the search manager we are about to delete a course. This prevents us sending updates
 | 
        
           |  |  | 4787 |     // for each individual context being deleted.
 | 
        
           |  |  | 4788 |     \core_search\manager::course_deleting_start($courseid);
 | 
        
           |  |  | 4789 |   | 
        
           |  |  | 4790 |     $handler = core_course\customfield\course_handler::create();
 | 
        
           |  |  | 4791 |     $handler->delete_instance($courseid);
 | 
        
           |  |  | 4792 |   | 
        
           |  |  | 4793 |     // Make the course completely empty.
 | 
        
           |  |  | 4794 |     remove_course_contents($courseid, $showfeedback);
 | 
        
           |  |  | 4795 |   | 
        
           |  |  | 4796 |     // Delete the course and related context instance.
 | 
        
           |  |  | 4797 |     context_helper::delete_instance(CONTEXT_COURSE, $courseid);
 | 
        
           |  |  | 4798 |   | 
        
           |  |  | 4799 |     $DB->delete_records("course", array("id" => $courseid));
 | 
        
           |  |  | 4800 |     $DB->delete_records("course_format_options", array("courseid" => $courseid));
 | 
        
           |  |  | 4801 |   | 
        
           |  |  | 4802 |     // Reset all course related caches here.
 | 
        
           |  |  | 4803 |     core_courseformat\base::reset_course_cache($courseid);
 | 
        
           |  |  | 4804 |   | 
        
           |  |  | 4805 |     // Tell search that we have deleted the course so it can delete course data from the index.
 | 
        
           |  |  | 4806 |     \core_search\manager::course_deleting_finish($courseid);
 | 
        
           |  |  | 4807 |   | 
        
           |  |  | 4808 |     // Trigger a course deleted event.
 | 
        
           |  |  | 4809 |     $event = \core\event\course_deleted::create(array(
 | 
        
           |  |  | 4810 |         'objectid' => $course->id,
 | 
        
           |  |  | 4811 |         'context' => $context,
 | 
        
           |  |  | 4812 |         'other' => array(
 | 
        
           |  |  | 4813 |             'shortname' => $course->shortname,
 | 
        
           |  |  | 4814 |             'fullname' => $course->fullname,
 | 
        
           |  |  | 4815 |             'idnumber' => $course->idnumber
 | 
        
           | 1326 | ariadna | 4816 |         )
 | 
        
           | 1 | efrain | 4817 |     ));
 | 
        
           |  |  | 4818 |     $event->add_record_snapshot('course', $course);
 | 
        
           |  |  | 4819 |     $event->trigger();
 | 
        
           |  |  | 4820 |   | 
        
           |  |  | 4821 |     return true;
 | 
        
           |  |  | 4822 | }
 | 
        
           |  |  | 4823 |   | 
        
           |  |  | 4824 | /**
 | 
        
           |  |  | 4825 |  * Clear a course out completely, deleting all content but don't delete the course itself.
 | 
        
           |  |  | 4826 |  *
 | 
        
           |  |  | 4827 |  * This function does not verify any permissions.
 | 
        
           |  |  | 4828 |  *
 | 
        
           |  |  | 4829 |  * Please note this function also deletes all user enrolments,
 | 
        
           |  |  | 4830 |  * enrolment instances and role assignments by default.
 | 
        
           |  |  | 4831 |  *
 | 
        
           |  |  | 4832 |  * $options:
 | 
        
           |  |  | 4833 |  *  - 'keep_roles_and_enrolments' - false by default
 | 
        
           |  |  | 4834 |  *  - 'keep_groups_and_groupings' - false by default
 | 
        
           |  |  | 4835 |  *
 | 
        
           |  |  | 4836 |  * @param int $courseid The id of the course that is being deleted
 | 
        
           |  |  | 4837 |  * @param bool $showfeedback Whether to display notifications of each action the function performs.
 | 
        
           |  |  | 4838 |  * @param array $options extra options
 | 
        
           |  |  | 4839 |  * @return bool true if all the removals succeeded. false if there were any failures. If this
 | 
        
           |  |  | 4840 |  *             method returns false, some of the removals will probably have succeeded, and others
 | 
        
           |  |  | 4841 |  *             failed, but you have no way of knowing which.
 | 
        
           |  |  | 4842 |  */
 | 
        
           | 1326 | ariadna | 4843 | function remove_course_contents($courseid, $showfeedback = true, array $options = null)
 | 
        
           |  |  | 4844 | {
 | 
        
           | 1 | efrain | 4845 |     global $CFG, $DB, $OUTPUT;
 | 
        
           |  |  | 4846 |   | 
        
           | 1326 | ariadna | 4847 |     require_once($CFG->libdir . '/badgeslib.php');
 | 
        
           |  |  | 4848 |     require_once($CFG->libdir . '/completionlib.php');
 | 
        
           |  |  | 4849 |     require_once($CFG->libdir . '/questionlib.php');
 | 
        
           |  |  | 4850 |     require_once($CFG->libdir . '/gradelib.php');
 | 
        
           |  |  | 4851 |     require_once($CFG->dirroot . '/group/lib.php');
 | 
        
           |  |  | 4852 |     require_once($CFG->dirroot . '/comment/lib.php');
 | 
        
           |  |  | 4853 |     require_once($CFG->dirroot . '/rating/lib.php');
 | 
        
           |  |  | 4854 |     require_once($CFG->dirroot . '/notes/lib.php');
 | 
        
           | 1 | efrain | 4855 |   | 
        
           |  |  | 4856 |     // Handle course badges.
 | 
        
           |  |  | 4857 |     badges_handle_course_deletion($courseid);
 | 
        
           |  |  | 4858 |   | 
        
           |  |  | 4859 |     // NOTE: these concatenated strings are suboptimal, but it is just extra info...
 | 
        
           | 1326 | ariadna | 4860 |     $strdeleted = get_string('deleted') . ' - ';
 | 
        
           | 1 | efrain | 4861 |   | 
        
           |  |  | 4862 |     // Some crazy wishlist of stuff we should skip during purging of course content.
 | 
        
           |  |  | 4863 |     $options = (array)$options;
 | 
        
           |  |  | 4864 |   | 
        
           |  |  | 4865 |     $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
 | 
        
           |  |  | 4866 |     $coursecontext = context_course::instance($courseid);
 | 
        
           |  |  | 4867 |     $fs = get_file_storage();
 | 
        
           |  |  | 4868 |   | 
        
           |  |  | 4869 |     // Delete course completion information, this has to be done before grades and enrols.
 | 
        
           |  |  | 4870 |     $cc = new completion_info($course);
 | 
        
           |  |  | 4871 |     $cc->clear_criteria();
 | 
        
           |  |  | 4872 |     if ($showfeedback) {
 | 
        
           | 1326 | ariadna | 4873 |         echo $OUTPUT->notification($strdeleted . get_string('completion', 'completion'), 'notifysuccess');
 | 
        
           | 1 | efrain | 4874 |     }
 | 
        
           |  |  | 4875 |   | 
        
           |  |  | 4876 |     // Remove all data from gradebook - this needs to be done before course modules
 | 
        
           |  |  | 4877 |     // because while deleting this information, the system may need to reference
 | 
        
           |  |  | 4878 |     // the course modules that own the grades.
 | 
        
           |  |  | 4879 |     remove_course_grades($courseid, $showfeedback);
 | 
        
           |  |  | 4880 |     remove_grade_letters($coursecontext, $showfeedback);
 | 
        
           |  |  | 4881 |   | 
        
           |  |  | 4882 |     // Delete course blocks in any all child contexts,
 | 
        
           |  |  | 4883 |     // they may depend on modules so delete them first.
 | 
        
           |  |  | 4884 |     $childcontexts = $coursecontext->get_child_contexts(); // Returns all subcontexts since 2.2.
 | 
        
           |  |  | 4885 |     foreach ($childcontexts as $childcontext) {
 | 
        
           |  |  | 4886 |         blocks_delete_all_for_context($childcontext->id);
 | 
        
           |  |  | 4887 |     }
 | 
        
           |  |  | 4888 |     unset($childcontexts);
 | 
        
           |  |  | 4889 |     blocks_delete_all_for_context($coursecontext->id);
 | 
        
           |  |  | 4890 |     if ($showfeedback) {
 | 
        
           | 1326 | ariadna | 4891 |         echo $OUTPUT->notification($strdeleted . get_string('type_block_plural', 'plugin'), 'notifysuccess');
 | 
        
           | 1 | efrain | 4892 |     }
 | 
        
           |  |  | 4893 |   | 
        
           |  |  | 4894 |     $DB->set_field('course_modules', 'deletioninprogress', '1', ['course' => $courseid]);
 | 
        
           |  |  | 4895 |     rebuild_course_cache($courseid, true);
 | 
        
           |  |  | 4896 |   | 
        
           |  |  | 4897 |     // Get the list of all modules that are properly installed.
 | 
        
           |  |  | 4898 |     $allmodules = $DB->get_records_menu('modules', array(), '', 'name, id');
 | 
        
           |  |  | 4899 |   | 
        
           |  |  | 4900 |     // Delete every instance of every module,
 | 
        
           |  |  | 4901 |     // this has to be done before deleting of course level stuff.
 | 
        
           |  |  | 4902 |     $locations = core_component::get_plugin_list('mod');
 | 
        
           |  |  | 4903 |     foreach ($locations as $modname => $moddir) {
 | 
        
           |  |  | 4904 |         if ($modname === 'NEWMODULE') {
 | 
        
           |  |  | 4905 |             continue;
 | 
        
           |  |  | 4906 |         }
 | 
        
           |  |  | 4907 |         if (array_key_exists($modname, $allmodules)) {
 | 
        
           |  |  | 4908 |             $sql = "SELECT cm.*, m.id AS modinstance, m.name, '$modname' AS modname
 | 
        
           | 1326 | ariadna | 4909 |               FROM {" . $modname . "} m
 | 
        
           | 1 | efrain | 4910 |                    LEFT JOIN {course_modules} cm ON cm.instance = m.id AND cm.module = :moduleid
 | 
        
           |  |  | 4911 |              WHERE m.course = :courseid";
 | 
        
           | 1326 | ariadna | 4912 |             $instances = $DB->get_records_sql($sql, array(
 | 
        
           |  |  | 4913 |                 'courseid' => $course->id,
 | 
        
           |  |  | 4914 |                 'modulename' => $modname,
 | 
        
           |  |  | 4915 |                 'moduleid' => $allmodules[$modname]
 | 
        
           |  |  | 4916 |             ));
 | 
        
           | 1 | efrain | 4917 |   | 
        
           |  |  | 4918 |             include_once("$moddir/lib.php");                 // Shows php warning only if plugin defective.
 | 
        
           | 1326 | ariadna | 4919 |             $moddelete = $modname . '_delete_instance';       // Delete everything connected to an instance.
 | 
        
           | 1 | efrain | 4920 |   | 
        
           |  |  | 4921 |             if ($instances) {
 | 
        
           |  |  | 4922 |                 foreach ($instances as $cm) {
 | 
        
           |  |  | 4923 |                     if ($cm->id) {
 | 
        
           |  |  | 4924 |                         // Delete activity context questions and question categories.
 | 
        
           |  |  | 4925 |                         question_delete_activity($cm);
 | 
        
           |  |  | 4926 |                         // Notify the competency subsystem.
 | 
        
           |  |  | 4927 |                         \core_competency\api::hook_course_module_deleted($cm);
 | 
        
           |  |  | 4928 |   | 
        
           |  |  | 4929 |                         // Delete all tag instances associated with the instance of this module.
 | 
        
           |  |  | 4930 |                         core_tag_tag::delete_instances("mod_{$modname}", null, context_module::instance($cm->id)->id);
 | 
        
           |  |  | 4931 |                         core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
 | 
        
           |  |  | 4932 |                     }
 | 
        
           |  |  | 4933 |                     if (function_exists($moddelete)) {
 | 
        
           |  |  | 4934 |                         // This purges all module data in related tables, extra user prefs, settings, etc.
 | 
        
           |  |  | 4935 |                         $moddelete($cm->modinstance);
 | 
        
           |  |  | 4936 |                     } else {
 | 
        
           |  |  | 4937 |                         // NOTE: we should not allow installation of modules with missing delete support!
 | 
        
           |  |  | 4938 |                         debugging("Defective module '$modname' detected when deleting course contents: missing function $moddelete()!");
 | 
        
           |  |  | 4939 |                         $DB->delete_records($modname, array('id' => $cm->modinstance));
 | 
        
           |  |  | 4940 |                     }
 | 
        
           |  |  | 4941 |   | 
        
           |  |  | 4942 |                     if ($cm->id) {
 | 
        
           |  |  | 4943 |                         // Delete cm and its context - orphaned contexts are purged in cron in case of any race condition.
 | 
        
           |  |  | 4944 |                         context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
 | 
        
           |  |  | 4945 |                         $DB->delete_records('course_modules_completion', ['coursemoduleid' => $cm->id]);
 | 
        
           |  |  | 4946 |                         $DB->delete_records('course_modules_viewed', ['coursemoduleid' => $cm->id]);
 | 
        
           |  |  | 4947 |                         $DB->delete_records('course_modules', array('id' => $cm->id));
 | 
        
           |  |  | 4948 |                         rebuild_course_cache($cm->course, true);
 | 
        
           |  |  | 4949 |                     }
 | 
        
           |  |  | 4950 |                 }
 | 
        
           |  |  | 4951 |             }
 | 
        
           |  |  | 4952 |             if ($instances and $showfeedback) {
 | 
        
           | 1326 | ariadna | 4953 |                 echo $OUTPUT->notification($strdeleted . get_string('pluginname', $modname), 'notifysuccess');
 | 
        
           | 1 | efrain | 4954 |             }
 | 
        
           |  |  | 4955 |         } else {
 | 
        
           |  |  | 4956 |             // Ooops, this module is not properly installed, force-delete it in the next block.
 | 
        
           |  |  | 4957 |         }
 | 
        
           |  |  | 4958 |     }
 | 
        
           |  |  | 4959 |   | 
        
           |  |  | 4960 |     // We have tried to delete everything the nice way - now let's force-delete any remaining module data.
 | 
        
           |  |  | 4961 |   | 
        
           |  |  | 4962 |     // Delete completion defaults.
 | 
        
           |  |  | 4963 |     $DB->delete_records("course_completion_defaults", array("course" => $courseid));
 | 
        
           |  |  | 4964 |   | 
        
           |  |  | 4965 |     // Remove all data from availability and completion tables that is associated
 | 
        
           |  |  | 4966 |     // with course-modules belonging to this course. Note this is done even if the
 | 
        
           |  |  | 4967 |     // features are not enabled now, in case they were enabled previously.
 | 
        
           | 1326 | ariadna | 4968 |     $DB->delete_records_subquery(
 | 
        
           |  |  | 4969 |         'course_modules_completion',
 | 
        
           |  |  | 4970 |         'coursemoduleid',
 | 
        
           |  |  | 4971 |         'id',
 | 
        
           |  |  | 4972 |         'SELECT id from {course_modules} WHERE course = ?',
 | 
        
           |  |  | 4973 |         [$courseid]
 | 
        
           |  |  | 4974 |     );
 | 
        
           |  |  | 4975 |     $DB->delete_records_subquery(
 | 
        
           |  |  | 4976 |         'course_modules_viewed',
 | 
        
           |  |  | 4977 |         'coursemoduleid',
 | 
        
           |  |  | 4978 |         'id',
 | 
        
           |  |  | 4979 |         'SELECT id from {course_modules} WHERE course = ?',
 | 
        
           |  |  | 4980 |         [$courseid]
 | 
        
           |  |  | 4981 |     );
 | 
        
           | 1 | efrain | 4982 |   | 
        
           |  |  | 4983 |     // Remove course-module data that has not been removed in modules' _delete_instance callbacks.
 | 
        
           |  |  | 4984 |     $cms = $DB->get_records('course_modules', array('course' => $course->id));
 | 
        
           |  |  | 4985 |     $allmodulesbyid = array_flip($allmodules);
 | 
        
           |  |  | 4986 |     foreach ($cms as $cm) {
 | 
        
           |  |  | 4987 |         if (array_key_exists($cm->module, $allmodulesbyid)) {
 | 
        
           |  |  | 4988 |             try {
 | 
        
           |  |  | 4989 |                 $DB->delete_records($allmodulesbyid[$cm->module], array('id' => $cm->instance));
 | 
        
           |  |  | 4990 |             } catch (Exception $e) {
 | 
        
           |  |  | 4991 |                 // Ignore weird or missing table problems.
 | 
        
           |  |  | 4992 |             }
 | 
        
           |  |  | 4993 |         }
 | 
        
           |  |  | 4994 |         context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
 | 
        
           |  |  | 4995 |         $DB->delete_records('course_modules', array('id' => $cm->id));
 | 
        
           |  |  | 4996 |         rebuild_course_cache($cm->course, true);
 | 
        
           |  |  | 4997 |     }
 | 
        
           |  |  | 4998 |   | 
        
           |  |  | 4999 |     if ($showfeedback) {
 | 
        
           | 1326 | ariadna | 5000 |         echo $OUTPUT->notification($strdeleted . get_string('type_mod_plural', 'plugin'), 'notifysuccess');
 | 
        
           | 1 | efrain | 5001 |     }
 | 
        
           |  |  | 5002 |   | 
        
           |  |  | 5003 |     // Delete questions and question categories.
 | 
        
           |  |  | 5004 |     question_delete_course($course);
 | 
        
           |  |  | 5005 |     if ($showfeedback) {
 | 
        
           | 1326 | ariadna | 5006 |         echo $OUTPUT->notification($strdeleted . get_string('questions', 'question'), 'notifysuccess');
 | 
        
           | 1 | efrain | 5007 |     }
 | 
        
           |  |  | 5008 |   | 
        
           |  |  | 5009 |     // Delete content bank contents.
 | 
        
           |  |  | 5010 |     $cb = new \core_contentbank\contentbank();
 | 
        
           |  |  | 5011 |     $cbdeleted = $cb->delete_contents($coursecontext);
 | 
        
           |  |  | 5012 |     if ($showfeedback && $cbdeleted) {
 | 
        
           | 1326 | ariadna | 5013 |         echo $OUTPUT->notification($strdeleted . get_string('contentbank', 'contentbank'), 'notifysuccess');
 | 
        
           | 1 | efrain | 5014 |     }
 | 
        
           |  |  | 5015 |   | 
        
           |  |  | 5016 |     // Make sure there are no subcontexts left - all valid blocks and modules should be already gone.
 | 
        
           |  |  | 5017 |     $childcontexts = $coursecontext->get_child_contexts(); // Returns all subcontexts since 2.2.
 | 
        
           |  |  | 5018 |     foreach ($childcontexts as $childcontext) {
 | 
        
           |  |  | 5019 |         $childcontext->delete();
 | 
        
           |  |  | 5020 |     }
 | 
        
           |  |  | 5021 |     unset($childcontexts);
 | 
        
           |  |  | 5022 |   | 
        
           |  |  | 5023 |     // Remove roles and enrolments by default.
 | 
        
           |  |  | 5024 |     if (empty($options['keep_roles_and_enrolments'])) {
 | 
        
           |  |  | 5025 |         // This hack is used in restore when deleting contents of existing course.
 | 
        
           |  |  | 5026 |         // During restore, we should remove only enrolment related data that the user performing the restore has a
 | 
        
           |  |  | 5027 |         // permission to remove.
 | 
        
           |  |  | 5028 |         $userid = $options['userid'] ?? null;
 | 
        
           |  |  | 5029 |         enrol_course_delete($course, $userid);
 | 
        
           |  |  | 5030 |         role_unassign_all(array('contextid' => $coursecontext->id, 'component' => ''), true);
 | 
        
           |  |  | 5031 |         if ($showfeedback) {
 | 
        
           | 1326 | ariadna | 5032 |             echo $OUTPUT->notification($strdeleted . get_string('type_enrol_plural', 'plugin'), 'notifysuccess');
 | 
        
           | 1 | efrain | 5033 |         }
 | 
        
           |  |  | 5034 |     }
 | 
        
           |  |  | 5035 |   | 
        
           |  |  | 5036 |     // Delete any groups, removing members and grouping/course links first.
 | 
        
           |  |  | 5037 |     if (empty($options['keep_groups_and_groupings'])) {
 | 
        
           |  |  | 5038 |         groups_delete_groupings($course->id, $showfeedback);
 | 
        
           |  |  | 5039 |         groups_delete_groups($course->id, $showfeedback);
 | 
        
           |  |  | 5040 |     }
 | 
        
           |  |  | 5041 |   | 
        
           |  |  | 5042 |     // Filters be gone!
 | 
        
           |  |  | 5043 |     filter_delete_all_for_context($coursecontext->id);
 | 
        
           |  |  | 5044 |   | 
        
           |  |  | 5045 |     // Notes, you shall not pass!
 | 
        
           |  |  | 5046 |     note_delete_all($course->id);
 | 
        
           |  |  | 5047 |   | 
        
           |  |  | 5048 |     // Die comments!
 | 
        
           |  |  | 5049 |     comment::delete_comments($coursecontext->id);
 | 
        
           |  |  | 5050 |   | 
        
           |  |  | 5051 |     // Ratings are history too.
 | 
        
           |  |  | 5052 |     $delopt = new stdclass();
 | 
        
           |  |  | 5053 |     $delopt->contextid = $coursecontext->id;
 | 
        
           |  |  | 5054 |     $rm = new rating_manager();
 | 
        
           |  |  | 5055 |     $rm->delete_ratings($delopt);
 | 
        
           |  |  | 5056 |   | 
        
           |  |  | 5057 |     // Delete course tags.
 | 
        
           |  |  | 5058 |     core_tag_tag::remove_all_item_tags('core', 'course', $course->id);
 | 
        
           |  |  | 5059 |   | 
        
           |  |  | 5060 |     // Give the course format the opportunity to remove its obscure data.
 | 
        
           |  |  | 5061 |     $format = course_get_format($course);
 | 
        
           |  |  | 5062 |     $format->delete_format_data();
 | 
        
           |  |  | 5063 |   | 
        
           |  |  | 5064 |     // Notify the competency subsystem.
 | 
        
           |  |  | 5065 |     \core_competency\api::hook_course_deleted($course);
 | 
        
           |  |  | 5066 |   | 
        
           |  |  | 5067 |     // Delete calendar events.
 | 
        
           |  |  | 5068 |     $DB->delete_records('event', array('courseid' => $course->id));
 | 
        
           |  |  | 5069 |     $fs->delete_area_files($coursecontext->id, 'calendar');
 | 
        
           |  |  | 5070 |   | 
        
           |  |  | 5071 |     // Delete all related records in other core tables that may have a courseid
 | 
        
           |  |  | 5072 |     // This array stores the tables that need to be cleared, as
 | 
        
           |  |  | 5073 |     // table_name => column_name that contains the course id.
 | 
        
           |  |  | 5074 |     $tablestoclear = array(
 | 
        
           |  |  | 5075 |         'backup_courses' => 'courseid',  // Scheduled backup stuff.
 | 
        
           |  |  | 5076 |         'user_lastaccess' => 'courseid', // User access info.
 | 
        
           |  |  | 5077 |     );
 | 
        
           |  |  | 5078 |     foreach ($tablestoclear as $table => $col) {
 | 
        
           |  |  | 5079 |         $DB->delete_records($table, array($col => $course->id));
 | 
        
           |  |  | 5080 |     }
 | 
        
           |  |  | 5081 |   | 
        
           |  |  | 5082 |     // Delete all course backup files.
 | 
        
           |  |  | 5083 |     $fs->delete_area_files($coursecontext->id, 'backup');
 | 
        
           |  |  | 5084 |   | 
        
           |  |  | 5085 |     // Cleanup course record - remove links to deleted stuff.
 | 
        
           |  |  | 5086 |     // Do not wipe cacherev, as this course might be reused and we need to ensure that it keeps
 | 
        
           |  |  | 5087 |     // increasing.
 | 
        
           |  |  | 5088 |     $oldcourse = new stdClass();
 | 
        
           |  |  | 5089 |     $oldcourse->id               = $course->id;
 | 
        
           |  |  | 5090 |     $oldcourse->summary          = '';
 | 
        
           |  |  | 5091 |     $oldcourse->legacyfiles      = 0;
 | 
        
           |  |  | 5092 |     if (!empty($options['keep_groups_and_groupings'])) {
 | 
        
           |  |  | 5093 |         $oldcourse->defaultgroupingid = 0;
 | 
        
           |  |  | 5094 |     }
 | 
        
           |  |  | 5095 |     $DB->update_record('course', $oldcourse);
 | 
        
           |  |  | 5096 |   | 
        
           |  |  | 5097 |     // Delete course sections.
 | 
        
           |  |  | 5098 |     $DB->delete_records('course_sections', array('course' => $course->id));
 | 
        
           |  |  | 5099 |   | 
        
           |  |  | 5100 |     // Delete legacy, section and any other course files.
 | 
        
           |  |  | 5101 |     $fs->delete_area_files($coursecontext->id, 'course'); // Files from summary and section.
 | 
        
           |  |  | 5102 |   | 
        
           |  |  | 5103 |     // Delete all remaining stuff linked to context such as files, comments, ratings, etc.
 | 
        
           |  |  | 5104 |     if (empty($options['keep_roles_and_enrolments']) and empty($options['keep_groups_and_groupings'])) {
 | 
        
           |  |  | 5105 |         // Easy, do not delete the context itself...
 | 
        
           |  |  | 5106 |         $coursecontext->delete_content();
 | 
        
           |  |  | 5107 |     } else {
 | 
        
           |  |  | 5108 |         // Hack alert!!!!
 | 
        
           |  |  | 5109 |         // We can not drop all context stuff because it would bork enrolments and roles,
 | 
        
           |  |  | 5110 |         // there might be also files used by enrol plugins...
 | 
        
           |  |  | 5111 |     }
 | 
        
           |  |  | 5112 |   | 
        
           |  |  | 5113 |     // Delete legacy files - just in case some files are still left there after conversion to new file api,
 | 
        
           |  |  | 5114 |     // also some non-standard unsupported plugins may try to store something there.
 | 
        
           | 1326 | ariadna | 5115 |     fulldelete($CFG->dataroot . '/' . $course->id);
 | 
        
           | 1 | efrain | 5116 |   | 
        
           |  |  | 5117 |     // Delete from cache to reduce the cache size especially makes sense in case of bulk course deletion.
 | 
        
           |  |  | 5118 |     course_modinfo::purge_course_cache($courseid);
 | 
        
           |  |  | 5119 |   | 
        
           |  |  | 5120 |     // Trigger a course content deleted event.
 | 
        
           |  |  | 5121 |     $event = \core\event\course_content_deleted::create(array(
 | 
        
           |  |  | 5122 |         'objectid' => $course->id,
 | 
        
           |  |  | 5123 |         'context' => $coursecontext,
 | 
        
           | 1326 | ariadna | 5124 |         'other' => array(
 | 
        
           |  |  | 5125 |             'shortname' => $course->shortname,
 | 
        
           |  |  | 5126 |             'fullname' => $course->fullname,
 | 
        
           |  |  | 5127 |             'options' => $options
 | 
        
           |  |  | 5128 |         ) // Passing this for legacy reasons.
 | 
        
           | 1 | efrain | 5129 |     ));
 | 
        
           |  |  | 5130 |     $event->add_record_snapshot('course', $course);
 | 
        
           |  |  | 5131 |     $event->trigger();
 | 
        
           |  |  | 5132 |   | 
        
           |  |  | 5133 |     return true;
 | 
        
           |  |  | 5134 | }
 | 
        
           |  |  | 5135 |   | 
        
           |  |  | 5136 | /**
 | 
        
           |  |  | 5137 |  * Change dates in module - used from course reset.
 | 
        
           |  |  | 5138 |  *
 | 
        
           |  |  | 5139 |  * @param string $modname forum, assignment, etc
 | 
        
           |  |  | 5140 |  * @param array $fields array of date fields from mod table
 | 
        
           |  |  | 5141 |  * @param int $timeshift time difference
 | 
        
           |  |  | 5142 |  * @param int $courseid
 | 
        
           |  |  | 5143 |  * @param int $modid (Optional) passed if specific mod instance in course needs to be updated.
 | 
        
           |  |  | 5144 |  * @return bool success
 | 
        
           |  |  | 5145 |  */
 | 
        
           | 1326 | ariadna | 5146 | function shift_course_mod_dates($modname, $fields, $timeshift, $courseid, $modid = 0)
 | 
        
           |  |  | 5147 | {
 | 
        
           | 1 | efrain | 5148 |     global $CFG, $DB;
 | 
        
           | 1326 | ariadna | 5149 |     include_once($CFG->dirroot . '/mod/' . $modname . '/lib.php');
 | 
        
           | 1 | efrain | 5150 |   | 
        
           |  |  | 5151 |     $return = true;
 | 
        
           |  |  | 5152 |     $params = array($timeshift, $courseid);
 | 
        
           |  |  | 5153 |     foreach ($fields as $field) {
 | 
        
           | 1326 | ariadna | 5154 |         $updatesql = "UPDATE {" . $modname . "}
 | 
        
           | 1 | efrain | 5155 |                           SET $field = $field + ?
 | 
        
           |  |  | 5156 |                         WHERE course=? AND $field<>0";
 | 
        
           |  |  | 5157 |         if ($modid) {
 | 
        
           |  |  | 5158 |             $updatesql .= ' AND id=?';
 | 
        
           |  |  | 5159 |             $params[] = $modid;
 | 
        
           |  |  | 5160 |         }
 | 
        
           |  |  | 5161 |         $return = $DB->execute($updatesql, $params) && $return;
 | 
        
           |  |  | 5162 |     }
 | 
        
           |  |  | 5163 |   | 
        
           |  |  | 5164 |     return $return;
 | 
        
           |  |  | 5165 | }
 | 
        
           |  |  | 5166 |   | 
        
           |  |  | 5167 | /**
 | 
        
           |  |  | 5168 |  * This function will empty a course of user data.
 | 
        
           |  |  | 5169 |  * It will retain the activities and the structure of the course.
 | 
        
           |  |  | 5170 |  *
 | 
        
           |  |  | 5171 |  * @param object $data an object containing all the settings including courseid (without magic quotes)
 | 
        
           |  |  | 5172 |  * @return array status array of array component, item, error
 | 
        
           |  |  | 5173 |  */
 | 
        
           | 1326 | ariadna | 5174 | function reset_course_userdata($data)
 | 
        
           |  |  | 5175 | {
 | 
        
           | 1 | efrain | 5176 |     global $CFG, $DB;
 | 
        
           | 1326 | ariadna | 5177 |     require_once($CFG->libdir . '/gradelib.php');
 | 
        
           |  |  | 5178 |     require_once($CFG->libdir . '/completionlib.php');
 | 
        
           |  |  | 5179 |     require_once($CFG->dirroot . '/completion/criteria/completion_criteria_date.php');
 | 
        
           |  |  | 5180 |     require_once($CFG->dirroot . '/group/lib.php');
 | 
        
           | 1 | efrain | 5181 |   | 
        
           |  |  | 5182 |     $data->courseid = $data->id;
 | 
        
           |  |  | 5183 |     $context = context_course::instance($data->courseid);
 | 
        
           |  |  | 5184 |   | 
        
           |  |  | 5185 |     $eventparams = array(
 | 
        
           |  |  | 5186 |         'context' => $context,
 | 
        
           |  |  | 5187 |         'courseid' => $data->id,
 | 
        
           |  |  | 5188 |         'other' => array(
 | 
        
           |  |  | 5189 |             'reset_options' => (array) $data
 | 
        
           |  |  | 5190 |         )
 | 
        
           |  |  | 5191 |     );
 | 
        
           |  |  | 5192 |     $event = \core\event\course_reset_started::create($eventparams);
 | 
        
           |  |  | 5193 |     $event->trigger();
 | 
        
           |  |  | 5194 |   | 
        
           |  |  | 5195 |     // Calculate the time shift of dates.
 | 
        
           |  |  | 5196 |     if (!empty($data->reset_start_date)) {
 | 
        
           |  |  | 5197 |         // Time part of course startdate should be zero.
 | 
        
           |  |  | 5198 |         $data->timeshift = $data->reset_start_date - usergetmidnight($data->reset_start_date_old);
 | 
        
           |  |  | 5199 |     } else {
 | 
        
           |  |  | 5200 |         $data->timeshift = 0;
 | 
        
           |  |  | 5201 |     }
 | 
        
           |  |  | 5202 |   | 
        
           |  |  | 5203 |     // Result array: component, item, error.
 | 
        
           |  |  | 5204 |     $status = array();
 | 
        
           |  |  | 5205 |   | 
        
           |  |  | 5206 |     // Start the resetting.
 | 
        
           |  |  | 5207 |     $componentstr = get_string('general');
 | 
        
           |  |  | 5208 |   | 
        
           |  |  | 5209 |     // Move the course start time.
 | 
        
           |  |  | 5210 |     if (!empty($data->reset_start_date) and $data->timeshift) {
 | 
        
           |  |  | 5211 |         // Change course start data.
 | 
        
           |  |  | 5212 |         $DB->set_field('course', 'startdate', $data->reset_start_date, array('id' => $data->courseid));
 | 
        
           |  |  | 5213 |         // Update all course and group events - do not move activity events.
 | 
        
           |  |  | 5214 |         $updatesql = "UPDATE {event}
 | 
        
           |  |  | 5215 |                          SET timestart = timestart + ?
 | 
        
           |  |  | 5216 |                        WHERE courseid=? AND instance=0";
 | 
        
           |  |  | 5217 |         $DB->execute($updatesql, array($data->timeshift, $data->courseid));
 | 
        
           |  |  | 5218 |   | 
        
           |  |  | 5219 |         // Update any date activity restrictions.
 | 
        
           |  |  | 5220 |         if ($CFG->enableavailability) {
 | 
        
           |  |  | 5221 |             \availability_date\condition::update_all_dates($data->courseid, $data->timeshift);
 | 
        
           |  |  | 5222 |         }
 | 
        
           |  |  | 5223 |   | 
        
           |  |  | 5224 |         // Update completion expected dates.
 | 
        
           |  |  | 5225 |         if ($CFG->enablecompletion) {
 | 
        
           |  |  | 5226 |             $modinfo = get_fast_modinfo($data->courseid);
 | 
        
           |  |  | 5227 |             $changed = false;
 | 
        
           |  |  | 5228 |             foreach ($modinfo->get_cms() as $cm) {
 | 
        
           |  |  | 5229 |                 if ($cm->completion && !empty($cm->completionexpected)) {
 | 
        
           | 1326 | ariadna | 5230 |                     $DB->set_field(
 | 
        
           |  |  | 5231 |                         'course_modules',
 | 
        
           |  |  | 5232 |                         'completionexpected',
 | 
        
           |  |  | 5233 |                         $cm->completionexpected + $data->timeshift,
 | 
        
           |  |  | 5234 |                         array('id' => $cm->id)
 | 
        
           |  |  | 5235 |                     );
 | 
        
           | 1 | efrain | 5236 |                     $changed = true;
 | 
        
           |  |  | 5237 |                 }
 | 
        
           |  |  | 5238 |             }
 | 
        
           |  |  | 5239 |   | 
        
           |  |  | 5240 |             // Clear course cache if changes made.
 | 
        
           |  |  | 5241 |             if ($changed) {
 | 
        
           |  |  | 5242 |                 rebuild_course_cache($data->courseid, true);
 | 
        
           |  |  | 5243 |             }
 | 
        
           |  |  | 5244 |   | 
        
           |  |  | 5245 |             // Update course date completion criteria.
 | 
        
           |  |  | 5246 |             \completion_criteria_date::update_date($data->courseid, $data->timeshift);
 | 
        
           |  |  | 5247 |         }
 | 
        
           |  |  | 5248 |   | 
        
           |  |  | 5249 |         $status[] = array('component' => $componentstr, 'item' => get_string('datechanged'), 'error' => false);
 | 
        
           |  |  | 5250 |     }
 | 
        
           |  |  | 5251 |   | 
        
           |  |  | 5252 |     if (!empty($data->reset_end_date)) {
 | 
        
           |  |  | 5253 |         // If the user set a end date value respect it.
 | 
        
           |  |  | 5254 |         $DB->set_field('course', 'enddate', $data->reset_end_date, array('id' => $data->courseid));
 | 
        
           |  |  | 5255 |     } else if ($data->timeshift > 0 && $data->reset_end_date_old) {
 | 
        
           |  |  | 5256 |         // If there is a time shift apply it to the end date as well.
 | 
        
           |  |  | 5257 |         $enddate = $data->reset_end_date_old + $data->timeshift;
 | 
        
           |  |  | 5258 |         $DB->set_field('course', 'enddate', $enddate, array('id' => $data->courseid));
 | 
        
           |  |  | 5259 |     }
 | 
        
           |  |  | 5260 |   | 
        
           |  |  | 5261 |     if (!empty($data->reset_events)) {
 | 
        
           |  |  | 5262 |         $DB->delete_records('event', array('courseid' => $data->courseid));
 | 
        
           |  |  | 5263 |         $status[] = array('component' => $componentstr, 'item' => get_string('deleteevents', 'calendar'), 'error' => false);
 | 
        
           |  |  | 5264 |     }
 | 
        
           |  |  | 5265 |   | 
        
           |  |  | 5266 |     if (!empty($data->reset_notes)) {
 | 
        
           | 1326 | ariadna | 5267 |         require_once($CFG->dirroot . '/notes/lib.php');
 | 
        
           | 1 | efrain | 5268 |         note_delete_all($data->courseid);
 | 
        
           |  |  | 5269 |         $status[] = array('component' => $componentstr, 'item' => get_string('deletenotes', 'notes'), 'error' => false);
 | 
        
           |  |  | 5270 |     }
 | 
        
           |  |  | 5271 |   | 
        
           |  |  | 5272 |     if (!empty($data->delete_blog_associations)) {
 | 
        
           | 1326 | ariadna | 5273 |         require_once($CFG->dirroot . '/blog/lib.php');
 | 
        
           | 1 | efrain | 5274 |         blog_remove_associations_for_course($data->courseid);
 | 
        
           |  |  | 5275 |         $status[] = array('component' => $componentstr, 'item' => get_string('deleteblogassociations', 'blog'), 'error' => false);
 | 
        
           |  |  | 5276 |     }
 | 
        
           |  |  | 5277 |   | 
        
           |  |  | 5278 |     if (!empty($data->reset_completion)) {
 | 
        
           |  |  | 5279 |         // Delete course and activity completion information.
 | 
        
           |  |  | 5280 |         $course = $DB->get_record('course', array('id' => $data->courseid));
 | 
        
           |  |  | 5281 |         $cc = new completion_info($course);
 | 
        
           |  |  | 5282 |         $cc->delete_all_completion_data();
 | 
        
           | 1326 | ariadna | 5283 |         $status[] = array(
 | 
        
           |  |  | 5284 |             'component' => $componentstr,
 | 
        
           |  |  | 5285 |             'item' => get_string('deletecompletiondata', 'completion'),
 | 
        
           |  |  | 5286 |             'error' => false
 | 
        
           |  |  | 5287 |         );
 | 
        
           | 1 | efrain | 5288 |     }
 | 
        
           |  |  | 5289 |   | 
        
           |  |  | 5290 |     if (!empty($data->reset_competency_ratings)) {
 | 
        
           |  |  | 5291 |         \core_competency\api::hook_course_reset_competency_ratings($data->courseid);
 | 
        
           | 1326 | ariadna | 5292 |         $status[] = array(
 | 
        
           |  |  | 5293 |             'component' => $componentstr,
 | 
        
           |  |  | 5294 |             'item' => get_string('deletecompetencyratings', 'core_competency'),
 | 
        
           |  |  | 5295 |             'error' => false
 | 
        
           |  |  | 5296 |         );
 | 
        
           | 1 | efrain | 5297 |     }
 | 
        
           |  |  | 5298 |   | 
        
           |  |  | 5299 |     $componentstr = get_string('roles');
 | 
        
           |  |  | 5300 |   | 
        
           |  |  | 5301 |     if (!empty($data->reset_roles_overrides)) {
 | 
        
           |  |  | 5302 |         $children = $context->get_child_contexts();
 | 
        
           |  |  | 5303 |         foreach ($children as $child) {
 | 
        
           |  |  | 5304 |             $child->delete_capabilities();
 | 
        
           |  |  | 5305 |         }
 | 
        
           |  |  | 5306 |         $context->delete_capabilities();
 | 
        
           |  |  | 5307 |         $status[] = array('component' => $componentstr, 'item' => get_string('deletecourseoverrides', 'role'), 'error' => false);
 | 
        
           |  |  | 5308 |     }
 | 
        
           |  |  | 5309 |   | 
        
           |  |  | 5310 |     if (!empty($data->reset_roles_local)) {
 | 
        
           |  |  | 5311 |         $children = $context->get_child_contexts();
 | 
        
           |  |  | 5312 |         foreach ($children as $child) {
 | 
        
           |  |  | 5313 |             role_unassign_all(array('contextid' => $child->id));
 | 
        
           |  |  | 5314 |         }
 | 
        
           |  |  | 5315 |         $status[] = array('component' => $componentstr, 'item' => get_string('deletelocalroles', 'role'), 'error' => false);
 | 
        
           |  |  | 5316 |     }
 | 
        
           |  |  | 5317 |   | 
        
           |  |  | 5318 |     // First unenrol users - this cleans some of related user data too, such as forum subscriptions, tracking, etc.
 | 
        
           |  |  | 5319 |     $data->unenrolled = array();
 | 
        
           |  |  | 5320 |     if (!empty($data->unenrol_users)) {
 | 
        
           |  |  | 5321 |         $plugins = enrol_get_plugins(true);
 | 
        
           |  |  | 5322 |         $instances = enrol_get_instances($data->courseid, true);
 | 
        
           |  |  | 5323 |         foreach ($instances as $key => $instance) {
 | 
        
           |  |  | 5324 |             if (!isset($plugins[$instance->enrol])) {
 | 
        
           |  |  | 5325 |                 unset($instances[$key]);
 | 
        
           |  |  | 5326 |                 continue;
 | 
        
           |  |  | 5327 |             }
 | 
        
           |  |  | 5328 |         }
 | 
        
           |  |  | 5329 |   | 
        
           |  |  | 5330 |         $usersroles = enrol_get_course_users_roles($data->courseid);
 | 
        
           |  |  | 5331 |         foreach ($data->unenrol_users as $withroleid) {
 | 
        
           |  |  | 5332 |             if ($withroleid) {
 | 
        
           |  |  | 5333 |                 $sql = "SELECT ue.*
 | 
        
           |  |  | 5334 |                           FROM {user_enrolments} ue
 | 
        
           |  |  | 5335 |                           JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
 | 
        
           |  |  | 5336 |                           JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
 | 
        
           |  |  | 5337 |                           JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.roleid = :roleid AND ra.userid = ue.userid)";
 | 
        
           |  |  | 5338 |                 $params = array('courseid' => $data->courseid, 'roleid' => $withroleid, 'courselevel' => CONTEXT_COURSE);
 | 
        
           |  |  | 5339 |             } else {
 | 
        
           |  |  | 5340 |                 // Without any role assigned at course context.
 | 
        
           |  |  | 5341 |                 $sql = "SELECT ue.*
 | 
        
           |  |  | 5342 |                           FROM {user_enrolments} ue
 | 
        
           |  |  | 5343 |                           JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
 | 
        
           |  |  | 5344 |                           JOIN {context} c ON (c.contextlevel = :courselevel AND c.instanceid = e.courseid)
 | 
        
           |  |  | 5345 |                      LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid)
 | 
        
           |  |  | 5346 |                          WHERE ra.id IS null";
 | 
        
           |  |  | 5347 |                 $params = array('courseid' => $data->courseid, 'courselevel' => CONTEXT_COURSE);
 | 
        
           |  |  | 5348 |             }
 | 
        
           |  |  | 5349 |   | 
        
           |  |  | 5350 |             $rs = $DB->get_recordset_sql($sql, $params);
 | 
        
           |  |  | 5351 |             foreach ($rs as $ue) {
 | 
        
           |  |  | 5352 |                 if (!isset($instances[$ue->enrolid])) {
 | 
        
           |  |  | 5353 |                     continue;
 | 
        
           |  |  | 5354 |                 }
 | 
        
           |  |  | 5355 |                 $instance = $instances[$ue->enrolid];
 | 
        
           |  |  | 5356 |                 $plugin = $plugins[$instance->enrol];
 | 
        
           |  |  | 5357 |                 if (!$plugin->allow_unenrol($instance) and !$plugin->allow_unenrol_user($instance, $ue)) {
 | 
        
           |  |  | 5358 |                     continue;
 | 
        
           |  |  | 5359 |                 }
 | 
        
           |  |  | 5360 |   | 
        
           |  |  | 5361 |                 if ($withroleid && count($usersroles[$ue->userid]) > 1) {
 | 
        
           |  |  | 5362 |                     // If we don't remove all roles and user has more than one role, just remove this role.
 | 
        
           |  |  | 5363 |                     role_unassign($withroleid, $ue->userid, $context->id);
 | 
        
           |  |  | 5364 |   | 
        
           |  |  | 5365 |                     unset($usersroles[$ue->userid][$withroleid]);
 | 
        
           |  |  | 5366 |                 } else {
 | 
        
           |  |  | 5367 |                     // If we remove all roles or user has only one role, unenrol user from course.
 | 
        
           |  |  | 5368 |                     $plugin->unenrol_user($instance, $ue->userid);
 | 
        
           |  |  | 5369 |                 }
 | 
        
           |  |  | 5370 |                 $data->unenrolled[$ue->userid] = $ue->userid;
 | 
        
           |  |  | 5371 |             }
 | 
        
           |  |  | 5372 |             $rs->close();
 | 
        
           |  |  | 5373 |         }
 | 
        
           |  |  | 5374 |     }
 | 
        
           |  |  | 5375 |     if (!empty($data->unenrolled)) {
 | 
        
           |  |  | 5376 |         $status[] = array(
 | 
        
           |  |  | 5377 |             'component' => $componentstr,
 | 
        
           | 1326 | ariadna | 5378 |             'item' => get_string('unenrol', 'enrol') . ' (' . count($data->unenrolled) . ')',
 | 
        
           | 1 | efrain | 5379 |             'error' => false
 | 
        
           |  |  | 5380 |         );
 | 
        
           |  |  | 5381 |     }
 | 
        
           |  |  | 5382 |   | 
        
           |  |  | 5383 |     $componentstr = get_string('groups');
 | 
        
           |  |  | 5384 |   | 
        
           |  |  | 5385 |     // Remove all group members.
 | 
        
           |  |  | 5386 |     if (!empty($data->reset_groups_members)) {
 | 
        
           |  |  | 5387 |         groups_delete_group_members($data->courseid);
 | 
        
           |  |  | 5388 |         $status[] = array('component' => $componentstr, 'item' => get_string('removegroupsmembers', 'group'), 'error' => false);
 | 
        
           |  |  | 5389 |     }
 | 
        
           |  |  | 5390 |   | 
        
           |  |  | 5391 |     // Remove all groups.
 | 
        
           |  |  | 5392 |     if (!empty($data->reset_groups_remove)) {
 | 
        
           |  |  | 5393 |         groups_delete_groups($data->courseid, false);
 | 
        
           |  |  | 5394 |         $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroups', 'group'), 'error' => false);
 | 
        
           |  |  | 5395 |     }
 | 
        
           |  |  | 5396 |   | 
        
           |  |  | 5397 |     // Remove all grouping members.
 | 
        
           |  |  | 5398 |     if (!empty($data->reset_groupings_members)) {
 | 
        
           |  |  | 5399 |         groups_delete_groupings_groups($data->courseid, false);
 | 
        
           |  |  | 5400 |         $status[] = array('component' => $componentstr, 'item' => get_string('removegroupingsmembers', 'group'), 'error' => false);
 | 
        
           |  |  | 5401 |     }
 | 
        
           |  |  | 5402 |   | 
        
           |  |  | 5403 |     // Remove all groupings.
 | 
        
           |  |  | 5404 |     if (!empty($data->reset_groupings_remove)) {
 | 
        
           |  |  | 5405 |         groups_delete_groupings($data->courseid, false);
 | 
        
           |  |  | 5406 |         $status[] = array('component' => $componentstr, 'item' => get_string('deleteallgroupings', 'group'), 'error' => false);
 | 
        
           |  |  | 5407 |     }
 | 
        
           |  |  | 5408 |   | 
        
           |  |  | 5409 |     // Look in every instance of every module for data to delete.
 | 
        
           |  |  | 5410 |     $unsupportedmods = array();
 | 
        
           | 1326 | ariadna | 5411 |     if ($allmods = $DB->get_records('modules')) {
 | 
        
           | 1 | efrain | 5412 |         foreach ($allmods as $mod) {
 | 
        
           |  |  | 5413 |             $modname = $mod->name;
 | 
        
           | 1326 | ariadna | 5414 |             $modfile = $CFG->dirroot . '/mod/' . $modname . '/lib.php';
 | 
        
           |  |  | 5415 |             $moddeleteuserdata = $modname . '_reset_userdata';   // Function to delete user data.
 | 
        
           | 1 | efrain | 5416 |             if (file_exists($modfile)) {
 | 
        
           |  |  | 5417 |                 if (!$DB->count_records($modname, array('course' => $data->courseid))) {
 | 
        
           |  |  | 5418 |                     continue; // Skip mods with no instances.
 | 
        
           |  |  | 5419 |                 }
 | 
        
           |  |  | 5420 |                 include_once($modfile);
 | 
        
           |  |  | 5421 |                 if (function_exists($moddeleteuserdata)) {
 | 
        
           |  |  | 5422 |                     $modstatus = $moddeleteuserdata($data);
 | 
        
           |  |  | 5423 |                     if (is_array($modstatus)) {
 | 
        
           |  |  | 5424 |                         $status = array_merge($status, $modstatus);
 | 
        
           |  |  | 5425 |                     } else {
 | 
        
           | 1326 | ariadna | 5426 |                         debugging('Module ' . $modname . ' returned incorrect staus - must be an array!');
 | 
        
           | 1 | efrain | 5427 |                     }
 | 
        
           |  |  | 5428 |                 } else {
 | 
        
           |  |  | 5429 |                     $unsupportedmods[] = $mod;
 | 
        
           |  |  | 5430 |                 }
 | 
        
           |  |  | 5431 |             } else {
 | 
        
           | 1326 | ariadna | 5432 |                 debugging('Missing lib.php in ' . $modname . ' module!');
 | 
        
           | 1 | efrain | 5433 |             }
 | 
        
           |  |  | 5434 |             // Update calendar events for all modules.
 | 
        
           |  |  | 5435 |             course_module_bulk_update_calendar_events($modname, $data->courseid);
 | 
        
           |  |  | 5436 |         }
 | 
        
           |  |  | 5437 |         // Purge the course cache after resetting course start date. MDL-76936
 | 
        
           |  |  | 5438 |         if ($data->timeshift) {
 | 
        
           |  |  | 5439 |             course_modinfo::purge_course_cache($data->courseid);
 | 
        
           |  |  | 5440 |         }
 | 
        
           |  |  | 5441 |     }
 | 
        
           |  |  | 5442 |   | 
        
           |  |  | 5443 |     // Mention unsupported mods.
 | 
        
           |  |  | 5444 |     if (!empty($unsupportedmods)) {
 | 
        
           |  |  | 5445 |         foreach ($unsupportedmods as $mod) {
 | 
        
           |  |  | 5446 |             $status[] = array(
 | 
        
           |  |  | 5447 |                 'component' => get_string('modulenameplural', $mod->name),
 | 
        
           |  |  | 5448 |                 'item' => '',
 | 
        
           |  |  | 5449 |                 'error' => get_string('resetnotimplemented')
 | 
        
           |  |  | 5450 |             );
 | 
        
           |  |  | 5451 |         }
 | 
        
           |  |  | 5452 |     }
 | 
        
           |  |  | 5453 |   | 
        
           |  |  | 5454 |     $componentstr = get_string('gradebook', 'grades');
 | 
        
           |  |  | 5455 |     // Reset gradebook,.
 | 
        
           |  |  | 5456 |     if (!empty($data->reset_gradebook_items)) {
 | 
        
           |  |  | 5457 |         remove_course_grades($data->courseid, false);
 | 
        
           |  |  | 5458 |         grade_grab_course_grades($data->courseid);
 | 
        
           |  |  | 5459 |         grade_regrade_final_grades($data->courseid);
 | 
        
           |  |  | 5460 |         $status[] = array('component' => $componentstr, 'item' => get_string('removeallcourseitems', 'grades'), 'error' => false);
 | 
        
           |  |  | 5461 |     } else if (!empty($data->reset_gradebook_grades)) {
 | 
        
           |  |  | 5462 |         grade_course_reset($data->courseid);
 | 
        
           |  |  | 5463 |         $status[] = array('component' => $componentstr, 'item' => get_string('removeallcoursegrades', 'grades'), 'error' => false);
 | 
        
           |  |  | 5464 |     }
 | 
        
           |  |  | 5465 |     // Reset comments.
 | 
        
           |  |  | 5466 |     if (!empty($data->reset_comments)) {
 | 
        
           | 1326 | ariadna | 5467 |         require_once($CFG->dirroot . '/comment/lib.php');
 | 
        
           | 1 | efrain | 5468 |         comment::reset_course_page_comments($context);
 | 
        
           |  |  | 5469 |     }
 | 
        
           |  |  | 5470 |   | 
        
           |  |  | 5471 |     $event = \core\event\course_reset_ended::create($eventparams);
 | 
        
           |  |  | 5472 |     $event->trigger();
 | 
        
           |  |  | 5473 |   | 
        
           |  |  | 5474 |     return $status;
 | 
        
           |  |  | 5475 | }
 | 
        
           |  |  | 5476 |   | 
        
           |  |  | 5477 | /**
 | 
        
           |  |  | 5478 |  * Generate an email processing address.
 | 
        
           |  |  | 5479 |  *
 | 
        
           |  |  | 5480 |  * @param int $modid
 | 
        
           |  |  | 5481 |  * @param string $modargs
 | 
        
           |  |  | 5482 |  * @return string Returns email processing address
 | 
        
           |  |  | 5483 |  */
 | 
        
           | 1326 | ariadna | 5484 | function generate_email_processing_address($modid, $modargs)
 | 
        
           |  |  | 5485 | {
 | 
        
           | 1 | efrain | 5486 |     global $CFG;
 | 
        
           |  |  | 5487 |   | 
        
           | 1326 | ariadna | 5488 |     $header = $CFG->mailprefix . substr(base64_encode(pack('C', $modid)), 0, 2) . $modargs;
 | 
        
           |  |  | 5489 |     return $header . substr(md5($header . get_site_identifier()), 0, 16) . '@' . $CFG->maildomain;
 | 
        
           | 1 | efrain | 5490 | }
 | 
        
           |  |  | 5491 |   | 
        
           |  |  | 5492 | /**
 | 
        
           |  |  | 5493 |  * ?
 | 
        
           |  |  | 5494 |  *
 | 
        
           |  |  | 5495 |  * @todo Finish documenting this function
 | 
        
           |  |  | 5496 |  *
 | 
        
           |  |  | 5497 |  * @param string $modargs
 | 
        
           |  |  | 5498 |  * @param string $body Currently unused
 | 
        
           |  |  | 5499 |  */
 | 
        
           | 1326 | ariadna | 5500 | function moodle_process_email($modargs, $body)
 | 
        
           |  |  | 5501 | {
 | 
        
           | 1 | efrain | 5502 |     global $DB;
 | 
        
           |  |  | 5503 |   | 
        
           |  |  | 5504 |     // The first char should be an unencoded letter. We'll take this as an action.
 | 
        
           |  |  | 5505 |     switch ($modargs[0]) {
 | 
        
           |  |  | 5506 |         case 'B': { // Bounce.
 | 
        
           | 1326 | ariadna | 5507 |                 list(, $userid) = unpack('V', base64_decode(substr($modargs, 1, 8)));
 | 
        
           |  |  | 5508 |                 if ($user = $DB->get_record("user", array('id' => $userid), "id,email")) {
 | 
        
           |  |  | 5509 |                     // Check the half md5 of their email.
 | 
        
           |  |  | 5510 |                     $md5check = substr(md5($user->email), 0, 16);
 | 
        
           |  |  | 5511 |                     if ($md5check == substr($modargs, -16)) {
 | 
        
           |  |  | 5512 |                         set_bounce_count($user);
 | 
        
           |  |  | 5513 |                     }
 | 
        
           |  |  | 5514 |                     // Else maybe they've already changed it?
 | 
        
           | 1 | efrain | 5515 |                 }
 | 
        
           |  |  | 5516 |             }
 | 
        
           | 1326 | ariadna | 5517 |             break;
 | 
        
           |  |  | 5518 |             // Maybe more later?
 | 
        
           | 1 | efrain | 5519 |     }
 | 
        
           |  |  | 5520 | }
 | 
        
           |  |  | 5521 |   | 
        
           |  |  | 5522 | // CORRESPONDENCE.
 | 
        
           |  |  | 5523 |   | 
        
           |  |  | 5524 | /**
 | 
        
           |  |  | 5525 |  * Get mailer instance, enable buffering, flush buffer or disable buffering.
 | 
        
           |  |  | 5526 |  *
 | 
        
           |  |  | 5527 |  * @param string $action 'get', 'buffer', 'close' or 'flush'
 | 
        
           |  |  | 5528 |  * @return moodle_phpmailer|null mailer instance if 'get' used or nothing
 | 
        
           |  |  | 5529 |  */
 | 
        
           | 1326 | ariadna | 5530 | function get_mailer($action = 'get')
 | 
        
           |  |  | 5531 | {
 | 
        
           | 1 | efrain | 5532 |     global $CFG;
 | 
        
           |  |  | 5533 |   | 
        
           |  |  | 5534 |     /** @var moodle_phpmailer $mailer */
 | 
        
           |  |  | 5535 |     static $mailer  = null;
 | 
        
           |  |  | 5536 |     static $counter = 0;
 | 
        
           |  |  | 5537 |   | 
        
           |  |  | 5538 |     if (!isset($CFG->smtpmaxbulk)) {
 | 
        
           |  |  | 5539 |         $CFG->smtpmaxbulk = 1;
 | 
        
           |  |  | 5540 |     }
 | 
        
           |  |  | 5541 |   | 
        
           |  |  | 5542 |     if ($action == 'get') {
 | 
        
           |  |  | 5543 |         $prevkeepalive = false;
 | 
        
           |  |  | 5544 |   | 
        
           |  |  | 5545 |         if (isset($mailer) and $mailer->Mailer == 'smtp') {
 | 
        
           |  |  | 5546 |             if ($counter < $CFG->smtpmaxbulk and !$mailer->isError()) {
 | 
        
           |  |  | 5547 |                 $counter++;
 | 
        
           |  |  | 5548 |                 // Reset the mailer.
 | 
        
           |  |  | 5549 |                 $mailer->Priority         = 3;
 | 
        
           |  |  | 5550 |                 $mailer->CharSet          = 'UTF-8'; // Our default.
 | 
        
           |  |  | 5551 |                 $mailer->ContentType      = "text/plain";
 | 
        
           |  |  | 5552 |                 $mailer->Encoding         = "8bit";
 | 
        
           |  |  | 5553 |                 $mailer->From             = "root@localhost";
 | 
        
           |  |  | 5554 |                 $mailer->FromName         = "Root User";
 | 
        
           |  |  | 5555 |                 $mailer->Sender           = "";
 | 
        
           |  |  | 5556 |                 $mailer->Subject          = "";
 | 
        
           |  |  | 5557 |                 $mailer->Body             = "";
 | 
        
           |  |  | 5558 |                 $mailer->AltBody          = "";
 | 
        
           |  |  | 5559 |                 $mailer->ConfirmReadingTo = "";
 | 
        
           |  |  | 5560 |   | 
        
           |  |  | 5561 |                 $mailer->clearAllRecipients();
 | 
        
           |  |  | 5562 |                 $mailer->clearReplyTos();
 | 
        
           |  |  | 5563 |                 $mailer->clearAttachments();
 | 
        
           |  |  | 5564 |                 $mailer->clearCustomHeaders();
 | 
        
           |  |  | 5565 |                 return $mailer;
 | 
        
           |  |  | 5566 |             }
 | 
        
           |  |  | 5567 |   | 
        
           |  |  | 5568 |             $prevkeepalive = $mailer->SMTPKeepAlive;
 | 
        
           |  |  | 5569 |             get_mailer('flush');
 | 
        
           |  |  | 5570 |         }
 | 
        
           |  |  | 5571 |   | 
        
           | 1326 | ariadna | 5572 |         require_once($CFG->libdir . '/phpmailer/moodle_phpmailer.php');
 | 
        
           | 1 | efrain | 5573 |         $mailer = new moodle_phpmailer();
 | 
        
           |  |  | 5574 |   | 
        
           |  |  | 5575 |         $counter = 1;
 | 
        
           |  |  | 5576 |   | 
        
           |  |  | 5577 |         if ($CFG->smtphosts == 'qmail') {
 | 
        
           |  |  | 5578 |             // Use Qmail system.
 | 
        
           |  |  | 5579 |             $mailer->isQmail();
 | 
        
           |  |  | 5580 |         } else if (empty($CFG->smtphosts)) {
 | 
        
           |  |  | 5581 |             // Use PHP mail() = sendmail.
 | 
        
           |  |  | 5582 |             $mailer->isMail();
 | 
        
           |  |  | 5583 |         } else {
 | 
        
           |  |  | 5584 |             // Use SMTP directly.
 | 
        
           |  |  | 5585 |             $mailer->isSMTP();
 | 
        
           |  |  | 5586 |             if (!empty($CFG->debugsmtp) && (!empty($CFG->debugdeveloper))) {
 | 
        
           |  |  | 5587 |                 $mailer->SMTPDebug = 3;
 | 
        
           |  |  | 5588 |             }
 | 
        
           |  |  | 5589 |             // Specify main and backup servers.
 | 
        
           |  |  | 5590 |             $mailer->Host          = $CFG->smtphosts;
 | 
        
           |  |  | 5591 |             // Specify secure connection protocol.
 | 
        
           |  |  | 5592 |             $mailer->SMTPSecure    = $CFG->smtpsecure;
 | 
        
           |  |  | 5593 |             // Use previous keepalive.
 | 
        
           |  |  | 5594 |             $mailer->SMTPKeepAlive = $prevkeepalive;
 | 
        
           |  |  | 5595 |   | 
        
           |  |  | 5596 |             if ($CFG->smtpuser) {
 | 
        
           |  |  | 5597 |                 // Use SMTP authentication.
 | 
        
           |  |  | 5598 |                 $mailer->SMTPAuth = true;
 | 
        
           |  |  | 5599 |                 $mailer->Username = $CFG->smtpuser;
 | 
        
           |  |  | 5600 |                 $mailer->Password = $CFG->smtppass;
 | 
        
           |  |  | 5601 |             }
 | 
        
           |  |  | 5602 |         }
 | 
        
           |  |  | 5603 |   | 
        
           |  |  | 5604 |         return $mailer;
 | 
        
           |  |  | 5605 |     }
 | 
        
           |  |  | 5606 |   | 
        
           |  |  | 5607 |     $nothing = null;
 | 
        
           |  |  | 5608 |   | 
        
           |  |  | 5609 |     // Keep smtp session open after sending.
 | 
        
           |  |  | 5610 |     if ($action == 'buffer') {
 | 
        
           |  |  | 5611 |         if (!empty($CFG->smtpmaxbulk)) {
 | 
        
           |  |  | 5612 |             get_mailer('flush');
 | 
        
           |  |  | 5613 |             $m = get_mailer();
 | 
        
           |  |  | 5614 |             if ($m->Mailer == 'smtp') {
 | 
        
           |  |  | 5615 |                 $m->SMTPKeepAlive = true;
 | 
        
           |  |  | 5616 |             }
 | 
        
           |  |  | 5617 |         }
 | 
        
           |  |  | 5618 |         return $nothing;
 | 
        
           |  |  | 5619 |     }
 | 
        
           |  |  | 5620 |   | 
        
           |  |  | 5621 |     // Close smtp session, but continue buffering.
 | 
        
           |  |  | 5622 |     if ($action == 'flush') {
 | 
        
           |  |  | 5623 |         if (isset($mailer) and $mailer->Mailer == 'smtp') {
 | 
        
           |  |  | 5624 |             if (!empty($mailer->SMTPDebug)) {
 | 
        
           | 1326 | ariadna | 5625 |                 echo '<pre>' . "\n";
 | 
        
           | 1 | efrain | 5626 |             }
 | 
        
           |  |  | 5627 |             $mailer->SmtpClose();
 | 
        
           |  |  | 5628 |             if (!empty($mailer->SMTPDebug)) {
 | 
        
           |  |  | 5629 |                 echo '</pre>';
 | 
        
           |  |  | 5630 |             }
 | 
        
           |  |  | 5631 |         }
 | 
        
           |  |  | 5632 |         return $nothing;
 | 
        
           |  |  | 5633 |     }
 | 
        
           |  |  | 5634 |   | 
        
           |  |  | 5635 |     // Close smtp session, do not buffer anymore.
 | 
        
           |  |  | 5636 |     if ($action == 'close') {
 | 
        
           |  |  | 5637 |         if (isset($mailer) and $mailer->Mailer == 'smtp') {
 | 
        
           |  |  | 5638 |             get_mailer('flush');
 | 
        
           |  |  | 5639 |             $mailer->SMTPKeepAlive = false;
 | 
        
           |  |  | 5640 |         }
 | 
        
           |  |  | 5641 |         $mailer = null; // Better force new instance.
 | 
        
           |  |  | 5642 |         return $nothing;
 | 
        
           |  |  | 5643 |     }
 | 
        
           |  |  | 5644 | }
 | 
        
           |  |  | 5645 |   | 
        
           |  |  | 5646 | /**
 | 
        
           |  |  | 5647 |  * A helper function to test for email diversion
 | 
        
           |  |  | 5648 |  *
 | 
        
           |  |  | 5649 |  * @param string $email
 | 
        
           |  |  | 5650 |  * @return bool Returns true if the email should be diverted
 | 
        
           |  |  | 5651 |  */
 | 
        
           | 1326 | ariadna | 5652 | function email_should_be_diverted($email)
 | 
        
           |  |  | 5653 | {
 | 
        
           | 1 | efrain | 5654 |     global $CFG;
 | 
        
           |  |  | 5655 |   | 
        
           |  |  | 5656 |     if (empty($CFG->divertallemailsto)) {
 | 
        
           |  |  | 5657 |         return false;
 | 
        
           |  |  | 5658 |     }
 | 
        
           |  |  | 5659 |   | 
        
           |  |  | 5660 |     if (empty($CFG->divertallemailsexcept)) {
 | 
        
           |  |  | 5661 |         return true;
 | 
        
           |  |  | 5662 |     }
 | 
        
           |  |  | 5663 |   | 
        
           |  |  | 5664 |     $patterns = array_map('trim', preg_split("/[\s,]+/", $CFG->divertallemailsexcept, -1, PREG_SPLIT_NO_EMPTY));
 | 
        
           |  |  | 5665 |     foreach ($patterns as $pattern) {
 | 
        
           |  |  | 5666 |         if (preg_match("/{$pattern}/i", $email)) {
 | 
        
           |  |  | 5667 |             return false;
 | 
        
           |  |  | 5668 |         }
 | 
        
           |  |  | 5669 |     }
 | 
        
           |  |  | 5670 |   | 
        
           |  |  | 5671 |     return true;
 | 
        
           |  |  | 5672 | }
 | 
        
           |  |  | 5673 |   | 
        
           |  |  | 5674 | /**
 | 
        
           |  |  | 5675 |  * Generate a unique email Message-ID using the moodle domain and install path
 | 
        
           |  |  | 5676 |  *
 | 
        
           |  |  | 5677 |  * @param string $localpart An optional unique message id prefix.
 | 
        
           |  |  | 5678 |  * @return string The formatted ID ready for appending to the email headers.
 | 
        
           |  |  | 5679 |  */
 | 
        
           | 1326 | ariadna | 5680 | function generate_email_messageid($localpart = null)
 | 
        
           |  |  | 5681 | {
 | 
        
           | 1 | efrain | 5682 |     global $CFG;
 | 
        
           |  |  | 5683 |   | 
        
           |  |  | 5684 |     $urlinfo = parse_url($CFG->wwwroot);
 | 
        
           |  |  | 5685 |     $base = '@' . $urlinfo['host'];
 | 
        
           |  |  | 5686 |   | 
        
           |  |  | 5687 |     // If multiple moodles are on the same domain we want to tell them
 | 
        
           |  |  | 5688 |     // apart so we add the install path to the local part. This means
 | 
        
           |  |  | 5689 |     // that the id local part should never contain a / character so
 | 
        
           |  |  | 5690 |     // we can correctly parse the id to reassemble the wwwroot.
 | 
        
           |  |  | 5691 |     if (isset($urlinfo['path'])) {
 | 
        
           |  |  | 5692 |         $base = $urlinfo['path'] . $base;
 | 
        
           |  |  | 5693 |     }
 | 
        
           |  |  | 5694 |   | 
        
           |  |  | 5695 |     if (empty($localpart)) {
 | 
        
           |  |  | 5696 |         $localpart = uniqid('', true);
 | 
        
           |  |  | 5697 |     }
 | 
        
           |  |  | 5698 |   | 
        
           |  |  | 5699 |     // Because we may have an option /installpath suffix to the local part
 | 
        
           |  |  | 5700 |     // of the id we need to escape any / chars which are in the $localpart.
 | 
        
           |  |  | 5701 |     $localpart = str_replace('/', '%2F', $localpart);
 | 
        
           |  |  | 5702 |   | 
        
           |  |  | 5703 |     return '<' . $localpart . $base . '>';
 | 
        
           |  |  | 5704 | }
 | 
        
           |  |  | 5705 |   | 
        
           |  |  | 5706 | /**
 | 
        
           |  |  | 5707 |  * Send an email to a specified user
 | 
        
           |  |  | 5708 |  *
 | 
        
           |  |  | 5709 |  * @param stdClass $user  A {@link $USER} object
 | 
        
           |  |  | 5710 |  * @param stdClass $from A {@link $USER} object
 | 
        
           |  |  | 5711 |  * @param string $subject plain text subject line of the email
 | 
        
           |  |  | 5712 |  * @param string $messagetext plain text version of the message
 | 
        
           |  |  | 5713 |  * @param string $messagehtml complete html version of the message (optional)
 | 
        
           |  |  | 5714 |  * @param string $attachment a file on the filesystem, either relative to $CFG->dataroot or a full path to a file in one of
 | 
        
           |  |  | 5715 |  *          the following directories: $CFG->cachedir, $CFG->dataroot, $CFG->dirroot, $CFG->localcachedir, $CFG->tempdir
 | 
        
           |  |  | 5716 |  * @param string $attachname the name of the file (extension indicates MIME)
 | 
        
           |  |  | 5717 |  * @param bool $usetrueaddress determines whether $from email address should
 | 
        
           |  |  | 5718 |  *          be sent out. Will be overruled by user profile setting for maildisplay
 | 
        
           |  |  | 5719 |  * @param string $replyto Email address to reply to
 | 
        
           |  |  | 5720 |  * @param string $replytoname Name of reply to recipient
 | 
        
           |  |  | 5721 |  * @param int $wordwrapwidth custom word wrap width, default 79
 | 
        
           |  |  | 5722 |  * @return bool Returns true if mail was sent OK and false if there was an error.
 | 
        
           |  |  | 5723 |  */
 | 
        
           | 1326 | ariadna | 5724 | function email_to_user(
 | 
        
           |  |  | 5725 |     $user,
 | 
        
           |  |  | 5726 |     $from,
 | 
        
           |  |  | 5727 |     $subject,
 | 
        
           |  |  | 5728 |     $messagetext,
 | 
        
           |  |  | 5729 |     $messagehtml = '',
 | 
        
           |  |  | 5730 |     $attachment = '',
 | 
        
           |  |  | 5731 |     $attachname = '',
 | 
        
           |  |  | 5732 |     $usetrueaddress = true,
 | 
        
           |  |  | 5733 |     $replyto = '',
 | 
        
           |  |  | 5734 |     $replytoname = '',
 | 
        
           |  |  | 5735 |     $wordwrapwidth = 79
 | 
        
           |  |  | 5736 | ) {
 | 
        
           | 1 | efrain | 5737 |   | 
        
           |  |  | 5738 |     global $CFG, $PAGE, $SITE;
 | 
        
           |  |  | 5739 |   | 
        
           |  |  | 5740 |     if (empty($user) or empty($user->id)) {
 | 
        
           |  |  | 5741 |         debugging('Can not send email to null user', DEBUG_DEVELOPER);
 | 
        
           |  |  | 5742 |         return false;
 | 
        
           |  |  | 5743 |     }
 | 
        
           |  |  | 5744 |   | 
        
           |  |  | 5745 |     if (empty($user->email)) {
 | 
        
           | 1326 | ariadna | 5746 |         debugging('Can not send email to user without email: ' . $user->id, DEBUG_DEVELOPER);
 | 
        
           | 1 | efrain | 5747 |         return false;
 | 
        
           |  |  | 5748 |     }
 | 
        
           |  |  | 5749 |   | 
        
           |  |  | 5750 |     if (!empty($user->deleted)) {
 | 
        
           | 1326 | ariadna | 5751 |         debugging('Can not send email to deleted user: ' . $user->id, DEBUG_DEVELOPER);
 | 
        
           | 1 | efrain | 5752 |         return false;
 | 
        
           |  |  | 5753 |     }
 | 
        
           |  |  | 5754 |   | 
        
           |  |  | 5755 |     if (defined('BEHAT_SITE_RUNNING')) {
 | 
        
           |  |  | 5756 |         // Fake email sending in behat.
 | 
        
           |  |  | 5757 |         return true;
 | 
        
           |  |  | 5758 |     }
 | 
        
           |  |  | 5759 |   | 
        
           |  |  | 5760 |     if (!empty($CFG->noemailever)) {
 | 
        
           |  |  | 5761 |         // Hidden setting for development sites, set in config.php if needed.
 | 
        
           |  |  | 5762 |         debugging('Not sending email due to $CFG->noemailever config setting', DEBUG_NORMAL);
 | 
        
           |  |  | 5763 |         return true;
 | 
        
           |  |  | 5764 |     }
 | 
        
           |  |  | 5765 |   | 
        
           |  |  | 5766 |     if (email_should_be_diverted($user->email)) {
 | 
        
           |  |  | 5767 |         $subject = "[DIVERTED {$user->email}] $subject";
 | 
        
           | 1326 | ariadna | 5768 |         $user = clone ($user);
 | 
        
           | 1 | efrain | 5769 |         $user->email = $CFG->divertallemailsto;
 | 
        
           |  |  | 5770 |     }
 | 
        
           |  |  | 5771 |   | 
        
           |  |  | 5772 |     // Skip mail to suspended users.
 | 
        
           | 1326 | ariadna | 5773 |     if ((isset($user->auth) && $user->auth == 'nologin') or (isset($user->suspended) && $user->suspended)) {
 | 
        
           | 1 | efrain | 5774 |         return true;
 | 
        
           |  |  | 5775 |     }
 | 
        
           |  |  | 5776 |   | 
        
           |  |  | 5777 |     if (!validate_email($user->email)) {
 | 
        
           |  |  | 5778 |         // We can not send emails to invalid addresses - it might create security issue or confuse the mailer.
 | 
        
           | 1326 | ariadna | 5779 |         debugging("email_to_user: User $user->id (" . fullname($user) . ") email ($user->email) is invalid! Not sending.");
 | 
        
           | 1 | efrain | 5780 |         return false;
 | 
        
           |  |  | 5781 |     }
 | 
        
           |  |  | 5782 |   | 
        
           |  |  | 5783 |     if (over_bounce_threshold($user)) {
 | 
        
           | 1326 | ariadna | 5784 |         debugging("email_to_user: User $user->id (" . fullname($user) . ") is over bounce threshold! Not sending.");
 | 
        
           | 1 | efrain | 5785 |         return false;
 | 
        
           |  |  | 5786 |     }
 | 
        
           |  |  | 5787 |   | 
        
           |  |  | 5788 |     // TLD .invalid  is specifically reserved for invalid domain names.
 | 
        
           |  |  | 5789 |     // For More information, see {@link http://tools.ietf.org/html/rfc2606#section-2}.
 | 
        
           |  |  | 5790 |     if (substr($user->email, -8) == '.invalid') {
 | 
        
           | 1326 | ariadna | 5791 |         debugging("email_to_user: User $user->id (" . fullname($user) . ") email domain ($user->email) is invalid! Not sending.");
 | 
        
           | 1 | efrain | 5792 |         return true; // This is not an error.
 | 
        
           |  |  | 5793 |     }
 | 
        
           |  |  | 5794 |   | 
        
           |  |  | 5795 |     // If the user is a remote mnet user, parse the email text for URL to the
 | 
        
           |  |  | 5796 |     // wwwroot and modify the url to direct the user's browser to login at their
 | 
        
           |  |  | 5797 |     // home site (identity provider - idp) before hitting the link itself.
 | 
        
           |  |  | 5798 |     if (is_mnet_remote_user($user)) {
 | 
        
           | 1326 | ariadna | 5799 |         require_once($CFG->dirroot . '/mnet/lib.php');
 | 
        
           | 1 | efrain | 5800 |   | 
        
           |  |  | 5801 |         $jumpurl = mnet_get_idp_jump_url($user);
 | 
        
           |  |  | 5802 |         $callback = partial('mnet_sso_apply_indirection', $jumpurl);
 | 
        
           |  |  | 5803 |   | 
        
           | 1326 | ariadna | 5804 |         $messagetext = preg_replace_callback(
 | 
        
           |  |  | 5805 |             "%($CFG->wwwroot[^[:space:]]*)%",
 | 
        
           |  |  | 5806 |             $callback,
 | 
        
           |  |  | 5807 |             $messagetext
 | 
        
           |  |  | 5808 |         );
 | 
        
           |  |  | 5809 |         $messagehtml = preg_replace_callback(
 | 
        
           |  |  | 5810 |             "%href=[\"'`]($CFG->wwwroot[\w_:\?=#&@/;.~-]*)[\"'`]%",
 | 
        
           |  |  | 5811 |             $callback,
 | 
        
           |  |  | 5812 |             $messagehtml
 | 
        
           |  |  | 5813 |         );
 | 
        
           | 1 | efrain | 5814 |     }
 | 
        
           |  |  | 5815 |     $mail = get_mailer();
 | 
        
           |  |  | 5816 |   | 
        
           |  |  | 5817 |     if (!empty($mail->SMTPDebug)) {
 | 
        
           |  |  | 5818 |         echo '<pre>' . "\n";
 | 
        
           |  |  | 5819 |     }
 | 
        
           |  |  | 5820 |   | 
        
           |  |  | 5821 |     $temprecipients = array();
 | 
        
           |  |  | 5822 |     $tempreplyto = array();
 | 
        
           |  |  | 5823 |   | 
        
           |  |  | 5824 |     // Make sure that we fall back onto some reasonable no-reply address.
 | 
        
           |  |  | 5825 |     $noreplyaddressdefault = 'noreply@' . get_host_from_url($CFG->wwwroot);
 | 
        
           |  |  | 5826 |     $noreplyaddress = empty($CFG->noreplyaddress) ? $noreplyaddressdefault : $CFG->noreplyaddress;
 | 
        
           |  |  | 5827 |   | 
        
           |  |  | 5828 |     if (!validate_email($noreplyaddress)) {
 | 
        
           | 1326 | ariadna | 5829 |         debugging('email_to_user: Invalid noreply-email ' . s($noreplyaddress));
 | 
        
           | 1 | efrain | 5830 |         $noreplyaddress = $noreplyaddressdefault;
 | 
        
           |  |  | 5831 |     }
 | 
        
           |  |  | 5832 |   | 
        
           |  |  | 5833 |     // Make up an email address for handling bounces.
 | 
        
           |  |  | 5834 |     if (!empty($CFG->handlebounces)) {
 | 
        
           | 1326 | ariadna | 5835 |         $modargs = 'B' . base64_encode(pack('V', $user->id)) . substr(md5($user->email), 0, 16);
 | 
        
           | 1 | efrain | 5836 |         $mail->Sender = generate_email_processing_address(0, $modargs);
 | 
        
           |  |  | 5837 |     } else {
 | 
        
           |  |  | 5838 |         $mail->Sender = $noreplyaddress;
 | 
        
           |  |  | 5839 |     }
 | 
        
           |  |  | 5840 |   | 
        
           |  |  | 5841 |     // Make sure that the explicit replyto is valid, fall back to the implicit one.
 | 
        
           |  |  | 5842 |     if (!empty($replyto) && !validate_email($replyto)) {
 | 
        
           | 1326 | ariadna | 5843 |         debugging('email_to_user: Invalid replyto-email ' . s($replyto));
 | 
        
           | 1 | efrain | 5844 |         $replyto = $noreplyaddress;
 | 
        
           |  |  | 5845 |     }
 | 
        
           |  |  | 5846 |   | 
        
           |  |  | 5847 |     if (is_string($from)) { // So we can pass whatever we want if there is need.
 | 
        
           |  |  | 5848 |         $mail->From     = $noreplyaddress;
 | 
        
           |  |  | 5849 |         $mail->FromName = $from;
 | 
        
           | 1326 | ariadna | 5850 |         // Check if using the true address is true, and the email is in the list of allowed domains for sending email,
 | 
        
           |  |  | 5851 |         // and that the senders email setting is either displayed to everyone, or display to only other users that are enrolled
 | 
        
           |  |  | 5852 |         // in a course with the sender.
 | 
        
           | 1 | efrain | 5853 |     } else if ($usetrueaddress && can_send_from_real_email_address($from, $user)) {
 | 
        
           |  |  | 5854 |         if (!validate_email($from->email)) {
 | 
        
           | 1326 | ariadna | 5855 |             debugging('email_to_user: Invalid from-email ' . s($from->email) . ' - not sending');
 | 
        
           | 1 | efrain | 5856 |             // Better not to use $noreplyaddress in this case.
 | 
        
           |  |  | 5857 |             return false;
 | 
        
           |  |  | 5858 |         }
 | 
        
           |  |  | 5859 |         $mail->From = $from->email;
 | 
        
           |  |  | 5860 |         $fromdetails = new stdClass();
 | 
        
           |  |  | 5861 |         $fromdetails->name = fullname($from);
 | 
        
           |  |  | 5862 |         $fromdetails->url = preg_replace('#^https?://#', '', $CFG->wwwroot);
 | 
        
           |  |  | 5863 |         $fromdetails->siteshortname = format_string($SITE->shortname);
 | 
        
           |  |  | 5864 |         $fromstring = $fromdetails->name;
 | 
        
           |  |  | 5865 |         if ($CFG->emailfromvia == EMAIL_VIA_ALWAYS) {
 | 
        
           |  |  | 5866 |             $fromstring = get_string('emailvia', 'core', $fromdetails);
 | 
        
           |  |  | 5867 |         }
 | 
        
           |  |  | 5868 |         $mail->FromName = $fromstring;
 | 
        
           |  |  | 5869 |         if (empty($replyto)) {
 | 
        
           |  |  | 5870 |             $tempreplyto[] = array($from->email, fullname($from));
 | 
        
           |  |  | 5871 |         }
 | 
        
           |  |  | 5872 |     } else {
 | 
        
           |  |  | 5873 |         $mail->From = $noreplyaddress;
 | 
        
           |  |  | 5874 |         $fromdetails = new stdClass();
 | 
        
           |  |  | 5875 |         $fromdetails->name = fullname($from);
 | 
        
           |  |  | 5876 |         $fromdetails->url = preg_replace('#^https?://#', '', $CFG->wwwroot);
 | 
        
           |  |  | 5877 |         $fromdetails->siteshortname = format_string($SITE->shortname);
 | 
        
           |  |  | 5878 |         $fromstring = $fromdetails->name;
 | 
        
           |  |  | 5879 |         if ($CFG->emailfromvia != EMAIL_VIA_NEVER) {
 | 
        
           |  |  | 5880 |             $fromstring = get_string('emailvia', 'core', $fromdetails);
 | 
        
           |  |  | 5881 |         }
 | 
        
           |  |  | 5882 |         $mail->FromName = $fromstring;
 | 
        
           |  |  | 5883 |         if (empty($replyto)) {
 | 
        
           |  |  | 5884 |             $tempreplyto[] = array($noreplyaddress, get_string('noreplyname'));
 | 
        
           |  |  | 5885 |         }
 | 
        
           |  |  | 5886 |     }
 | 
        
           |  |  | 5887 |   | 
        
           |  |  | 5888 |     if (!empty($replyto)) {
 | 
        
           |  |  | 5889 |         $tempreplyto[] = array($replyto, $replytoname);
 | 
        
           |  |  | 5890 |     }
 | 
        
           |  |  | 5891 |   | 
        
           |  |  | 5892 |     $temprecipients[] = array($user->email, fullname($user));
 | 
        
           |  |  | 5893 |   | 
        
           |  |  | 5894 |     // Set word wrap.
 | 
        
           |  |  | 5895 |     $mail->WordWrap = $wordwrapwidth;
 | 
        
           |  |  | 5896 |   | 
        
           |  |  | 5897 |     if (!empty($from->customheaders)) {
 | 
        
           |  |  | 5898 |         // Add custom headers.
 | 
        
           |  |  | 5899 |         if (is_array($from->customheaders)) {
 | 
        
           |  |  | 5900 |             foreach ($from->customheaders as $customheader) {
 | 
        
           |  |  | 5901 |                 $mail->addCustomHeader($customheader);
 | 
        
           |  |  | 5902 |             }
 | 
        
           |  |  | 5903 |         } else {
 | 
        
           |  |  | 5904 |             $mail->addCustomHeader($from->customheaders);
 | 
        
           |  |  | 5905 |         }
 | 
        
           |  |  | 5906 |     }
 | 
        
           |  |  | 5907 |   | 
        
           |  |  | 5908 |     // If the X-PHP-Originating-Script email header is on then also add an additional
 | 
        
           |  |  | 5909 |     // header with details of where exactly in moodle the email was triggered from,
 | 
        
           |  |  | 5910 |     // either a call to message_send() or to email_to_user().
 | 
        
           |  |  | 5911 |     if (ini_get('mail.add_x_header')) {
 | 
        
           |  |  | 5912 |   | 
        
           |  |  | 5913 |         $stack = debug_backtrace(false);
 | 
        
           |  |  | 5914 |         $origin = $stack[0];
 | 
        
           |  |  | 5915 |   | 
        
           |  |  | 5916 |         foreach ($stack as $depth => $call) {
 | 
        
           |  |  | 5917 |             if ($call['function'] == 'message_send') {
 | 
        
           |  |  | 5918 |                 $origin = $call;
 | 
        
           |  |  | 5919 |             }
 | 
        
           |  |  | 5920 |         }
 | 
        
           |  |  | 5921 |   | 
        
           |  |  | 5922 |         $originheader = $CFG->wwwroot . ' => ' . gethostname() . ':'
 | 
        
           | 1326 | ariadna | 5923 |             . str_replace($CFG->dirroot . '/', '', $origin['file']) . ':' . $origin['line'];
 | 
        
           | 1 | efrain | 5924 |         $mail->addCustomHeader('X-Moodle-Originating-Script: ' . $originheader);
 | 
        
           |  |  | 5925 |     }
 | 
        
           |  |  | 5926 |   | 
        
           |  |  | 5927 |     if (!empty($CFG->emailheaders)) {
 | 
        
           |  |  | 5928 |         $headers = array_map('trim', explode("\n", $CFG->emailheaders));
 | 
        
           |  |  | 5929 |         foreach ($headers as $header) {
 | 
        
           |  |  | 5930 |             if (!empty($header)) {
 | 
        
           |  |  | 5931 |                 $mail->addCustomHeader($header);
 | 
        
           |  |  | 5932 |             }
 | 
        
           |  |  | 5933 |         }
 | 
        
           |  |  | 5934 |     }
 | 
        
           |  |  | 5935 |   | 
        
           |  |  | 5936 |     if (!empty($from->priority)) {
 | 
        
           |  |  | 5937 |         $mail->Priority = $from->priority;
 | 
        
           |  |  | 5938 |     }
 | 
        
           |  |  | 5939 |   | 
        
           |  |  | 5940 |     $renderer = $PAGE->get_renderer('core');
 | 
        
           |  |  | 5941 |     $context = array(
 | 
        
           |  |  | 5942 |         'sitefullname' => $SITE->fullname,
 | 
        
           |  |  | 5943 |         'siteshortname' => $SITE->shortname,
 | 
        
           |  |  | 5944 |         'sitewwwroot' => $CFG->wwwroot,
 | 
        
           |  |  | 5945 |         'subject' => $subject,
 | 
        
           |  |  | 5946 |         'prefix' => $CFG->emailsubjectprefix,
 | 
        
           |  |  | 5947 |         'to' => $user->email,
 | 
        
           |  |  | 5948 |         'toname' => fullname($user),
 | 
        
           |  |  | 5949 |         'from' => $mail->From,
 | 
        
           |  |  | 5950 |         'fromname' => $mail->FromName,
 | 
        
           |  |  | 5951 |     );
 | 
        
           |  |  | 5952 |     if (!empty($tempreplyto[0])) {
 | 
        
           |  |  | 5953 |         $context['replyto'] = $tempreplyto[0][0];
 | 
        
           |  |  | 5954 |         $context['replytoname'] = $tempreplyto[0][1];
 | 
        
           |  |  | 5955 |     }
 | 
        
           |  |  | 5956 |     if ($user->id > 0) {
 | 
        
           |  |  | 5957 |         $context['touserid'] = $user->id;
 | 
        
           |  |  | 5958 |         $context['tousername'] = $user->username;
 | 
        
           |  |  | 5959 |     }
 | 
        
           |  |  | 5960 |   | 
        
           |  |  | 5961 |     if (!empty($user->mailformat) && $user->mailformat == 1) {
 | 
        
           |  |  | 5962 |         // Only process html templates if the user preferences allow html email.
 | 
        
           |  |  | 5963 |   | 
        
           |  |  | 5964 |         if (!$messagehtml) {
 | 
        
           |  |  | 5965 |             // If no html has been given, BUT there is an html wrapping template then
 | 
        
           |  |  | 5966 |             // auto convert the text to html and then wrap it.
 | 
        
           |  |  | 5967 |             $messagehtml = trim(text_to_html($messagetext));
 | 
        
           |  |  | 5968 |         }
 | 
        
           |  |  | 5969 |         $context['body'] = $messagehtml;
 | 
        
           |  |  | 5970 |         $messagehtml = $renderer->render_from_template('core/email_html', $context);
 | 
        
           |  |  | 5971 |     }
 | 
        
           |  |  | 5972 |   | 
        
           |  |  | 5973 |     $context['body'] = html_to_text(nl2br($messagetext));
 | 
        
           | 1361 | ariadna | 5974 |     $mail->Subject = $renderer->render_from_template('core/email_subject', $context);
 | 
        
           |  |  | 5975 |     $mail->FromName = $renderer->render_from_template('core/email_fromname', $context);
 | 
        
           | 1 | efrain | 5976 |     $messagetext = $renderer->render_from_template('core/email_text', $context);
 | 
        
           |  |  | 5977 |   | 
        
           |  |  | 5978 |     // Autogenerate a MessageID if it's missing.
 | 
        
           |  |  | 5979 |     if (empty($mail->MessageID)) {
 | 
        
           |  |  | 5980 |         $mail->MessageID = generate_email_messageid();
 | 
        
           |  |  | 5981 |     }
 | 
        
           |  |  | 5982 |   | 
        
           |  |  | 5983 |     if ($messagehtml && !empty($user->mailformat) && $user->mailformat == 1) {
 | 
        
           | 1334 | ariadna | 5984 |         global $CFG; // Necesario para usar $CFG->wwwroot
 | 
        
           | 1326 | ariadna | 5985 |   | 
        
           | 1334 | ariadna | 5986 |         // Construir URLs de imágenes basadas en el dominio del sitio
 | 
        
           |  |  | 5987 |         $header_image_url = $CFG->wwwroot . '/theme/universe_child/pix/logo-horizontal-cesa.png';
 | 
        
           |  |  | 5988 |         $footer_image_url = $CFG->wwwroot . '/theme/universe_child/pix/email-footer.png';
 | 
        
           | 1333 | ariadna | 5989 |   | 
        
           | 1358 | ariadna | 5990 |         $header_html = "
 | 
        
           | 1359 | ariadna | 5991 |             <div style=\"text-align:left; margin-bottom:20px;\">
 | 
        
           |  |  | 5992 |                 <h1 style=\"margin: 0; font-size: 24px; font-weight: bold;\">ONROOM</h1>
 | 
        
           | 1358 | ariadna | 5993 |             </div>";
 | 
        
           | 1326 | ariadna | 5994 |   | 
        
           | 1358 | ariadna | 5995 |         $footer_html = "
 | 
        
           |  |  | 5996 |             <div style=\"text-align:center; margin-top:40px;\">
 | 
        
           | 1359 | ariadna | 5997 |                 <p style=\"margin: 0; font-size: 16px;\">Construyendo Futuro - Desarrollando Líderes y Personas</p>
 | 
        
           | 1358 | ariadna | 5998 |             </div>";
 | 
        
           | 1326 | ariadna | 5999 |   | 
        
           | 1334 | ariadna | 6000 |         // Unir todo: Header + Contenido original + Footer
 | 
        
           | 1360 | ariadna | 6001 |         $full_html_message = $messagehtml;
 | 
        
           | 1333 | ariadna | 6002 |   | 
        
           | 1327 | ariadna | 6003 |         $mail->isHTML(true);
 | 
        
           |  |  | 6004 |         $mail->Encoding = 'quoted-printable';
 | 
        
           | 1334 | ariadna | 6005 |         $mail->Body = $full_html_message; // Ahora enviamos Header + Contenido + Footer
 | 
        
           | 1360 | ariadna | 6006 |         $mail->AltBody = $messagetext; // Versión texto plano
 | 
        
           | 1 | efrain | 6007 |     } else {
 | 
        
           |  |  | 6008 |         $mail->IsHTML(false);
 | 
        
           |  |  | 6009 |         $mail->Body =  "\n$messagetext\n";
 | 
        
           |  |  | 6010 |     }
 | 
        
           |  |  | 6011 |   | 
        
           |  |  | 6012 |     if ($attachment && $attachname) {
 | 
        
           | 1326 | ariadna | 6013 |         if (preg_match("~\\.\\.~", $attachment)) {
 | 
        
           | 1 | efrain | 6014 |             // Security check for ".." in dir path.
 | 
        
           |  |  | 6015 |             $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6016 |             $temprecipients[] = array($supportuser->email, fullname($supportuser, true));
 | 
        
           |  |  | 6017 |             $mail->addStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
 | 
        
           |  |  | 6018 |         } else {
 | 
        
           | 1326 | ariadna | 6019 |             require_once($CFG->libdir . '/filelib.php');
 | 
        
           | 1 | efrain | 6020 |             $mimetype = mimeinfo('type', $attachname);
 | 
        
           |  |  | 6021 |   | 
        
           |  |  | 6022 |             // Before doing the comparison, make sure that the paths are correct (Windows uses slashes in the other direction).
 | 
        
           |  |  | 6023 |             // The absolute (real) path is also fetched to ensure that comparisons to allowed paths are compared equally.
 | 
        
           |  |  | 6024 |             $attachpath = str_replace('\\', '/', realpath($attachment));
 | 
        
           |  |  | 6025 |   | 
        
           |  |  | 6026 |             // Build an array of all filepaths from which attachments can be added (normalised slashes, absolute/real path).
 | 
        
           | 1326 | ariadna | 6027 |             $allowedpaths = array_map(function (string $path): string {
 | 
        
           | 1 | efrain | 6028 |                 return str_replace('\\', '/', realpath($path));
 | 
        
           |  |  | 6029 |             }, [
 | 
        
           |  |  | 6030 |                 $CFG->cachedir,
 | 
        
           |  |  | 6031 |                 $CFG->dataroot,
 | 
        
           |  |  | 6032 |                 $CFG->dirroot,
 | 
        
           |  |  | 6033 |                 $CFG->localcachedir,
 | 
        
           |  |  | 6034 |                 $CFG->tempdir,
 | 
        
           |  |  | 6035 |                 $CFG->localrequestdir,
 | 
        
           |  |  | 6036 |             ]);
 | 
        
           |  |  | 6037 |   | 
        
           |  |  | 6038 |             // Set addpath to true.
 | 
        
           |  |  | 6039 |             $addpath = true;
 | 
        
           |  |  | 6040 |   | 
        
           |  |  | 6041 |             // Check if attachment includes one of the allowed paths.
 | 
        
           |  |  | 6042 |             foreach (array_filter($allowedpaths) as $allowedpath) {
 | 
        
           |  |  | 6043 |                 // Set addpath to false if the attachment includes one of the allowed paths.
 | 
        
           |  |  | 6044 |                 if (strpos($attachpath, $allowedpath) === 0) {
 | 
        
           |  |  | 6045 |                     $addpath = false;
 | 
        
           |  |  | 6046 |                     break;
 | 
        
           |  |  | 6047 |                 }
 | 
        
           |  |  | 6048 |             }
 | 
        
           |  |  | 6049 |   | 
        
           |  |  | 6050 |             // If the attachment is a full path to a file in the multiple allowed paths, use it as is,
 | 
        
           |  |  | 6051 |             // otherwise assume it is a relative path from the dataroot (for backwards compatibility reasons).
 | 
        
           |  |  | 6052 |             if ($addpath == true) {
 | 
        
           |  |  | 6053 |                 $attachment = $CFG->dataroot . '/' . $attachment;
 | 
        
           |  |  | 6054 |             }
 | 
        
           |  |  | 6055 |   | 
        
           |  |  | 6056 |             $mail->addAttachment($attachment, $attachname, 'base64', $mimetype);
 | 
        
           |  |  | 6057 |         }
 | 
        
           |  |  | 6058 |     }
 | 
        
           |  |  | 6059 |   | 
        
           |  |  | 6060 |     // Check if the email should be sent in an other charset then the default UTF-8.
 | 
        
           |  |  | 6061 |     if ((!empty($CFG->sitemailcharset) || !empty($CFG->allowusermailcharset))) {
 | 
        
           |  |  | 6062 |   | 
        
           |  |  | 6063 |         // Use the defined site mail charset or eventually the one preferred by the recipient.
 | 
        
           |  |  | 6064 |         $charset = $CFG->sitemailcharset;
 | 
        
           |  |  | 6065 |         if (!empty($CFG->allowusermailcharset)) {
 | 
        
           |  |  | 6066 |             if ($useremailcharset = get_user_preferences('mailcharset', '0', $user->id)) {
 | 
        
           |  |  | 6067 |                 $charset = $useremailcharset;
 | 
        
           |  |  | 6068 |             }
 | 
        
           |  |  | 6069 |         }
 | 
        
           |  |  | 6070 |   | 
        
           |  |  | 6071 |         // Convert all the necessary strings if the charset is supported.
 | 
        
           |  |  | 6072 |         $charsets = get_list_of_charsets();
 | 
        
           |  |  | 6073 |         unset($charsets['UTF-8']);
 | 
        
           |  |  | 6074 |         if (in_array($charset, $charsets)) {
 | 
        
           |  |  | 6075 |             $mail->CharSet  = $charset;
 | 
        
           |  |  | 6076 |             $mail->FromName = core_text::convert($mail->FromName, 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6077 |             $mail->Subject  = core_text::convert($mail->Subject, 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6078 |             $mail->Body     = core_text::convert($mail->Body, 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6079 |             $mail->AltBody  = core_text::convert($mail->AltBody, 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6080 |   | 
        
           |  |  | 6081 |             foreach ($temprecipients as $key => $values) {
 | 
        
           |  |  | 6082 |                 $temprecipients[$key][1] = core_text::convert($values[1], 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6083 |             }
 | 
        
           |  |  | 6084 |             foreach ($tempreplyto as $key => $values) {
 | 
        
           |  |  | 6085 |                 $tempreplyto[$key][1] = core_text::convert($values[1], 'utf-8', strtolower($charset));
 | 
        
           |  |  | 6086 |             }
 | 
        
           |  |  | 6087 |         }
 | 
        
           |  |  | 6088 |     }
 | 
        
           |  |  | 6089 |   | 
        
           |  |  | 6090 |     foreach ($temprecipients as $values) {
 | 
        
           |  |  | 6091 |         $mail->addAddress($values[0], $values[1]);
 | 
        
           |  |  | 6092 |     }
 | 
        
           |  |  | 6093 |     foreach ($tempreplyto as $values) {
 | 
        
           |  |  | 6094 |         $mail->addReplyTo($values[0], $values[1]);
 | 
        
           |  |  | 6095 |     }
 | 
        
           |  |  | 6096 |   | 
        
           |  |  | 6097 |     if (!empty($CFG->emaildkimselector)) {
 | 
        
           |  |  | 6098 |         $domain = substr(strrchr($mail->From, "@"), 1);
 | 
        
           |  |  | 6099 |         $pempath = "{$CFG->dataroot}/dkim/{$domain}/{$CFG->emaildkimselector}.private";
 | 
        
           |  |  | 6100 |         if (file_exists($pempath)) {
 | 
        
           |  |  | 6101 |             $mail->DKIM_domain      = $domain;
 | 
        
           |  |  | 6102 |             $mail->DKIM_private     = $pempath;
 | 
        
           |  |  | 6103 |             $mail->DKIM_selector    = $CFG->emaildkimselector;
 | 
        
           |  |  | 6104 |             $mail->DKIM_identity    = $mail->From;
 | 
        
           |  |  | 6105 |         } else {
 | 
        
           |  |  | 6106 |             debugging("Email DKIM selector chosen due to {$mail->From} but no certificate found at $pempath", DEBUG_DEVELOPER);
 | 
        
           |  |  | 6107 |         }
 | 
        
           |  |  | 6108 |     }
 | 
        
           |  |  | 6109 |   | 
        
           |  |  | 6110 |     if ($mail->send()) {
 | 
        
           |  |  | 6111 |         set_send_count($user);
 | 
        
           |  |  | 6112 |         if (!empty($mail->SMTPDebug)) {
 | 
        
           |  |  | 6113 |             echo '</pre>';
 | 
        
           |  |  | 6114 |         }
 | 
        
           |  |  | 6115 |         return true;
 | 
        
           |  |  | 6116 |     } else {
 | 
        
           |  |  | 6117 |         // Trigger event for failing to send email.
 | 
        
           |  |  | 6118 |         $event = \core\event\email_failed::create(array(
 | 
        
           |  |  | 6119 |             'context' => context_system::instance(),
 | 
        
           |  |  | 6120 |             'userid' => $from->id,
 | 
        
           |  |  | 6121 |             'relateduserid' => $user->id,
 | 
        
           |  |  | 6122 |             'other' => array(
 | 
        
           |  |  | 6123 |                 'subject' => $subject,
 | 
        
           |  |  | 6124 |                 'message' => $messagetext,
 | 
        
           |  |  | 6125 |                 'errorinfo' => $mail->ErrorInfo
 | 
        
           |  |  | 6126 |             )
 | 
        
           |  |  | 6127 |         ));
 | 
        
           |  |  | 6128 |         $event->trigger();
 | 
        
           |  |  | 6129 |         if (CLI_SCRIPT) {
 | 
        
           | 1326 | ariadna | 6130 |             mtrace('Error: lib/moodlelib.php email_to_user(): ' . $mail->ErrorInfo);
 | 
        
           | 1 | efrain | 6131 |         }
 | 
        
           |  |  | 6132 |         if (!empty($mail->SMTPDebug)) {
 | 
        
           |  |  | 6133 |             echo '</pre>';
 | 
        
           |  |  | 6134 |         }
 | 
        
           |  |  | 6135 |         return false;
 | 
        
           |  |  | 6136 |     }
 | 
        
           |  |  | 6137 | }
 | 
        
           |  |  | 6138 |   | 
        
           |  |  | 6139 | /**
 | 
        
           |  |  | 6140 |  * Check to see if a user's real email address should be used for the "From" field.
 | 
        
           |  |  | 6141 |  *
 | 
        
           |  |  | 6142 |  * @param  object $from The user object for the user we are sending the email from.
 | 
        
           |  |  | 6143 |  * @param  object $user The user object that we are sending the email to.
 | 
        
           |  |  | 6144 |  * @param  array $unused No longer used.
 | 
        
           |  |  | 6145 |  * @return bool Returns true if we can use the from user's email adress in the "From" field.
 | 
        
           |  |  | 6146 |  */
 | 
        
           | 1326 | ariadna | 6147 | function can_send_from_real_email_address($from, $user, $unused = null)
 | 
        
           |  |  | 6148 | {
 | 
        
           | 1 | efrain | 6149 |     global $CFG;
 | 
        
           |  |  | 6150 |     if (!isset($CFG->allowedemaildomains) || empty(trim($CFG->allowedemaildomains))) {
 | 
        
           |  |  | 6151 |         return false;
 | 
        
           |  |  | 6152 |     }
 | 
        
           |  |  | 6153 |     $alloweddomains = array_map('trim', explode("\n", $CFG->allowedemaildomains));
 | 
        
           |  |  | 6154 |     // Email is in the list of allowed domains for sending email,
 | 
        
           |  |  | 6155 |     // and the senders email setting is either displayed to everyone, or display to only other users that are enrolled
 | 
        
           |  |  | 6156 |     // in a course with the sender.
 | 
        
           | 1326 | ariadna | 6157 |     if (
 | 
        
           |  |  | 6158 |         \core\ip_utils::is_domain_in_allowed_list(substr($from->email, strpos($from->email, '@') + 1), $alloweddomains)
 | 
        
           |  |  | 6159 |         && ($from->maildisplay == core_user::MAILDISPLAY_EVERYONE
 | 
        
           |  |  | 6160 |             || ($from->maildisplay == core_user::MAILDISPLAY_COURSE_MEMBERS_ONLY
 | 
        
           |  |  | 6161 |                 && enrol_get_shared_courses($user, $from, false, true)))
 | 
        
           |  |  | 6162 |     ) {
 | 
        
           | 1 | efrain | 6163 |         return true;
 | 
        
           |  |  | 6164 |     }
 | 
        
           |  |  | 6165 |     return false;
 | 
        
           |  |  | 6166 | }
 | 
        
           |  |  | 6167 |   | 
        
           |  |  | 6168 | /**
 | 
        
           |  |  | 6169 |  * Generate a signoff for emails based on support settings
 | 
        
           |  |  | 6170 |  *
 | 
        
           |  |  | 6171 |  * @return string
 | 
        
           |  |  | 6172 |  */
 | 
        
           | 1326 | ariadna | 6173 | function generate_email_signoff()
 | 
        
           |  |  | 6174 | {
 | 
        
           | 1 | efrain | 6175 |     global $CFG, $OUTPUT;
 | 
        
           |  |  | 6176 |   | 
        
           |  |  | 6177 |     $signoff = "\n";
 | 
        
           |  |  | 6178 |     if (!empty($CFG->supportname)) {
 | 
        
           | 1326 | ariadna | 6179 |         $signoff .= $CFG->supportname . "\n";
 | 
        
           | 1 | efrain | 6180 |     }
 | 
        
           |  |  | 6181 |   | 
        
           |  |  | 6182 |     $supportemail = $OUTPUT->supportemail(['class' => 'font-weight-bold']);
 | 
        
           |  |  | 6183 |   | 
        
           |  |  | 6184 |     if ($supportemail) {
 | 
        
           |  |  | 6185 |         $signoff .= "\n" . $supportemail . "\n";
 | 
        
           |  |  | 6186 |     }
 | 
        
           |  |  | 6187 |   | 
        
           |  |  | 6188 |     return $signoff;
 | 
        
           |  |  | 6189 | }
 | 
        
           |  |  | 6190 |   | 
        
           |  |  | 6191 | /**
 | 
        
           |  |  | 6192 |  * Sets specified user's password and send the new password to the user via email.
 | 
        
           |  |  | 6193 |  *
 | 
        
           |  |  | 6194 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 6195 |  * @param bool $fasthash If true, use a low cost factor when generating the hash for speed.
 | 
        
           |  |  | 6196 |  * @return bool|string Returns "true" if mail was sent OK and "false" if there was an error
 | 
        
           |  |  | 6197 |  */
 | 
        
           | 1326 | ariadna | 6198 | function setnew_password_and_mail($user, $fasthash = false)
 | 
        
           |  |  | 6199 | {
 | 
        
           | 1 | efrain | 6200 |     global $CFG, $DB;
 | 
        
           |  |  | 6201 |   | 
        
           |  |  | 6202 |     // We try to send the mail in language the user understands,
 | 
        
           |  |  | 6203 |     // unfortunately the filter_string() does not support alternative langs yet
 | 
        
           |  |  | 6204 |     // so multilang will not work properly for site->fullname.
 | 
        
           |  |  | 6205 |     $lang = empty($user->lang) ? get_newuser_language() : $user->lang;
 | 
        
           |  |  | 6206 |   | 
        
           |  |  | 6207 |     $site  = get_site();
 | 
        
           |  |  | 6208 |   | 
        
           |  |  | 6209 |     $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6210 |   | 
        
           |  |  | 6211 |     $newpassword = generate_password();
 | 
        
           |  |  | 6212 |   | 
        
           |  |  | 6213 |     update_internal_user_password($user, $newpassword, $fasthash);
 | 
        
           |  |  | 6214 |   | 
        
           |  |  | 6215 |     $a = new stdClass();
 | 
        
           |  |  | 6216 |     $a->firstname   = fullname($user, true);
 | 
        
           |  |  | 6217 |     $a->sitename    = format_string($site->fullname);
 | 
        
           |  |  | 6218 |     $a->username    = $user->username;
 | 
        
           |  |  | 6219 |     $a->newpassword = $newpassword;
 | 
        
           | 1326 | ariadna | 6220 |     $a->link        = $CFG->wwwroot . '/login/?lang=' . $lang;
 | 
        
           | 1 | efrain | 6221 |     $a->signoff     = generate_email_signoff();
 | 
        
           |  |  | 6222 |   | 
        
           |  |  | 6223 |     $message = (string)new lang_string('newusernewpasswordtext', '', $a, $lang);
 | 
        
           |  |  | 6224 |   | 
        
           | 1326 | ariadna | 6225 |     $subject = format_string($site->fullname) . ': ' . (string)new lang_string('newusernewpasswordsubj', '', $a, $lang);
 | 
        
           | 1 | efrain | 6226 |   | 
        
           |  |  | 6227 |     // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6228 |     return email_to_user($user, $supportuser, $subject, $message);
 | 
        
           |  |  | 6229 | }
 | 
        
           |  |  | 6230 |   | 
        
           |  |  | 6231 | /**
 | 
        
           |  |  | 6232 |  * Resets specified user's password and send the new password to the user via email.
 | 
        
           |  |  | 6233 |  *
 | 
        
           |  |  | 6234 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 6235 |  * @return bool Returns true if mail was sent OK and false if there was an error.
 | 
        
           |  |  | 6236 |  */
 | 
        
           | 1326 | ariadna | 6237 | function reset_password_and_mail($user)
 | 
        
           |  |  | 6238 | {
 | 
        
           | 1 | efrain | 6239 |     global $CFG;
 | 
        
           |  |  | 6240 |   | 
        
           |  |  | 6241 |     $site  = get_site();
 | 
        
           |  |  | 6242 |     $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6243 |   | 
        
           |  |  | 6244 |     $userauth = get_auth_plugin($user->auth);
 | 
        
           |  |  | 6245 |     if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)) {
 | 
        
           |  |  | 6246 |         trigger_error("Attempt to reset user password for user $user->username with Auth $user->auth.");
 | 
        
           |  |  | 6247 |         return false;
 | 
        
           |  |  | 6248 |     }
 | 
        
           |  |  | 6249 |   | 
        
           |  |  | 6250 |     $newpassword = generate_password();
 | 
        
           |  |  | 6251 |   | 
        
           |  |  | 6252 |     if (!$userauth->user_update_password($user, $newpassword)) {
 | 
        
           |  |  | 6253 |         throw new \moodle_exception("cannotsetpassword");
 | 
        
           |  |  | 6254 |     }
 | 
        
           |  |  | 6255 |   | 
        
           |  |  | 6256 |     $a = new stdClass();
 | 
        
           |  |  | 6257 |     $a->firstname   = $user->firstname;
 | 
        
           |  |  | 6258 |     $a->lastname    = $user->lastname;
 | 
        
           |  |  | 6259 |     $a->sitename    = format_string($site->fullname);
 | 
        
           |  |  | 6260 |     $a->username    = $user->username;
 | 
        
           |  |  | 6261 |     $a->newpassword = $newpassword;
 | 
        
           | 1326 | ariadna | 6262 |     $a->link        = $CFG->wwwroot . '/login/change_password.php';
 | 
        
           | 1 | efrain | 6263 |     $a->signoff     = generate_email_signoff();
 | 
        
           |  |  | 6264 |   | 
        
           |  |  | 6265 |     $message = get_string('newpasswordtext', '', $a);
 | 
        
           |  |  | 6266 |   | 
        
           | 1326 | ariadna | 6267 |     $subject  = format_string($site->fullname) . ': ' . get_string('changedpassword');
 | 
        
           | 1 | efrain | 6268 |   | 
        
           |  |  | 6269 |     unset_user_preference('create_password', $user); // Prevent cron from generating the password.
 | 
        
           |  |  | 6270 |   | 
        
           |  |  | 6271 |     // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6272 |     return email_to_user($user, $supportuser, $subject, $message);
 | 
        
           |  |  | 6273 | }
 | 
        
           |  |  | 6274 |   | 
        
           |  |  | 6275 | /**
 | 
        
           |  |  | 6276 |  * Send email to specified user with confirmation text and activation link.
 | 
        
           |  |  | 6277 |  *
 | 
        
           |  |  | 6278 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 6279 |  * @param string $confirmationurl user confirmation URL
 | 
        
           |  |  | 6280 |  * @return bool Returns true if mail was sent OK and false if there was an error.
 | 
        
           |  |  | 6281 |  */
 | 
        
           | 1326 | ariadna | 6282 | function send_confirmation_email($user, $confirmationurl = null)
 | 
        
           |  |  | 6283 | {
 | 
        
           | 1 | efrain | 6284 |     global $CFG;
 | 
        
           |  |  | 6285 |   | 
        
           |  |  | 6286 |     $site = get_site();
 | 
        
           |  |  | 6287 |     $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6288 |   | 
        
           |  |  | 6289 |     $data = new stdClass();
 | 
        
           |  |  | 6290 |     $data->sitename  = format_string($site->fullname);
 | 
        
           |  |  | 6291 |     $data->admin     = generate_email_signoff();
 | 
        
           |  |  | 6292 |   | 
        
           |  |  | 6293 |     $subject = get_string('emailconfirmationsubject', '', format_string($site->fullname));
 | 
        
           |  |  | 6294 |   | 
        
           |  |  | 6295 |     if (empty($confirmationurl)) {
 | 
        
           |  |  | 6296 |         $confirmationurl = '/login/confirm.php';
 | 
        
           |  |  | 6297 |     }
 | 
        
           |  |  | 6298 |   | 
        
           |  |  | 6299 |     $confirmationurl = new moodle_url($confirmationurl);
 | 
        
           |  |  | 6300 |     // Remove data parameter just in case it was included in the confirmation so we can add it manually later.
 | 
        
           |  |  | 6301 |     $confirmationurl->remove_params('data');
 | 
        
           |  |  | 6302 |     $confirmationpath = $confirmationurl->out(false);
 | 
        
           |  |  | 6303 |   | 
        
           |  |  | 6304 |     // We need to custom encode the username to include trailing dots in the link.
 | 
        
           |  |  | 6305 |     // Because of this custom encoding we can't use moodle_url directly.
 | 
        
           |  |  | 6306 |     // Determine if a query string is present in the confirmation url.
 | 
        
           |  |  | 6307 |     $hasquerystring = strpos($confirmationpath, '?') !== false;
 | 
        
           |  |  | 6308 |     // Perform normal url encoding of the username first.
 | 
        
           |  |  | 6309 |     $username = urlencode($user->username);
 | 
        
           |  |  | 6310 |     // Prevent problems with trailing dots not being included as part of link in some mail clients.
 | 
        
           |  |  | 6311 |     $username = str_replace('.', '%2E', $username);
 | 
        
           |  |  | 6312 |   | 
        
           | 1326 | ariadna | 6313 |     $data->link = $confirmationpath . ($hasquerystring ? '&' : '?') . 'data=' . $user->secret . '/' . $username;
 | 
        
           | 1 | efrain | 6314 |   | 
        
           |  |  | 6315 |     $message     = get_string('emailconfirmation', '', $data);
 | 
        
           |  |  | 6316 |     $messagehtml = text_to_html(get_string('emailconfirmation', '', $data), false, false, true);
 | 
        
           |  |  | 6317 |   | 
        
           |  |  | 6318 |     // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6319 |     return email_to_user($user, $supportuser, $subject, $message, $messagehtml);
 | 
        
           |  |  | 6320 | }
 | 
        
           |  |  | 6321 |   | 
        
           |  |  | 6322 | /**
 | 
        
           |  |  | 6323 |  * Sends a password change confirmation email.
 | 
        
           |  |  | 6324 |  *
 | 
        
           |  |  | 6325 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 6326 |  * @param stdClass $resetrecord An object tracking metadata regarding password reset request
 | 
        
           |  |  | 6327 |  * @return bool Returns true if mail was sent OK and false if there was an error.
 | 
        
           |  |  | 6328 |  */
 | 
        
           | 1326 | ariadna | 6329 | function send_password_change_confirmation_email($user, $resetrecord)
 | 
        
           |  |  | 6330 | {
 | 
        
           | 1 | efrain | 6331 |     global $CFG;
 | 
        
           |  |  | 6332 |   | 
        
           |  |  | 6333 |     $site = get_site();
 | 
        
           |  |  | 6334 |     $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6335 |     $pwresetmins = isset($CFG->pwresettime) ? floor($CFG->pwresettime / MINSECS) : 30;
 | 
        
           |  |  | 6336 |   | 
        
           |  |  | 6337 |     $data = new stdClass();
 | 
        
           |  |  | 6338 |     $data->firstname = $user->firstname;
 | 
        
           |  |  | 6339 |     $data->lastname  = $user->lastname;
 | 
        
           |  |  | 6340 |     $data->username  = $user->username;
 | 
        
           |  |  | 6341 |     $data->sitename  = format_string($site->fullname);
 | 
        
           | 1326 | ariadna | 6342 |     $data->link      = $CFG->wwwroot . '/login/forgot_password.php?token=' . $resetrecord->token;
 | 
        
           | 1 | efrain | 6343 |     $data->admin     = generate_email_signoff();
 | 
        
           |  |  | 6344 |     $data->resetminutes = $pwresetmins;
 | 
        
           |  |  | 6345 |   | 
        
           |  |  | 6346 |     $message = get_string('emailresetconfirmation', '', $data);
 | 
        
           |  |  | 6347 |     $subject = get_string('emailresetconfirmationsubject', '', format_string($site->fullname));
 | 
        
           |  |  | 6348 |   | 
        
           |  |  | 6349 |     // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6350 |     return email_to_user($user, $supportuser, $subject, $message);
 | 
        
           |  |  | 6351 | }
 | 
        
           |  |  | 6352 |   | 
        
           |  |  | 6353 | /**
 | 
        
           |  |  | 6354 |  * Sends an email containing information on how to change your password.
 | 
        
           |  |  | 6355 |  *
 | 
        
           |  |  | 6356 |  * @param stdClass $user A {@link $USER} object
 | 
        
           |  |  | 6357 |  * @return bool Returns true if mail was sent OK and false if there was an error.
 | 
        
           |  |  | 6358 |  */
 | 
        
           | 1326 | ariadna | 6359 | function send_password_change_info($user)
 | 
        
           |  |  | 6360 | {
 | 
        
           | 1 | efrain | 6361 |     $site = get_site();
 | 
        
           |  |  | 6362 |     $supportuser = core_user::get_support_user();
 | 
        
           |  |  | 6363 |   | 
        
           |  |  | 6364 |     $data = new stdClass();
 | 
        
           |  |  | 6365 |     $data->firstname = $user->firstname;
 | 
        
           |  |  | 6366 |     $data->lastname  = $user->lastname;
 | 
        
           |  |  | 6367 |     $data->username  = $user->username;
 | 
        
           |  |  | 6368 |     $data->sitename  = format_string($site->fullname);
 | 
        
           |  |  | 6369 |     $data->admin     = generate_email_signoff();
 | 
        
           |  |  | 6370 |   | 
        
           |  |  | 6371 |     if (!is_enabled_auth($user->auth)) {
 | 
        
           |  |  | 6372 |         $message = get_string('emailpasswordchangeinfodisabled', '', $data);
 | 
        
           |  |  | 6373 |         $subject = get_string('emailpasswordchangeinfosubject', '', format_string($site->fullname));
 | 
        
           |  |  | 6374 |         // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6375 |         return email_to_user($user, $supportuser, $subject, $message);
 | 
        
           |  |  | 6376 |     }
 | 
        
           |  |  | 6377 |   | 
        
           |  |  | 6378 |     $userauth = get_auth_plugin($user->auth);
 | 
        
           |  |  | 6379 |     ['subject' => $subject, 'message' => $message] = $userauth->get_password_change_info($user);
 | 
        
           |  |  | 6380 |   | 
        
           |  |  | 6381 |     // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
 | 
        
           |  |  | 6382 |     return email_to_user($user, $supportuser, $subject, $message);
 | 
        
           |  |  | 6383 | }
 | 
        
           |  |  | 6384 |   | 
        
           |  |  | 6385 | /**
 | 
        
           |  |  | 6386 |  * Check that an email is allowed.  It returns an error message if there was a problem.
 | 
        
           |  |  | 6387 |  *
 | 
        
           |  |  | 6388 |  * @param string $email Content of email
 | 
        
           |  |  | 6389 |  * @return string|false
 | 
        
           |  |  | 6390 |  */
 | 
        
           | 1326 | ariadna | 6391 | function email_is_not_allowed($email)
 | 
        
           |  |  | 6392 | {
 | 
        
           | 1 | efrain | 6393 |     global $CFG;
 | 
        
           |  |  | 6394 |   | 
        
           |  |  | 6395 |     // Comparing lowercase domains.
 | 
        
           |  |  | 6396 |     $email = strtolower($email);
 | 
        
           |  |  | 6397 |     if (!empty($CFG->allowemailaddresses)) {
 | 
        
           |  |  | 6398 |         $allowed = explode(' ', strtolower($CFG->allowemailaddresses));
 | 
        
           |  |  | 6399 |         foreach ($allowed as $allowedpattern) {
 | 
        
           |  |  | 6400 |             $allowedpattern = trim($allowedpattern);
 | 
        
           |  |  | 6401 |             if (!$allowedpattern) {
 | 
        
           |  |  | 6402 |                 continue;
 | 
        
           |  |  | 6403 |             }
 | 
        
           |  |  | 6404 |             if (strpos($allowedpattern, '.') === 0) {
 | 
        
           |  |  | 6405 |                 if (strpos(strrev($email), strrev($allowedpattern)) === 0) {
 | 
        
           |  |  | 6406 |                     // Subdomains are in a form ".example.com" - matches "xxx@anything.example.com".
 | 
        
           |  |  | 6407 |                     return false;
 | 
        
           |  |  | 6408 |                 }
 | 
        
           | 1326 | ariadna | 6409 |             } else if (strpos(strrev($email), strrev('@' . $allowedpattern)) === 0) {
 | 
        
           | 1 | efrain | 6410 |                 return false;
 | 
        
           |  |  | 6411 |             }
 | 
        
           |  |  | 6412 |         }
 | 
        
           |  |  | 6413 |         return get_string('emailonlyallowed', '', $CFG->allowemailaddresses);
 | 
        
           |  |  | 6414 |     } else if (!empty($CFG->denyemailaddresses)) {
 | 
        
           |  |  | 6415 |         $denied = explode(' ', strtolower($CFG->denyemailaddresses));
 | 
        
           |  |  | 6416 |         foreach ($denied as $deniedpattern) {
 | 
        
           |  |  | 6417 |             $deniedpattern = trim($deniedpattern);
 | 
        
           |  |  | 6418 |             if (!$deniedpattern) {
 | 
        
           |  |  | 6419 |                 continue;
 | 
        
           |  |  | 6420 |             }
 | 
        
           |  |  | 6421 |             if (strpos($deniedpattern, '.') === 0) {
 | 
        
           |  |  | 6422 |                 if (strpos(strrev($email), strrev($deniedpattern)) === 0) {
 | 
        
           |  |  | 6423 |                     // Subdomains are in a form ".example.com" - matches "xxx@anything.example.com".
 | 
        
           |  |  | 6424 |                     return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
 | 
        
           |  |  | 6425 |                 }
 | 
        
           | 1326 | ariadna | 6426 |             } else if (strpos(strrev($email), strrev('@' . $deniedpattern)) === 0) {
 | 
        
           | 1 | efrain | 6427 |                 return get_string('emailnotallowed', '', $CFG->denyemailaddresses);
 | 
        
           |  |  | 6428 |             }
 | 
        
           |  |  | 6429 |         }
 | 
        
           |  |  | 6430 |     }
 | 
        
           |  |  | 6431 |   | 
        
           |  |  | 6432 |     return false;
 | 
        
           |  |  | 6433 | }
 | 
        
           |  |  | 6434 |   | 
        
           |  |  | 6435 | // FILE HANDLING.
 | 
        
           |  |  | 6436 |   | 
        
           |  |  | 6437 | /**
 | 
        
           |  |  | 6438 |  * Returns local file storage instance
 | 
        
           |  |  | 6439 |  *
 | 
        
           |  |  | 6440 |  * @return ?file_storage
 | 
        
           |  |  | 6441 |  */
 | 
        
           | 1326 | ariadna | 6442 | function get_file_storage($reset = false)
 | 
        
           |  |  | 6443 | {
 | 
        
           | 1 | efrain | 6444 |     global $CFG;
 | 
        
           |  |  | 6445 |   | 
        
           |  |  | 6446 |     static $fs = null;
 | 
        
           |  |  | 6447 |   | 
        
           |  |  | 6448 |     if ($reset) {
 | 
        
           |  |  | 6449 |         $fs = null;
 | 
        
           |  |  | 6450 |         return;
 | 
        
           |  |  | 6451 |     }
 | 
        
           |  |  | 6452 |   | 
        
           |  |  | 6453 |     if ($fs) {
 | 
        
           |  |  | 6454 |         return $fs;
 | 
        
           |  |  | 6455 |     }
 | 
        
           |  |  | 6456 |   | 
        
           |  |  | 6457 |     require_once("$CFG->libdir/filelib.php");
 | 
        
           |  |  | 6458 |   | 
        
           |  |  | 6459 |     $fs = new file_storage();
 | 
        
           |  |  | 6460 |   | 
        
           |  |  | 6461 |     return $fs;
 | 
        
           |  |  | 6462 | }
 | 
        
           |  |  | 6463 |   | 
        
           |  |  | 6464 | /**
 | 
        
           |  |  | 6465 |  * Returns local file storage instance
 | 
        
           |  |  | 6466 |  *
 | 
        
           |  |  | 6467 |  * @return file_browser
 | 
        
           |  |  | 6468 |  */
 | 
        
           | 1326 | ariadna | 6469 | function get_file_browser()
 | 
        
           |  |  | 6470 | {
 | 
        
           | 1 | efrain | 6471 |     global $CFG;
 | 
        
           |  |  | 6472 |   | 
        
           |  |  | 6473 |     static $fb = null;
 | 
        
           |  |  | 6474 |   | 
        
           |  |  | 6475 |     if ($fb) {
 | 
        
           |  |  | 6476 |         return $fb;
 | 
        
           |  |  | 6477 |     }
 | 
        
           |  |  | 6478 |   | 
        
           |  |  | 6479 |     require_once("$CFG->libdir/filelib.php");
 | 
        
           |  |  | 6480 |   | 
        
           |  |  | 6481 |     $fb = new file_browser();
 | 
        
           |  |  | 6482 |   | 
        
           |  |  | 6483 |     return $fb;
 | 
        
           |  |  | 6484 | }
 | 
        
           |  |  | 6485 |   | 
        
           |  |  | 6486 | /**
 | 
        
           |  |  | 6487 |  * Returns file packer
 | 
        
           |  |  | 6488 |  *
 | 
        
           |  |  | 6489 |  * @param string $mimetype default application/zip
 | 
        
           |  |  | 6490 |  * @return file_packer|false
 | 
        
           |  |  | 6491 |  */
 | 
        
           | 1326 | ariadna | 6492 | function get_file_packer($mimetype = 'application/zip')
 | 
        
           |  |  | 6493 | {
 | 
        
           | 1 | efrain | 6494 |     global $CFG;
 | 
        
           |  |  | 6495 |   | 
        
           |  |  | 6496 |     static $fp = array();
 | 
        
           |  |  | 6497 |   | 
        
           |  |  | 6498 |     if (isset($fp[$mimetype])) {
 | 
        
           |  |  | 6499 |         return $fp[$mimetype];
 | 
        
           |  |  | 6500 |     }
 | 
        
           |  |  | 6501 |   | 
        
           |  |  | 6502 |     switch ($mimetype) {
 | 
        
           |  |  | 6503 |         case 'application/zip':
 | 
        
           |  |  | 6504 |         case 'application/vnd.moodle.profiling':
 | 
        
           |  |  | 6505 |             $classname = 'zip_packer';
 | 
        
           |  |  | 6506 |             break;
 | 
        
           |  |  | 6507 |   | 
        
           | 1326 | ariadna | 6508 |         case 'application/x-gzip':
 | 
        
           | 1 | efrain | 6509 |             $classname = 'tgz_packer';
 | 
        
           |  |  | 6510 |             break;
 | 
        
           |  |  | 6511 |   | 
        
           |  |  | 6512 |         case 'application/vnd.moodle.backup':
 | 
        
           |  |  | 6513 |             $classname = 'mbz_packer';
 | 
        
           |  |  | 6514 |             break;
 | 
        
           |  |  | 6515 |   | 
        
           |  |  | 6516 |         default:
 | 
        
           |  |  | 6517 |             return false;
 | 
        
           |  |  | 6518 |     }
 | 
        
           |  |  | 6519 |   | 
        
           |  |  | 6520 |     require_once("$CFG->libdir/filestorage/$classname.php");
 | 
        
           |  |  | 6521 |     $fp[$mimetype] = new $classname();
 | 
        
           |  |  | 6522 |   | 
        
           |  |  | 6523 |     return $fp[$mimetype];
 | 
        
           |  |  | 6524 | }
 | 
        
           |  |  | 6525 |   | 
        
           |  |  | 6526 | /**
 | 
        
           |  |  | 6527 |  * Returns current name of file on disk if it exists.
 | 
        
           |  |  | 6528 |  *
 | 
        
           |  |  | 6529 |  * @param string $newfile File to be verified
 | 
        
           |  |  | 6530 |  * @return string Current name of file on disk if true
 | 
        
           |  |  | 6531 |  */
 | 
        
           | 1326 | ariadna | 6532 | function valid_uploaded_file($newfile)
 | 
        
           |  |  | 6533 | {
 | 
        
           | 1 | efrain | 6534 |     if (empty($newfile)) {
 | 
        
           |  |  | 6535 |         return '';
 | 
        
           |  |  | 6536 |     }
 | 
        
           |  |  | 6537 |     if (is_uploaded_file($newfile['tmp_name']) and $newfile['size'] > 0) {
 | 
        
           |  |  | 6538 |         return $newfile['tmp_name'];
 | 
        
           |  |  | 6539 |     } else {
 | 
        
           |  |  | 6540 |         return '';
 | 
        
           |  |  | 6541 |     }
 | 
        
           |  |  | 6542 | }
 | 
        
           |  |  | 6543 |   | 
        
           |  |  | 6544 | /**
 | 
        
           |  |  | 6545 |  * Returns the maximum size for uploading files.
 | 
        
           |  |  | 6546 |  *
 | 
        
           |  |  | 6547 |  * There are seven possible upload limits:
 | 
        
           |  |  | 6548 |  * 1. in Apache using LimitRequestBody (no way of checking or changing this)
 | 
        
           |  |  | 6549 |  * 2. in php.ini for 'upload_max_filesize' (can not be changed inside PHP)
 | 
        
           |  |  | 6550 |  * 3. in .htaccess for 'upload_max_filesize' (can not be changed inside PHP)
 | 
        
           |  |  | 6551 |  * 4. in php.ini for 'post_max_size' (can not be changed inside PHP)
 | 
        
           |  |  | 6552 |  * 5. by the Moodle admin in $CFG->maxbytes
 | 
        
           |  |  | 6553 |  * 6. by the teacher in the current course $course->maxbytes
 | 
        
           |  |  | 6554 |  * 7. by the teacher for the current module, eg $assignment->maxbytes
 | 
        
           |  |  | 6555 |  *
 | 
        
           |  |  | 6556 |  * These last two are passed to this function as arguments (in bytes).
 | 
        
           |  |  | 6557 |  * Anything defined as 0 is ignored.
 | 
        
           |  |  | 6558 |  * The smallest of all the non-zero numbers is returned.
 | 
        
           |  |  | 6559 |  *
 | 
        
           |  |  | 6560 |  * @todo Finish documenting this function
 | 
        
           |  |  | 6561 |  *
 | 
        
           |  |  | 6562 |  * @param int $sitebytes Set maximum size
 | 
        
           |  |  | 6563 |  * @param int $coursebytes Current course $course->maxbytes (in bytes)
 | 
        
           |  |  | 6564 |  * @param int $modulebytes Current module ->maxbytes (in bytes)
 | 
        
           |  |  | 6565 |  * @param bool $unused This parameter has been deprecated and is not used any more.
 | 
        
           |  |  | 6566 |  * @return int The maximum size for uploading files.
 | 
        
           |  |  | 6567 |  */
 | 
        
           | 1326 | ariadna | 6568 | function get_max_upload_file_size($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $unused = false)
 | 
        
           |  |  | 6569 | {
 | 
        
           | 1 | efrain | 6570 |   | 
        
           |  |  | 6571 |     if (! $filesize = ini_get('upload_max_filesize')) {
 | 
        
           |  |  | 6572 |         $filesize = '5M';
 | 
        
           |  |  | 6573 |     }
 | 
        
           |  |  | 6574 |     $minimumsize = get_real_size($filesize);
 | 
        
           |  |  | 6575 |   | 
        
           |  |  | 6576 |     if ($postsize = ini_get('post_max_size')) {
 | 
        
           |  |  | 6577 |         $postsize = get_real_size($postsize);
 | 
        
           |  |  | 6578 |         if ($postsize < $minimumsize) {
 | 
        
           |  |  | 6579 |             $minimumsize = $postsize;
 | 
        
           |  |  | 6580 |         }
 | 
        
           |  |  | 6581 |     }
 | 
        
           |  |  | 6582 |   | 
        
           |  |  | 6583 |     if (($sitebytes > 0) and ($sitebytes < $minimumsize)) {
 | 
        
           |  |  | 6584 |         $minimumsize = $sitebytes;
 | 
        
           |  |  | 6585 |     }
 | 
        
           |  |  | 6586 |   | 
        
           |  |  | 6587 |     if (($coursebytes > 0) and ($coursebytes < $minimumsize)) {
 | 
        
           |  |  | 6588 |         $minimumsize = $coursebytes;
 | 
        
           |  |  | 6589 |     }
 | 
        
           |  |  | 6590 |   | 
        
           |  |  | 6591 |     if (($modulebytes > 0) and ($modulebytes < $minimumsize)) {
 | 
        
           |  |  | 6592 |         $minimumsize = $modulebytes;
 | 
        
           |  |  | 6593 |     }
 | 
        
           |  |  | 6594 |   | 
        
           |  |  | 6595 |     return $minimumsize;
 | 
        
           |  |  | 6596 | }
 | 
        
           |  |  | 6597 |   | 
        
           |  |  | 6598 | /**
 | 
        
           |  |  | 6599 |  * Returns the maximum size for uploading files for the current user
 | 
        
           |  |  | 6600 |  *
 | 
        
           |  |  | 6601 |  * This function takes in account {@link get_max_upload_file_size()} the user's capabilities
 | 
        
           |  |  | 6602 |  *
 | 
        
           |  |  | 6603 |  * @param context $context The context in which to check user capabilities
 | 
        
           |  |  | 6604 |  * @param int $sitebytes Set maximum size
 | 
        
           |  |  | 6605 |  * @param int $coursebytes Current course $course->maxbytes (in bytes)
 | 
        
           |  |  | 6606 |  * @param int $modulebytes Current module ->maxbytes (in bytes)
 | 
        
           |  |  | 6607 |  * @param stdClass|int|null $user The user
 | 
        
           |  |  | 6608 |  * @param bool $unused This parameter has been deprecated and is not used any more.
 | 
        
           |  |  | 6609 |  * @return int The maximum size for uploading files.
 | 
        
           |  |  | 6610 |  */
 | 
        
           | 1326 | ariadna | 6611 | function get_user_max_upload_file_size(
 | 
        
           |  |  | 6612 |     $context,
 | 
        
           |  |  | 6613 |     $sitebytes = 0,
 | 
        
           |  |  | 6614 |     $coursebytes = 0,
 | 
        
           |  |  | 6615 |     $modulebytes = 0,
 | 
        
           |  |  | 6616 |     $user = null,
 | 
        
           |  |  | 6617 |     $unused = false
 | 
        
           |  |  | 6618 | ) {
 | 
        
           | 1 | efrain | 6619 |     global $USER;
 | 
        
           |  |  | 6620 |   | 
        
           |  |  | 6621 |     if (empty($user)) {
 | 
        
           |  |  | 6622 |         $user = $USER;
 | 
        
           |  |  | 6623 |     }
 | 
        
           |  |  | 6624 |   | 
        
           |  |  | 6625 |     if (has_capability('moodle/course:ignorefilesizelimits', $context, $user)) {
 | 
        
           |  |  | 6626 |         return USER_CAN_IGNORE_FILE_SIZE_LIMITS;
 | 
        
           |  |  | 6627 |     }
 | 
        
           |  |  | 6628 |   | 
        
           |  |  | 6629 |     return get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes);
 | 
        
           |  |  | 6630 | }
 | 
        
           |  |  | 6631 |   | 
        
           |  |  | 6632 | /**
 | 
        
           |  |  | 6633 |  * Returns an array of possible sizes in local language
 | 
        
           |  |  | 6634 |  *
 | 
        
           |  |  | 6635 |  * Related to {@link get_max_upload_file_size()} - this function returns an
 | 
        
           |  |  | 6636 |  * array of possible sizes in an array, translated to the
 | 
        
           |  |  | 6637 |  * local language.
 | 
        
           |  |  | 6638 |  *
 | 
        
           |  |  | 6639 |  * The list of options will go up to the minimum of $sitebytes, $coursebytes or $modulebytes.
 | 
        
           |  |  | 6640 |  *
 | 
        
           |  |  | 6641 |  * If $coursebytes or $sitebytes is not 0, an option will be included for "Course/Site upload limit (X)"
 | 
        
           |  |  | 6642 |  * with the value set to 0. This option will be the first in the list.
 | 
        
           |  |  | 6643 |  *
 | 
        
           |  |  | 6644 |  * @uses SORT_NUMERIC
 | 
        
           |  |  | 6645 |  * @param int $sitebytes Set maximum size
 | 
        
           |  |  | 6646 |  * @param int $coursebytes Current course $course->maxbytes (in bytes)
 | 
        
           |  |  | 6647 |  * @param int $modulebytes Current module ->maxbytes (in bytes)
 | 
        
           |  |  | 6648 |  * @param int|array $custombytes custom upload size/s which will be added to list,
 | 
        
           |  |  | 6649 |  *        Only value/s smaller then maxsize will be added to list.
 | 
        
           |  |  | 6650 |  * @return array
 | 
        
           |  |  | 6651 |  */
 | 
        
           | 1326 | ariadna | 6652 | function get_max_upload_sizes($sitebytes = 0, $coursebytes = 0, $modulebytes = 0, $custombytes = null)
 | 
        
           |  |  | 6653 | {
 | 
        
           | 1 | efrain | 6654 |     global $CFG;
 | 
        
           |  |  | 6655 |   | 
        
           |  |  | 6656 |     if (!$maxsize = get_max_upload_file_size($sitebytes, $coursebytes, $modulebytes)) {
 | 
        
           |  |  | 6657 |         return array();
 | 
        
           |  |  | 6658 |     }
 | 
        
           |  |  | 6659 |   | 
        
           |  |  | 6660 |     if ($sitebytes == 0) {
 | 
        
           |  |  | 6661 |         // Will get the minimum of upload_max_filesize or post_max_size.
 | 
        
           |  |  | 6662 |         $sitebytes = get_max_upload_file_size();
 | 
        
           |  |  | 6663 |     }
 | 
        
           |  |  | 6664 |   | 
        
           |  |  | 6665 |     $filesize = array();
 | 
        
           | 1326 | ariadna | 6666 |     $sizelist = array(
 | 
        
           |  |  | 6667 |         10240,
 | 
        
           |  |  | 6668 |         51200,
 | 
        
           |  |  | 6669 |         102400,
 | 
        
           |  |  | 6670 |         512000,
 | 
        
           |  |  | 6671 |         1048576,
 | 
        
           |  |  | 6672 |         2097152,
 | 
        
           |  |  | 6673 |         5242880,
 | 
        
           |  |  | 6674 |         10485760,
 | 
        
           |  |  | 6675 |         20971520,
 | 
        
           |  |  | 6676 |         52428800,
 | 
        
           |  |  | 6677 |         104857600,
 | 
        
           |  |  | 6678 |         262144000,
 | 
        
           |  |  | 6679 |         524288000,
 | 
        
           |  |  | 6680 |         786432000,
 | 
        
           |  |  | 6681 |         1073741824,
 | 
        
           |  |  | 6682 |         2147483648,
 | 
        
           |  |  | 6683 |         4294967296,
 | 
        
           |  |  | 6684 |         8589934592
 | 
        
           |  |  | 6685 |     );
 | 
        
           | 1 | efrain | 6686 |   | 
        
           |  |  | 6687 |     // If custombytes is given and is valid then add it to the list.
 | 
        
           |  |  | 6688 |     if (is_number($custombytes) and $custombytes > 0) {
 | 
        
           |  |  | 6689 |         $custombytes = (int)$custombytes;
 | 
        
           |  |  | 6690 |         if (!in_array($custombytes, $sizelist)) {
 | 
        
           |  |  | 6691 |             $sizelist[] = $custombytes;
 | 
        
           |  |  | 6692 |         }
 | 
        
           |  |  | 6693 |     } else if (is_array($custombytes)) {
 | 
        
           |  |  | 6694 |         $sizelist = array_unique(array_merge($sizelist, $custombytes));
 | 
        
           |  |  | 6695 |     }
 | 
        
           |  |  | 6696 |   | 
        
           |  |  | 6697 |     // Allow maxbytes to be selected if it falls outside the above boundaries.
 | 
        
           |  |  | 6698 |     if (isset($CFG->maxbytes) && !in_array(get_real_size($CFG->maxbytes), $sizelist)) {
 | 
        
           |  |  | 6699 |         // Note: get_real_size() is used in order to prevent problems with invalid values.
 | 
        
           |  |  | 6700 |         $sizelist[] = get_real_size($CFG->maxbytes);
 | 
        
           |  |  | 6701 |     }
 | 
        
           |  |  | 6702 |   | 
        
           |  |  | 6703 |     foreach ($sizelist as $sizebytes) {
 | 
        
           |  |  | 6704 |         if ($sizebytes < $maxsize && $sizebytes > 0) {
 | 
        
           |  |  | 6705 |             $filesize[(string)intval($sizebytes)] = display_size($sizebytes, 0);
 | 
        
           |  |  | 6706 |         }
 | 
        
           |  |  | 6707 |     }
 | 
        
           |  |  | 6708 |   | 
        
           |  |  | 6709 |     $limitlevel = '';
 | 
        
           |  |  | 6710 |     $displaysize = '';
 | 
        
           | 1326 | ariadna | 6711 |     if (
 | 
        
           |  |  | 6712 |         $modulebytes &&
 | 
        
           | 1 | efrain | 6713 |         (($modulebytes < $coursebytes || $coursebytes == 0) &&
 | 
        
           | 1326 | ariadna | 6714 |             ($modulebytes < $sitebytes || $sitebytes == 0))
 | 
        
           |  |  | 6715 |     ) {
 | 
        
           | 1 | efrain | 6716 |         $limitlevel = get_string('activity', 'core');
 | 
        
           |  |  | 6717 |         $displaysize = display_size($modulebytes, 0);
 | 
        
           |  |  | 6718 |         $filesize[$modulebytes] = $displaysize; // Make sure the limit is also included in the list.
 | 
        
           |  |  | 6719 |   | 
        
           |  |  | 6720 |     } else if ($coursebytes && ($coursebytes < $sitebytes || $sitebytes == 0)) {
 | 
        
           |  |  | 6721 |         $limitlevel = get_string('course', 'core');
 | 
        
           |  |  | 6722 |         $displaysize = display_size($coursebytes, 0);
 | 
        
           |  |  | 6723 |         $filesize[$coursebytes] = $displaysize; // Make sure the limit is also included in the list.
 | 
        
           |  |  | 6724 |   | 
        
           |  |  | 6725 |     } else if ($sitebytes) {
 | 
        
           |  |  | 6726 |         $limitlevel = get_string('site', 'core');
 | 
        
           |  |  | 6727 |         $displaysize = display_size($sitebytes, 0);
 | 
        
           |  |  | 6728 |         $filesize[$sitebytes] = $displaysize; // Make sure the limit is also included in the list.
 | 
        
           |  |  | 6729 |     }
 | 
        
           |  |  | 6730 |   | 
        
           |  |  | 6731 |     krsort($filesize, SORT_NUMERIC);
 | 
        
           |  |  | 6732 |     if ($limitlevel) {
 | 
        
           |  |  | 6733 |         $params = (object) array('contextname' => $limitlevel, 'displaysize' => $displaysize);
 | 
        
           |  |  | 6734 |         $filesize  = array('0' => get_string('uploadlimitwithsize', 'core', $params)) + $filesize;
 | 
        
           |  |  | 6735 |     }
 | 
        
           |  |  | 6736 |   | 
        
           |  |  | 6737 |     return $filesize;
 | 
        
           |  |  | 6738 | }
 | 
        
           |  |  | 6739 |   | 
        
           |  |  | 6740 | /**
 | 
        
           |  |  | 6741 |  * Returns an array with all the filenames in all subdirectories, relative to the given rootdir.
 | 
        
           |  |  | 6742 |  *
 | 
        
           |  |  | 6743 |  * If excludefiles is defined, then that file/directory is ignored
 | 
        
           |  |  | 6744 |  * If getdirs is true, then (sub)directories are included in the output
 | 
        
           |  |  | 6745 |  * If getfiles is true, then files are included in the output
 | 
        
           |  |  | 6746 |  * (at least one of these must be true!)
 | 
        
           |  |  | 6747 |  *
 | 
        
           |  |  | 6748 |  * @todo Finish documenting this function. Add examples of $excludefile usage.
 | 
        
           |  |  | 6749 |  *
 | 
        
           |  |  | 6750 |  * @param string $rootdir A given root directory to start from
 | 
        
           |  |  | 6751 |  * @param string|array $excludefiles If defined then the specified file/directory is ignored
 | 
        
           |  |  | 6752 |  * @param bool $descend If true then subdirectories are recursed as well
 | 
        
           |  |  | 6753 |  * @param bool $getdirs If true then (sub)directories are included in the output
 | 
        
           |  |  | 6754 |  * @param bool $getfiles  If true then files are included in the output
 | 
        
           |  |  | 6755 |  * @return array An array with all the filenames in all subdirectories, relative to the given rootdir
 | 
        
           |  |  | 6756 |  */
 | 
        
           | 1326 | ariadna | 6757 | function get_directory_list($rootdir, $excludefiles = '', $descend = true, $getdirs = false, $getfiles = true)
 | 
        
           |  |  | 6758 | {
 | 
        
           | 1 | efrain | 6759 |   | 
        
           |  |  | 6760 |     $dirs = array();
 | 
        
           |  |  | 6761 |   | 
        
           |  |  | 6762 |     if (!$getdirs and !$getfiles) {   // Nothing to show.
 | 
        
           |  |  | 6763 |         return $dirs;
 | 
        
           |  |  | 6764 |     }
 | 
        
           |  |  | 6765 |   | 
        
           |  |  | 6766 |     if (!is_dir($rootdir)) {          // Must be a directory.
 | 
        
           |  |  | 6767 |         return $dirs;
 | 
        
           |  |  | 6768 |     }
 | 
        
           |  |  | 6769 |   | 
        
           |  |  | 6770 |     if (!$dir = opendir($rootdir)) {  // Can't open it for some reason.
 | 
        
           |  |  | 6771 |         return $dirs;
 | 
        
           |  |  | 6772 |     }
 | 
        
           |  |  | 6773 |   | 
        
           |  |  | 6774 |     if (!is_array($excludefiles)) {
 | 
        
           |  |  | 6775 |         $excludefiles = array($excludefiles);
 | 
        
           |  |  | 6776 |     }
 | 
        
           |  |  | 6777 |   | 
        
           |  |  | 6778 |     while (false !== ($file = readdir($dir))) {
 | 
        
           |  |  | 6779 |         $firstchar = substr($file, 0, 1);
 | 
        
           |  |  | 6780 |         if ($firstchar == '.' or $file == 'CVS' or in_array($file, $excludefiles)) {
 | 
        
           |  |  | 6781 |             continue;
 | 
        
           |  |  | 6782 |         }
 | 
        
           | 1326 | ariadna | 6783 |         $fullfile = $rootdir . '/' . $file;
 | 
        
           | 1 | efrain | 6784 |         if (filetype($fullfile) == 'dir') {
 | 
        
           |  |  | 6785 |             if ($getdirs) {
 | 
        
           |  |  | 6786 |                 $dirs[] = $file;
 | 
        
           |  |  | 6787 |             }
 | 
        
           |  |  | 6788 |             if ($descend) {
 | 
        
           |  |  | 6789 |                 $subdirs = get_directory_list($fullfile, $excludefiles, $descend, $getdirs, $getfiles);
 | 
        
           |  |  | 6790 |                 foreach ($subdirs as $subdir) {
 | 
        
           | 1326 | ariadna | 6791 |                     $dirs[] = $file . '/' . $subdir;
 | 
        
           | 1 | efrain | 6792 |                 }
 | 
        
           |  |  | 6793 |             }
 | 
        
           |  |  | 6794 |         } else if ($getfiles) {
 | 
        
           |  |  | 6795 |             $dirs[] = $file;
 | 
        
           |  |  | 6796 |         }
 | 
        
           |  |  | 6797 |     }
 | 
        
           |  |  | 6798 |     closedir($dir);
 | 
        
           |  |  | 6799 |   | 
        
           |  |  | 6800 |     asort($dirs);
 | 
        
           |  |  | 6801 |   | 
        
           |  |  | 6802 |     return $dirs;
 | 
        
           |  |  | 6803 | }
 | 
        
           |  |  | 6804 |   | 
        
           |  |  | 6805 |   | 
        
           |  |  | 6806 | /**
 | 
        
           |  |  | 6807 |  * Adds up all the files in a directory and works out the size.
 | 
        
           |  |  | 6808 |  *
 | 
        
           |  |  | 6809 |  * @param string $rootdir  The directory to start from
 | 
        
           |  |  | 6810 |  * @param string $excludefile A file to exclude when summing directory size
 | 
        
           |  |  | 6811 |  * @return int The summed size of all files and subfiles within the root directory
 | 
        
           |  |  | 6812 |  */
 | 
        
           | 1326 | ariadna | 6813 | function get_directory_size($rootdir, $excludefile = '')
 | 
        
           |  |  | 6814 | {
 | 
        
           | 1 | efrain | 6815 |     global $CFG;
 | 
        
           |  |  | 6816 |   | 
        
           |  |  | 6817 |     // Do it this way if we can, it's much faster.
 | 
        
           |  |  | 6818 |     if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
 | 
        
           | 1326 | ariadna | 6819 |         $command = trim($CFG->pathtodu) . ' -sk ' . escapeshellarg($rootdir);
 | 
        
           | 1 | efrain | 6820 |         $output = null;
 | 
        
           |  |  | 6821 |         $return = null;
 | 
        
           |  |  | 6822 |         exec($command, $output, $return);
 | 
        
           |  |  | 6823 |         if (is_array($output)) {
 | 
        
           |  |  | 6824 |             // We told it to return k.
 | 
        
           | 1326 | ariadna | 6825 |             return get_real_size(intval($output[0]) . 'k');
 | 
        
           | 1 | efrain | 6826 |         }
 | 
        
           |  |  | 6827 |     }
 | 
        
           |  |  | 6828 |   | 
        
           |  |  | 6829 |     if (!is_dir($rootdir)) {
 | 
        
           |  |  | 6830 |         // Must be a directory.
 | 
        
           |  |  | 6831 |         return 0;
 | 
        
           |  |  | 6832 |     }
 | 
        
           |  |  | 6833 |   | 
        
           |  |  | 6834 |     if (!$dir = @opendir($rootdir)) {
 | 
        
           |  |  | 6835 |         // Can't open it for some reason.
 | 
        
           |  |  | 6836 |         return 0;
 | 
        
           |  |  | 6837 |     }
 | 
        
           |  |  | 6838 |   | 
        
           |  |  | 6839 |     $size = 0;
 | 
        
           |  |  | 6840 |   | 
        
           |  |  | 6841 |     while (false !== ($file = readdir($dir))) {
 | 
        
           |  |  | 6842 |         $firstchar = substr($file, 0, 1);
 | 
        
           |  |  | 6843 |         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
 | 
        
           |  |  | 6844 |             continue;
 | 
        
           |  |  | 6845 |         }
 | 
        
           | 1326 | ariadna | 6846 |         $fullfile = $rootdir . '/' . $file;
 | 
        
           | 1 | efrain | 6847 |         if (filetype($fullfile) == 'dir') {
 | 
        
           |  |  | 6848 |             $size += get_directory_size($fullfile, $excludefile);
 | 
        
           |  |  | 6849 |         } else {
 | 
        
           |  |  | 6850 |             $size += filesize($fullfile);
 | 
        
           |  |  | 6851 |         }
 | 
        
           |  |  | 6852 |     }
 | 
        
           |  |  | 6853 |     closedir($dir);
 | 
        
           |  |  | 6854 |   | 
        
           |  |  | 6855 |     return $size;
 | 
        
           |  |  | 6856 | }
 | 
        
           |  |  | 6857 |   | 
        
           |  |  | 6858 | /**
 | 
        
           |  |  | 6859 |  * Converts bytes into display form
 | 
        
           |  |  | 6860 |  *
 | 
        
           |  |  | 6861 |  * @param int $size  The size to convert to human readable form
 | 
        
           |  |  | 6862 |  * @param int $decimalplaces If specified, uses fixed number of decimal places
 | 
        
           |  |  | 6863 |  * @param string $fixedunits If specified, uses fixed units (e.g. 'KB')
 | 
        
           |  |  | 6864 |  * @return string Display version of size
 | 
        
           |  |  | 6865 |  */
 | 
        
           | 1326 | ariadna | 6866 | function display_size($size, int $decimalplaces = 1, string $fixedunits = ''): string
 | 
        
           |  |  | 6867 | {
 | 
        
           | 1 | efrain | 6868 |   | 
        
           |  |  | 6869 |     static $units;
 | 
        
           |  |  | 6870 |   | 
        
           |  |  | 6871 |     if ($size === USER_CAN_IGNORE_FILE_SIZE_LIMITS) {
 | 
        
           |  |  | 6872 |         return get_string('unlimited');
 | 
        
           |  |  | 6873 |     }
 | 
        
           |  |  | 6874 |   | 
        
           |  |  | 6875 |     if (empty($units)) {
 | 
        
           |  |  | 6876 |         $units[] = get_string('sizeb');
 | 
        
           |  |  | 6877 |         $units[] = get_string('sizekb');
 | 
        
           |  |  | 6878 |         $units[] = get_string('sizemb');
 | 
        
           |  |  | 6879 |         $units[] = get_string('sizegb');
 | 
        
           |  |  | 6880 |         $units[] = get_string('sizetb');
 | 
        
           |  |  | 6881 |         $units[] = get_string('sizepb');
 | 
        
           |  |  | 6882 |     }
 | 
        
           |  |  | 6883 |   | 
        
           |  |  | 6884 |     switch ($fixedunits) {
 | 
        
           | 1326 | ariadna | 6885 |         case 'PB':
 | 
        
           | 1 | efrain | 6886 |             $magnitude = 5;
 | 
        
           |  |  | 6887 |             break;
 | 
        
           | 1326 | ariadna | 6888 |         case 'TB':
 | 
        
           | 1 | efrain | 6889 |             $magnitude = 4;
 | 
        
           |  |  | 6890 |             break;
 | 
        
           | 1326 | ariadna | 6891 |         case 'GB':
 | 
        
           | 1 | efrain | 6892 |             $magnitude = 3;
 | 
        
           |  |  | 6893 |             break;
 | 
        
           | 1326 | ariadna | 6894 |         case 'MB':
 | 
        
           | 1 | efrain | 6895 |             $magnitude = 2;
 | 
        
           |  |  | 6896 |             break;
 | 
        
           | 1326 | ariadna | 6897 |         case 'KB':
 | 
        
           | 1 | efrain | 6898 |             $magnitude = 1;
 | 
        
           |  |  | 6899 |             break;
 | 
        
           | 1326 | ariadna | 6900 |         case 'B':
 | 
        
           | 1 | efrain | 6901 |             $magnitude = 0;
 | 
        
           |  |  | 6902 |             break;
 | 
        
           |  |  | 6903 |         case '':
 | 
        
           |  |  | 6904 |             $magnitude = floor(log($size, 1024));
 | 
        
           |  |  | 6905 |             $magnitude = max(0, min(5, $magnitude));
 | 
        
           |  |  | 6906 |             break;
 | 
        
           |  |  | 6907 |         default:
 | 
        
           |  |  | 6908 |             throw new coding_exception('Unknown fixed units value: ' . $fixedunits);
 | 
        
           |  |  | 6909 |     }
 | 
        
           |  |  | 6910 |   | 
        
           |  |  | 6911 |     // Special case for magnitude 0 (bytes) - never use decimal places.
 | 
        
           |  |  | 6912 |     $nbsp = "\xc2\xa0";
 | 
        
           |  |  | 6913 |     if ($magnitude === 0) {
 | 
        
           |  |  | 6914 |         return round($size) . $nbsp . $units[$magnitude];
 | 
        
           |  |  | 6915 |     }
 | 
        
           |  |  | 6916 |   | 
        
           |  |  | 6917 |     // Convert to specified units.
 | 
        
           |  |  | 6918 |     $sizeinunit = $size / 1024 ** $magnitude;
 | 
        
           |  |  | 6919 |   | 
        
           |  |  | 6920 |     // Fixed decimal places.
 | 
        
           |  |  | 6921 |     return sprintf('%.' . $decimalplaces . 'f', $sizeinunit) . $nbsp . $units[$magnitude];
 | 
        
           |  |  | 6922 | }
 | 
        
           |  |  | 6923 |   | 
        
           |  |  | 6924 | /**
 | 
        
           |  |  | 6925 |  * Cleans a given filename by removing suspicious or troublesome characters
 | 
        
           |  |  | 6926 |  *
 | 
        
           |  |  | 6927 |  * @see clean_param()
 | 
        
           |  |  | 6928 |  * @param string $string file name
 | 
        
           |  |  | 6929 |  * @return string cleaned file name
 | 
        
           |  |  | 6930 |  */
 | 
        
           | 1326 | ariadna | 6931 | function clean_filename($string)
 | 
        
           |  |  | 6932 | {
 | 
        
           | 1 | efrain | 6933 |     return clean_param($string, PARAM_FILE);
 | 
        
           |  |  | 6934 | }
 | 
        
           |  |  | 6935 |   | 
        
           |  |  | 6936 | // STRING TRANSLATION.
 | 
        
           |  |  | 6937 |   | 
        
           |  |  | 6938 | /**
 | 
        
           |  |  | 6939 |  * Returns the code for the current language
 | 
        
           |  |  | 6940 |  *
 | 
        
           |  |  | 6941 |  * @category string
 | 
        
           |  |  | 6942 |  * @return string
 | 
        
           |  |  | 6943 |  */
 | 
        
           | 1326 | ariadna | 6944 | function current_language()
 | 
        
           |  |  | 6945 | {
 | 
        
           | 1 | efrain | 6946 |     global $CFG, $PAGE, $SESSION, $USER;
 | 
        
           |  |  | 6947 |   | 
        
           |  |  | 6948 |     if (!empty($SESSION->forcelang)) {
 | 
        
           |  |  | 6949 |         // Allows overriding course-forced language (useful for admins to check
 | 
        
           |  |  | 6950 |         // issues in courses whose language they don't understand).
 | 
        
           |  |  | 6951 |         // Also used by some code to temporarily get language-related information in a
 | 
        
           |  |  | 6952 |         // specific language (see force_current_language()).
 | 
        
           |  |  | 6953 |         $return = $SESSION->forcelang;
 | 
        
           |  |  | 6954 |     } else if (!empty($PAGE->cm->lang)) {
 | 
        
           |  |  | 6955 |         // Activity language, if set.
 | 
        
           |  |  | 6956 |         $return = $PAGE->cm->lang;
 | 
        
           |  |  | 6957 |     } else if (!empty($PAGE->course->id) && $PAGE->course->id != SITEID && !empty($PAGE->course->lang)) {
 | 
        
           |  |  | 6958 |         // Course language can override all other settings for this page.
 | 
        
           |  |  | 6959 |         $return = $PAGE->course->lang;
 | 
        
           |  |  | 6960 |     } else if (!empty($SESSION->lang)) {
 | 
        
           |  |  | 6961 |         // Session language can override other settings.
 | 
        
           |  |  | 6962 |         $return = $SESSION->lang;
 | 
        
           |  |  | 6963 |     } else if (!empty($USER->lang)) {
 | 
        
           |  |  | 6964 |         $return = $USER->lang;
 | 
        
           |  |  | 6965 |     } else if (isset($CFG->lang)) {
 | 
        
           |  |  | 6966 |         $return = $CFG->lang;
 | 
        
           |  |  | 6967 |     } else {
 | 
        
           |  |  | 6968 |         $return = 'en';
 | 
        
           |  |  | 6969 |     }
 | 
        
           |  |  | 6970 |   | 
        
           |  |  | 6971 |     // Just in case this slipped in from somewhere by accident.
 | 
        
           |  |  | 6972 |     $return = str_replace('_utf8', '', $return);
 | 
        
           |  |  | 6973 |   | 
        
           |  |  | 6974 |     return $return;
 | 
        
           |  |  | 6975 | }
 | 
        
           |  |  | 6976 |   | 
        
           |  |  | 6977 | /**
 | 
        
           |  |  | 6978 |  * Fix the current language to the given language code.
 | 
        
           |  |  | 6979 |  *
 | 
        
           |  |  | 6980 |  * @param string $lang The language code to use.
 | 
        
           |  |  | 6981 |  * @return void
 | 
        
           |  |  | 6982 |  */
 | 
        
           | 1326 | ariadna | 6983 | function fix_current_language(string $lang): void
 | 
        
           |  |  | 6984 | {
 | 
        
           | 1 | efrain | 6985 |     global $CFG, $COURSE, $SESSION, $USER;
 | 
        
           |  |  | 6986 |   | 
        
           |  |  | 6987 |     if (!get_string_manager()->translation_exists($lang)) {
 | 
        
           |  |  | 6988 |         throw new coding_exception("The language pack for $lang is not available");
 | 
        
           |  |  | 6989 |     }
 | 
        
           |  |  | 6990 |   | 
        
           |  |  | 6991 |     $fixglobal = '';
 | 
        
           |  |  | 6992 |     $fixlang = 'lang';
 | 
        
           |  |  | 6993 |     if (!empty($SESSION->forcelang)) {
 | 
        
           |  |  | 6994 |         $fixglobal = $SESSION;
 | 
        
           |  |  | 6995 |         $fixlang = 'forcelang';
 | 
        
           |  |  | 6996 |     } else if (!empty($COURSE->id) && $COURSE->id != SITEID && !empty($COURSE->lang)) {
 | 
        
           |  |  | 6997 |         $fixglobal = $COURSE;
 | 
        
           |  |  | 6998 |     } else if (!empty($SESSION->lang)) {
 | 
        
           |  |  | 6999 |         $fixglobal = $SESSION;
 | 
        
           |  |  | 7000 |     } else if (!empty($USER->lang)) {
 | 
        
           |  |  | 7001 |         $fixglobal = $USER;
 | 
        
           |  |  | 7002 |     } else if (isset($CFG->lang)) {
 | 
        
           |  |  | 7003 |         set_config('lang', $lang);
 | 
        
           |  |  | 7004 |     }
 | 
        
           |  |  | 7005 |   | 
        
           |  |  | 7006 |     if ($fixglobal) {
 | 
        
           |  |  | 7007 |         $fixglobal->$fixlang = $lang;
 | 
        
           |  |  | 7008 |     }
 | 
        
           |  |  | 7009 | }
 | 
        
           |  |  | 7010 |   | 
        
           |  |  | 7011 | /**
 | 
        
           |  |  | 7012 |  * Returns parent language of current active language if defined
 | 
        
           |  |  | 7013 |  *
 | 
        
           |  |  | 7014 |  * @category string
 | 
        
           |  |  | 7015 |  * @param string $lang null means current language
 | 
        
           |  |  | 7016 |  * @return string
 | 
        
           |  |  | 7017 |  */
 | 
        
           | 1326 | ariadna | 7018 | function get_parent_language($lang = null)
 | 
        
           |  |  | 7019 | {
 | 
        
           | 1 | efrain | 7020 |   | 
        
           |  |  | 7021 |     $parentlang = get_string_manager()->get_string('parentlanguage', 'langconfig', null, $lang);
 | 
        
           |  |  | 7022 |   | 
        
           |  |  | 7023 |     if ($parentlang === 'en') {
 | 
        
           |  |  | 7024 |         $parentlang = '';
 | 
        
           |  |  | 7025 |     }
 | 
        
           |  |  | 7026 |   | 
        
           |  |  | 7027 |     return $parentlang;
 | 
        
           |  |  | 7028 | }
 | 
        
           |  |  | 7029 |   | 
        
           |  |  | 7030 | /**
 | 
        
           |  |  | 7031 |  * Force the current language to get strings and dates localised in the given language.
 | 
        
           |  |  | 7032 |  *
 | 
        
           |  |  | 7033 |  * After calling this function, all strings will be provided in the given language
 | 
        
           |  |  | 7034 |  * until this function is called again, or equivalent code is run.
 | 
        
           |  |  | 7035 |  *
 | 
        
           |  |  | 7036 |  * @param string $language
 | 
        
           |  |  | 7037 |  * @return string previous $SESSION->forcelang value
 | 
        
           |  |  | 7038 |  */
 | 
        
           | 1326 | ariadna | 7039 | function force_current_language($language)
 | 
        
           |  |  | 7040 | {
 | 
        
           | 1 | efrain | 7041 |     global $SESSION;
 | 
        
           |  |  | 7042 |     $sessionforcelang = isset($SESSION->forcelang) ? $SESSION->forcelang : '';
 | 
        
           |  |  | 7043 |     if ($language !== $sessionforcelang) {
 | 
        
           |  |  | 7044 |         // Setting forcelang to null or an empty string disables its effect.
 | 
        
           |  |  | 7045 |         if (empty($language) || get_string_manager()->translation_exists($language, false)) {
 | 
        
           |  |  | 7046 |             $SESSION->forcelang = $language;
 | 
        
           |  |  | 7047 |             moodle_setlocale();
 | 
        
           |  |  | 7048 |         }
 | 
        
           |  |  | 7049 |     }
 | 
        
           |  |  | 7050 |     return $sessionforcelang;
 | 
        
           |  |  | 7051 | }
 | 
        
           |  |  | 7052 |   | 
        
           |  |  | 7053 | /**
 | 
        
           |  |  | 7054 |  * Returns current string_manager instance.
 | 
        
           |  |  | 7055 |  *
 | 
        
           |  |  | 7056 |  * The param $forcereload is needed for CLI installer only where the string_manager instance
 | 
        
           |  |  | 7057 |  * must be replaced during the install.php script life time.
 | 
        
           |  |  | 7058 |  *
 | 
        
           |  |  | 7059 |  * @category string
 | 
        
           |  |  | 7060 |  * @param bool $forcereload shall the singleton be released and new instance created instead?
 | 
        
           |  |  | 7061 |  * @return core_string_manager
 | 
        
           |  |  | 7062 |  */
 | 
        
           | 1326 | ariadna | 7063 | function get_string_manager($forcereload = false)
 | 
        
           |  |  | 7064 | {
 | 
        
           | 1 | efrain | 7065 |     global $CFG;
 | 
        
           |  |  | 7066 |   | 
        
           |  |  | 7067 |     static $singleton = null;
 | 
        
           |  |  | 7068 |   | 
        
           |  |  | 7069 |     if ($forcereload) {
 | 
        
           |  |  | 7070 |         $singleton = null;
 | 
        
           |  |  | 7071 |     }
 | 
        
           |  |  | 7072 |     if ($singleton === null) {
 | 
        
           |  |  | 7073 |         if (empty($CFG->early_install_lang)) {
 | 
        
           |  |  | 7074 |   | 
        
           |  |  | 7075 |             $transaliases = array();
 | 
        
           |  |  | 7076 |             if (empty($CFG->langlist)) {
 | 
        
           | 1326 | ariadna | 7077 |                 $translist = array();
 | 
        
           | 1 | efrain | 7078 |             } else {
 | 
        
           |  |  | 7079 |                 $translist = explode(',', $CFG->langlist);
 | 
        
           |  |  | 7080 |                 $translist = array_map('trim', $translist);
 | 
        
           |  |  | 7081 |                 // Each language in the $CFG->langlist can has an "alias" that would substitute the default language name.
 | 
        
           |  |  | 7082 |                 foreach ($translist as $i => $value) {
 | 
        
           |  |  | 7083 |                     $parts = preg_split('/\s*\|\s*/', $value, 2);
 | 
        
           |  |  | 7084 |                     if (count($parts) == 2) {
 | 
        
           |  |  | 7085 |                         $transaliases[$parts[0]] = $parts[1];
 | 
        
           |  |  | 7086 |                         $translist[$i] = $parts[0];
 | 
        
           |  |  | 7087 |                     }
 | 
        
           |  |  | 7088 |                 }
 | 
        
           |  |  | 7089 |             }
 | 
        
           |  |  | 7090 |   | 
        
           |  |  | 7091 |             if (!empty($CFG->config_php_settings['customstringmanager'])) {
 | 
        
           |  |  | 7092 |                 $classname = $CFG->config_php_settings['customstringmanager'];
 | 
        
           |  |  | 7093 |   | 
        
           |  |  | 7094 |                 if (class_exists($classname)) {
 | 
        
           |  |  | 7095 |                     $implements = class_implements($classname);
 | 
        
           |  |  | 7096 |   | 
        
           |  |  | 7097 |                     if (isset($implements['core_string_manager'])) {
 | 
        
           |  |  | 7098 |                         $singleton = new $classname($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
 | 
        
           |  |  | 7099 |                         return $singleton;
 | 
        
           |  |  | 7100 |                     } else {
 | 
        
           | 1326 | ariadna | 7101 |                         debugging('Unable to instantiate custom string manager: class ' . $classname .
 | 
        
           | 1 | efrain | 7102 |                             ' does not implement the core_string_manager interface.');
 | 
        
           |  |  | 7103 |                     }
 | 
        
           |  |  | 7104 |                 } else {
 | 
        
           | 1326 | ariadna | 7105 |                     debugging('Unable to instantiate custom string manager: class ' . $classname . ' can not be found.');
 | 
        
           | 1 | efrain | 7106 |                 }
 | 
        
           |  |  | 7107 |             }
 | 
        
           |  |  | 7108 |   | 
        
           |  |  | 7109 |             $singleton = new core_string_manager_standard($CFG->langotherroot, $CFG->langlocalroot, $translist, $transaliases);
 | 
        
           |  |  | 7110 |         } else {
 | 
        
           |  |  | 7111 |             $singleton = new core_string_manager_install();
 | 
        
           |  |  | 7112 |         }
 | 
        
           |  |  | 7113 |     }
 | 
        
           |  |  | 7114 |   | 
        
           |  |  | 7115 |     return $singleton;
 | 
        
           |  |  | 7116 | }
 | 
        
           |  |  | 7117 |   | 
        
           |  |  | 7118 | /**
 | 
        
           |  |  | 7119 |  * Returns a localized string.
 | 
        
           |  |  | 7120 |  *
 | 
        
           |  |  | 7121 |  * Returns the translated string specified by $identifier as
 | 
        
           |  |  | 7122 |  * for $module.  Uses the same format files as STphp.
 | 
        
           |  |  | 7123 |  * $a is an object, string or number that can be used
 | 
        
           |  |  | 7124 |  * within translation strings
 | 
        
           |  |  | 7125 |  *
 | 
        
           |  |  | 7126 |  * eg 'hello {$a->firstname} {$a->lastname}'
 | 
        
           |  |  | 7127 |  * or 'hello {$a}'
 | 
        
           |  |  | 7128 |  *
 | 
        
           |  |  | 7129 |  * If you would like to directly echo the localized string use
 | 
        
           |  |  | 7130 |  * the function {@link print_string()}
 | 
        
           |  |  | 7131 |  *
 | 
        
           |  |  | 7132 |  * Example usage of this function involves finding the string you would
 | 
        
           |  |  | 7133 |  * like a local equivalent of and using its identifier and module information
 | 
        
           |  |  | 7134 |  * to retrieve it.<br/>
 | 
        
           |  |  | 7135 |  * If you open moodle/lang/en/moodle.php and look near line 278
 | 
        
           |  |  | 7136 |  * you will find a string to prompt a user for their word for 'course'
 | 
        
           |  |  | 7137 |  * <code>
 | 
        
           |  |  | 7138 |  * $string['course'] = 'Course';
 | 
        
           |  |  | 7139 |  * </code>
 | 
        
           |  |  | 7140 |  * So if you want to display the string 'Course'
 | 
        
           |  |  | 7141 |  * in any language that supports it on your site
 | 
        
           |  |  | 7142 |  * you just need to use the identifier 'course'
 | 
        
           |  |  | 7143 |  * <code>
 | 
        
           |  |  | 7144 |  * $mystring = '<strong>'. get_string('course') .'</strong>';
 | 
        
           |  |  | 7145 |  * or
 | 
        
           |  |  | 7146 |  * </code>
 | 
        
           |  |  | 7147 |  * If the string you want is in another file you'd take a slightly
 | 
        
           |  |  | 7148 |  * different approach. Looking in moodle/lang/en/calendar.php you find
 | 
        
           |  |  | 7149 |  * around line 75:
 | 
        
           |  |  | 7150 |  * <code>
 | 
        
           |  |  | 7151 |  * $string['typecourse'] = 'Course event';
 | 
        
           |  |  | 7152 |  * </code>
 | 
        
           |  |  | 7153 |  * If you want to display the string "Course event" in any language
 | 
        
           |  |  | 7154 |  * supported you would use the identifier 'typecourse' and the module 'calendar'
 | 
        
           |  |  | 7155 |  * (because it is in the file calendar.php):
 | 
        
           |  |  | 7156 |  * <code>
 | 
        
           |  |  | 7157 |  * $mystring = '<h1>'. get_string('typecourse', 'calendar') .'</h1>';
 | 
        
           |  |  | 7158 |  * </code>
 | 
        
           |  |  | 7159 |  *
 | 
        
           |  |  | 7160 |  * As a last resort, should the identifier fail to map to a string
 | 
        
           |  |  | 7161 |  * the returned string will be [[ $identifier ]]
 | 
        
           |  |  | 7162 |  *
 | 
        
           |  |  | 7163 |  * In Moodle 2.3 there is a new argument to this function $lazyload.
 | 
        
           |  |  | 7164 |  * Setting $lazyload to true causes get_string to return a lang_string object
 | 
        
           |  |  | 7165 |  * rather than the string itself. The fetching of the string is then put off until
 | 
        
           |  |  | 7166 |  * the string object is first used. The object can be used by calling it's out
 | 
        
           |  |  | 7167 |  * method or by casting the object to a string, either directly e.g.
 | 
        
           |  |  | 7168 |  *     (string)$stringobject
 | 
        
           |  |  | 7169 |  * or indirectly by using the string within another string or echoing it out e.g.
 | 
        
           |  |  | 7170 |  *     echo $stringobject
 | 
        
           |  |  | 7171 |  *     return "<p>{$stringobject}</p>";
 | 
        
           |  |  | 7172 |  * It is worth noting that using $lazyload and attempting to use the string as an
 | 
        
           |  |  | 7173 |  * array key will cause a fatal error as objects cannot be used as array keys.
 | 
        
           |  |  | 7174 |  * But you should never do that anyway!
 | 
        
           |  |  | 7175 |  * For more information {@link lang_string}
 | 
        
           |  |  | 7176 |  *
 | 
        
           |  |  | 7177 |  * @category string
 | 
        
           |  |  | 7178 |  * @param string $identifier The key identifier for the localized string
 | 
        
           |  |  | 7179 |  * @param string $component The module where the key identifier is stored,
 | 
        
           |  |  | 7180 |  *      usually expressed as the filename in the language pack without the
 | 
        
           |  |  | 7181 |  *      .php on the end but can also be written as mod/forum or grade/export/xls.
 | 
        
           |  |  | 7182 |  *      If none is specified then moodle.php is used.
 | 
        
           |  |  | 7183 |  * @param string|object|array|int $a An object, string or number that can be used
 | 
        
           |  |  | 7184 |  *      within translation strings
 | 
        
           |  |  | 7185 |  * @param bool $lazyload If set to true a string object is returned instead of
 | 
        
           |  |  | 7186 |  *      the string itself. The string then isn't calculated until it is first used.
 | 
        
           |  |  | 7187 |  * @return string The localized string.
 | 
        
           |  |  | 7188 |  * @throws coding_exception
 | 
        
           |  |  | 7189 |  */
 | 
        
           | 1326 | ariadna | 7190 | function get_string($identifier, $component = '', $a = null, $lazyload = false)
 | 
        
           |  |  | 7191 | {
 | 
        
           | 1 | efrain | 7192 |     global $CFG;
 | 
        
           |  |  | 7193 |   | 
        
           |  |  | 7194 |     // If the lazy load argument has been supplied return a lang_string object
 | 
        
           |  |  | 7195 |     // instead.
 | 
        
           |  |  | 7196 |     // We need to make sure it is true (and a bool) as you will see below there
 | 
        
           |  |  | 7197 |     // used to be a forth argument at one point.
 | 
        
           |  |  | 7198 |     if ($lazyload === true) {
 | 
        
           |  |  | 7199 |         return new lang_string($identifier, $component, $a);
 | 
        
           |  |  | 7200 |     }
 | 
        
           |  |  | 7201 |   | 
        
           |  |  | 7202 |     if ($CFG->debugdeveloper && clean_param($identifier, PARAM_STRINGID) === '') {
 | 
        
           |  |  | 7203 |         throw new coding_exception('Invalid string identifier. The identifier cannot be empty. Please fix your get_string() call.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 7204 |     }
 | 
        
           |  |  | 7205 |   | 
        
           |  |  | 7206 |     // There is now a forth argument again, this time it is a boolean however so
 | 
        
           |  |  | 7207 |     // we can still check for the old extralocations parameter.
 | 
        
           |  |  | 7208 |     if (!is_bool($lazyload) && !empty($lazyload)) {
 | 
        
           |  |  | 7209 |         debugging('extralocations parameter in get_string() is not supported any more, please use standard lang locations only.');
 | 
        
           |  |  | 7210 |     }
 | 
        
           |  |  | 7211 |   | 
        
           |  |  | 7212 |     if (strpos((string)$component, '/') !== false) {
 | 
        
           |  |  | 7213 |         debugging('The module name you passed to get_string is the deprecated format ' .
 | 
        
           | 1326 | ariadna | 7214 |             'like mod/mymod or block/myblock. The correct form looks like mymod, or block_myblock.', DEBUG_DEVELOPER);
 | 
        
           | 1 | efrain | 7215 |         $componentpath = explode('/', $component);
 | 
        
           |  |  | 7216 |   | 
        
           |  |  | 7217 |         switch ($componentpath[0]) {
 | 
        
           |  |  | 7218 |             case 'mod':
 | 
        
           |  |  | 7219 |                 $component = $componentpath[1];
 | 
        
           |  |  | 7220 |                 break;
 | 
        
           |  |  | 7221 |             case 'blocks':
 | 
        
           |  |  | 7222 |             case 'block':
 | 
        
           | 1326 | ariadna | 7223 |                 $component = 'block_' . $componentpath[1];
 | 
        
           | 1 | efrain | 7224 |                 break;
 | 
        
           |  |  | 7225 |             case 'enrol':
 | 
        
           | 1326 | ariadna | 7226 |                 $component = 'enrol_' . $componentpath[1];
 | 
        
           | 1 | efrain | 7227 |                 break;
 | 
        
           |  |  | 7228 |             case 'format':
 | 
        
           | 1326 | ariadna | 7229 |                 $component = 'format_' . $componentpath[1];
 | 
        
           | 1 | efrain | 7230 |                 break;
 | 
        
           |  |  | 7231 |             case 'grade':
 | 
        
           | 1326 | ariadna | 7232 |                 $component = 'grade' . $componentpath[1] . '_' . $componentpath[2];
 | 
        
           | 1 | efrain | 7233 |                 break;
 | 
        
           |  |  | 7234 |         }
 | 
        
           |  |  | 7235 |     }
 | 
        
           |  |  | 7236 |   | 
        
           |  |  | 7237 |     $result = get_string_manager()->get_string($identifier, $component, $a);
 | 
        
           |  |  | 7238 |   | 
        
           |  |  | 7239 |     // Debugging feature lets you display string identifier and component.
 | 
        
           |  |  | 7240 |     if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
 | 
        
           |  |  | 7241 |         $result .= ' {' . $identifier . '/' . $component . '}';
 | 
        
           |  |  | 7242 |     }
 | 
        
           |  |  | 7243 |     return $result;
 | 
        
           |  |  | 7244 | }
 | 
        
           |  |  | 7245 |   | 
        
           |  |  | 7246 | /**
 | 
        
           |  |  | 7247 |  * Converts an array of strings to their localized value.
 | 
        
           |  |  | 7248 |  *
 | 
        
           |  |  | 7249 |  * @param array $array An array of strings
 | 
        
           |  |  | 7250 |  * @param string $component The language module that these strings can be found in.
 | 
        
           |  |  | 7251 |  * @return stdClass translated strings.
 | 
        
           |  |  | 7252 |  */
 | 
        
           | 1326 | ariadna | 7253 | function get_strings($array, $component = '')
 | 
        
           |  |  | 7254 | {
 | 
        
           | 1 | efrain | 7255 |     $string = new stdClass;
 | 
        
           |  |  | 7256 |     foreach ($array as $item) {
 | 
        
           |  |  | 7257 |         $string->$item = get_string($item, $component);
 | 
        
           |  |  | 7258 |     }
 | 
        
           |  |  | 7259 |     return $string;
 | 
        
           |  |  | 7260 | }
 | 
        
           |  |  | 7261 |   | 
        
           |  |  | 7262 | /**
 | 
        
           |  |  | 7263 |  * Prints out a translated string.
 | 
        
           |  |  | 7264 |  *
 | 
        
           |  |  | 7265 |  * Prints out a translated string using the return value from the {@link get_string()} function.
 | 
        
           |  |  | 7266 |  *
 | 
        
           |  |  | 7267 |  * Example usage of this function when the string is in the moodle.php file:<br/>
 | 
        
           |  |  | 7268 |  * <code>
 | 
        
           |  |  | 7269 |  * echo '<strong>';
 | 
        
           |  |  | 7270 |  * print_string('course');
 | 
        
           |  |  | 7271 |  * echo '</strong>';
 | 
        
           |  |  | 7272 |  * </code>
 | 
        
           |  |  | 7273 |  *
 | 
        
           |  |  | 7274 |  * Example usage of this function when the string is not in the moodle.php file:<br/>
 | 
        
           |  |  | 7275 |  * <code>
 | 
        
           |  |  | 7276 |  * echo '<h1>';
 | 
        
           |  |  | 7277 |  * print_string('typecourse', 'calendar');
 | 
        
           |  |  | 7278 |  * echo '</h1>';
 | 
        
           |  |  | 7279 |  * </code>
 | 
        
           |  |  | 7280 |  *
 | 
        
           |  |  | 7281 |  * @category string
 | 
        
           |  |  | 7282 |  * @param string $identifier The key identifier for the localized string
 | 
        
           |  |  | 7283 |  * @param string $component The module where the key identifier is stored. If none is specified then moodle.php is used.
 | 
        
           |  |  | 7284 |  * @param string|object|array $a An object, string or number that can be used within translation strings
 | 
        
           |  |  | 7285 |  */
 | 
        
           | 1326 | ariadna | 7286 | function print_string($identifier, $component = '', $a = null)
 | 
        
           |  |  | 7287 | {
 | 
        
           | 1 | efrain | 7288 |     echo get_string($identifier, $component, $a);
 | 
        
           |  |  | 7289 | }
 | 
        
           |  |  | 7290 |   | 
        
           |  |  | 7291 | /**
 | 
        
           |  |  | 7292 |  * Returns a list of charset codes
 | 
        
           |  |  | 7293 |  *
 | 
        
           |  |  | 7294 |  * Returns a list of charset codes. It's hardcoded, so they should be added manually
 | 
        
           |  |  | 7295 |  * (checking that such charset is supported by the texlib library!)
 | 
        
           |  |  | 7296 |  *
 | 
        
           |  |  | 7297 |  * @return array And associative array with contents in the form of charset => charset
 | 
        
           |  |  | 7298 |  */
 | 
        
           | 1326 | ariadna | 7299 | function get_list_of_charsets()
 | 
        
           |  |  | 7300 | {
 | 
        
           | 1 | efrain | 7301 |   | 
        
           |  |  | 7302 |     $charsets = array(
 | 
        
           |  |  | 7303 |         'EUC-JP'     => 'EUC-JP',
 | 
        
           | 1326 | ariadna | 7304 |         'ISO-2022-JP' => 'ISO-2022-JP',
 | 
        
           | 1 | efrain | 7305 |         'ISO-8859-1' => 'ISO-8859-1',
 | 
        
           |  |  | 7306 |         'SHIFT-JIS'  => 'SHIFT-JIS',
 | 
        
           |  |  | 7307 |         'GB2312'     => 'GB2312',
 | 
        
           |  |  | 7308 |         'GB18030'    => 'GB18030', // GB18030 not supported by typo and mbstring.
 | 
        
           | 1326 | ariadna | 7309 |         'UTF-8'      => 'UTF-8'
 | 
        
           |  |  | 7310 |     );
 | 
        
           | 1 | efrain | 7311 |   | 
        
           |  |  | 7312 |     asort($charsets);
 | 
        
           |  |  | 7313 |   | 
        
           |  |  | 7314 |     return $charsets;
 | 
        
           |  |  | 7315 | }
 | 
        
           |  |  | 7316 |   | 
        
           |  |  | 7317 | /**
 | 
        
           |  |  | 7318 |  * Returns a list of valid and compatible themes
 | 
        
           |  |  | 7319 |  *
 | 
        
           |  |  | 7320 |  * @return array
 | 
        
           |  |  | 7321 |  */
 | 
        
           | 1326 | ariadna | 7322 | function get_list_of_themes()
 | 
        
           |  |  | 7323 | {
 | 
        
           | 1 | efrain | 7324 |     global $CFG;
 | 
        
           |  |  | 7325 |   | 
        
           |  |  | 7326 |     $themes = array();
 | 
        
           |  |  | 7327 |   | 
        
           |  |  | 7328 |     if (!empty($CFG->themelist)) {       // Use admin's list of themes.
 | 
        
           |  |  | 7329 |         $themelist = explode(',', $CFG->themelist);
 | 
        
           |  |  | 7330 |     } else {
 | 
        
           |  |  | 7331 |         $themelist = array_keys(core_component::get_plugin_list("theme"));
 | 
        
           |  |  | 7332 |     }
 | 
        
           |  |  | 7333 |   | 
        
           |  |  | 7334 |     foreach ($themelist as $key => $themename) {
 | 
        
           |  |  | 7335 |         $theme = theme_config::load($themename);
 | 
        
           |  |  | 7336 |         $themes[$themename] = $theme;
 | 
        
           |  |  | 7337 |     }
 | 
        
           |  |  | 7338 |   | 
        
           |  |  | 7339 |     core_collator::asort_objects_by_method($themes, 'get_theme_name');
 | 
        
           |  |  | 7340 |   | 
        
           |  |  | 7341 |     return $themes;
 | 
        
           |  |  | 7342 | }
 | 
        
           |  |  | 7343 |   | 
        
           |  |  | 7344 | /**
 | 
        
           |  |  | 7345 |  * Factory function for emoticon_manager
 | 
        
           |  |  | 7346 |  *
 | 
        
           |  |  | 7347 |  * @return emoticon_manager singleton
 | 
        
           |  |  | 7348 |  */
 | 
        
           | 1326 | ariadna | 7349 | function get_emoticon_manager()
 | 
        
           |  |  | 7350 | {
 | 
        
           | 1 | efrain | 7351 |     static $singleton = null;
 | 
        
           |  |  | 7352 |   | 
        
           |  |  | 7353 |     if (is_null($singleton)) {
 | 
        
           |  |  | 7354 |         $singleton = new emoticon_manager();
 | 
        
           |  |  | 7355 |     }
 | 
        
           |  |  | 7356 |   | 
        
           |  |  | 7357 |     return $singleton;
 | 
        
           |  |  | 7358 | }
 | 
        
           |  |  | 7359 |   | 
        
           |  |  | 7360 | /**
 | 
        
           |  |  | 7361 |  * Provides core support for plugins that have to deal with emoticons (like HTML editor or emoticon filter).
 | 
        
           |  |  | 7362 |  *
 | 
        
           |  |  | 7363 |  * Whenever this manager mentiones 'emoticon object', the following data
 | 
        
           |  |  | 7364 |  * structure is expected: stdClass with properties text, imagename, imagecomponent,
 | 
        
           |  |  | 7365 |  * altidentifier and altcomponent
 | 
        
           |  |  | 7366 |  *
 | 
        
           |  |  | 7367 |  * @see admin_setting_emoticons
 | 
        
           |  |  | 7368 |  *
 | 
        
           |  |  | 7369 |  * @copyright 2010 David Mudrak
 | 
        
           |  |  | 7370 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 7371 |  */
 | 
        
           | 1326 | ariadna | 7372 | class emoticon_manager
 | 
        
           |  |  | 7373 | {
 | 
        
           | 1 | efrain | 7374 |   | 
        
           |  |  | 7375 |     /**
 | 
        
           |  |  | 7376 |      * Returns the currently enabled emoticons
 | 
        
           |  |  | 7377 |      *
 | 
        
           |  |  | 7378 |      * @param boolean $selectable - If true, only return emoticons that should be selectable from a list.
 | 
        
           |  |  | 7379 |      * @return array of emoticon objects
 | 
        
           |  |  | 7380 |      */
 | 
        
           | 1326 | ariadna | 7381 |     public function get_emoticons($selectable = false)
 | 
        
           |  |  | 7382 |     {
 | 
        
           | 1 | efrain | 7383 |         global $CFG;
 | 
        
           |  |  | 7384 |         $notselectable = ['martin', 'egg'];
 | 
        
           |  |  | 7385 |   | 
        
           |  |  | 7386 |         if (empty($CFG->emoticons)) {
 | 
        
           |  |  | 7387 |             return array();
 | 
        
           |  |  | 7388 |         }
 | 
        
           |  |  | 7389 |   | 
        
           |  |  | 7390 |         $emoticons = $this->decode_stored_config($CFG->emoticons);
 | 
        
           |  |  | 7391 |   | 
        
           |  |  | 7392 |         if (!is_array($emoticons)) {
 | 
        
           |  |  | 7393 |             // Something is wrong with the format of stored setting.
 | 
        
           |  |  | 7394 |             debugging('Invalid format of emoticons setting, please resave the emoticons settings form', DEBUG_NORMAL);
 | 
        
           |  |  | 7395 |             return array();
 | 
        
           |  |  | 7396 |         }
 | 
        
           |  |  | 7397 |         if ($selectable) {
 | 
        
           |  |  | 7398 |             foreach ($emoticons as $index => $emote) {
 | 
        
           |  |  | 7399 |                 if (in_array($emote->altidentifier, $notselectable)) {
 | 
        
           |  |  | 7400 |                     // Skip this one.
 | 
        
           |  |  | 7401 |                     unset($emoticons[$index]);
 | 
        
           |  |  | 7402 |                 }
 | 
        
           |  |  | 7403 |             }
 | 
        
           |  |  | 7404 |         }
 | 
        
           |  |  | 7405 |   | 
        
           |  |  | 7406 |         return $emoticons;
 | 
        
           |  |  | 7407 |     }
 | 
        
           |  |  | 7408 |   | 
        
           |  |  | 7409 |     /**
 | 
        
           |  |  | 7410 |      * Converts emoticon object into renderable pix_emoticon object
 | 
        
           |  |  | 7411 |      *
 | 
        
           |  |  | 7412 |      * @param stdClass $emoticon emoticon object
 | 
        
           |  |  | 7413 |      * @param array $attributes explicit HTML attributes to set
 | 
        
           |  |  | 7414 |      * @return pix_emoticon
 | 
        
           |  |  | 7415 |      */
 | 
        
           | 1326 | ariadna | 7416 |     public function prepare_renderable_emoticon(stdClass $emoticon, array $attributes = array())
 | 
        
           |  |  | 7417 |     {
 | 
        
           | 1 | efrain | 7418 |         $stringmanager = get_string_manager();
 | 
        
           |  |  | 7419 |         if ($stringmanager->string_exists($emoticon->altidentifier, $emoticon->altcomponent)) {
 | 
        
           |  |  | 7420 |             $alt = get_string($emoticon->altidentifier, $emoticon->altcomponent);
 | 
        
           |  |  | 7421 |         } else {
 | 
        
           |  |  | 7422 |             $alt = s($emoticon->text);
 | 
        
           |  |  | 7423 |         }
 | 
        
           |  |  | 7424 |         return new pix_emoticon($emoticon->imagename, $alt, $emoticon->imagecomponent, $attributes);
 | 
        
           |  |  | 7425 |     }
 | 
        
           |  |  | 7426 |   | 
        
           |  |  | 7427 |     /**
 | 
        
           |  |  | 7428 |      * Encodes the array of emoticon objects into a string storable in config table
 | 
        
           |  |  | 7429 |      *
 | 
        
           |  |  | 7430 |      * @see self::decode_stored_config()
 | 
        
           |  |  | 7431 |      * @param array $emoticons array of emtocion objects
 | 
        
           |  |  | 7432 |      * @return string
 | 
        
           |  |  | 7433 |      */
 | 
        
           | 1326 | ariadna | 7434 |     public function encode_stored_config(array $emoticons)
 | 
        
           |  |  | 7435 |     {
 | 
        
           | 1 | efrain | 7436 |         return json_encode($emoticons);
 | 
        
           |  |  | 7437 |     }
 | 
        
           |  |  | 7438 |   | 
        
           |  |  | 7439 |     /**
 | 
        
           |  |  | 7440 |      * Decodes the string into an array of emoticon objects
 | 
        
           |  |  | 7441 |      *
 | 
        
           |  |  | 7442 |      * @see self::encode_stored_config()
 | 
        
           |  |  | 7443 |      * @param string $encoded
 | 
        
           |  |  | 7444 |      * @return array|null
 | 
        
           |  |  | 7445 |      */
 | 
        
           | 1326 | ariadna | 7446 |     public function decode_stored_config($encoded)
 | 
        
           |  |  | 7447 |     {
 | 
        
           | 1 | efrain | 7448 |         $decoded = json_decode($encoded);
 | 
        
           |  |  | 7449 |         if (!is_array($decoded)) {
 | 
        
           |  |  | 7450 |             return null;
 | 
        
           |  |  | 7451 |         }
 | 
        
           |  |  | 7452 |         return $decoded;
 | 
        
           |  |  | 7453 |     }
 | 
        
           |  |  | 7454 |   | 
        
           |  |  | 7455 |     /**
 | 
        
           |  |  | 7456 |      * Returns default set of emoticons supported by Moodle
 | 
        
           |  |  | 7457 |      *
 | 
        
           |  |  | 7458 |      * @return array of sdtClasses
 | 
        
           |  |  | 7459 |      */
 | 
        
           | 1326 | ariadna | 7460 |     public function default_emoticons()
 | 
        
           |  |  | 7461 |     {
 | 
        
           | 1 | efrain | 7462 |         return array(
 | 
        
           |  |  | 7463 |             $this->prepare_emoticon_object(":-)", 's/smiley', 'smiley'),
 | 
        
           |  |  | 7464 |             $this->prepare_emoticon_object(":)", 's/smiley', 'smiley'),
 | 
        
           |  |  | 7465 |             $this->prepare_emoticon_object(":-D", 's/biggrin', 'biggrin'),
 | 
        
           |  |  | 7466 |             $this->prepare_emoticon_object(";-)", 's/wink', 'wink'),
 | 
        
           |  |  | 7467 |             $this->prepare_emoticon_object(":-/", 's/mixed', 'mixed'),
 | 
        
           |  |  | 7468 |             $this->prepare_emoticon_object("V-.", 's/thoughtful', 'thoughtful'),
 | 
        
           |  |  | 7469 |             $this->prepare_emoticon_object(":-P", 's/tongueout', 'tongueout'),
 | 
        
           |  |  | 7470 |             $this->prepare_emoticon_object(":-p", 's/tongueout', 'tongueout'),
 | 
        
           |  |  | 7471 |             $this->prepare_emoticon_object("B-)", 's/cool', 'cool'),
 | 
        
           |  |  | 7472 |             $this->prepare_emoticon_object("^-)", 's/approve', 'approve'),
 | 
        
           |  |  | 7473 |             $this->prepare_emoticon_object("8-)", 's/wideeyes', 'wideeyes'),
 | 
        
           |  |  | 7474 |             $this->prepare_emoticon_object(":o)", 's/clown', 'clown'),
 | 
        
           |  |  | 7475 |             $this->prepare_emoticon_object(":-(", 's/sad', 'sad'),
 | 
        
           |  |  | 7476 |             $this->prepare_emoticon_object(":(", 's/sad', 'sad'),
 | 
        
           |  |  | 7477 |             $this->prepare_emoticon_object("8-.", 's/shy', 'shy'),
 | 
        
           |  |  | 7478 |             $this->prepare_emoticon_object(":-I", 's/blush', 'blush'),
 | 
        
           |  |  | 7479 |             $this->prepare_emoticon_object(":-X", 's/kiss', 'kiss'),
 | 
        
           |  |  | 7480 |             $this->prepare_emoticon_object("8-o", 's/surprise', 'surprise'),
 | 
        
           |  |  | 7481 |             $this->prepare_emoticon_object("P-|", 's/blackeye', 'blackeye'),
 | 
        
           |  |  | 7482 |             $this->prepare_emoticon_object("8-[", 's/angry', 'angry'),
 | 
        
           |  |  | 7483 |             $this->prepare_emoticon_object("(grr)", 's/angry', 'angry'),
 | 
        
           |  |  | 7484 |             $this->prepare_emoticon_object("xx-P", 's/dead', 'dead'),
 | 
        
           |  |  | 7485 |             $this->prepare_emoticon_object("|-.", 's/sleepy', 'sleepy'),
 | 
        
           |  |  | 7486 |             $this->prepare_emoticon_object("}-]", 's/evil', 'evil'),
 | 
        
           |  |  | 7487 |             $this->prepare_emoticon_object("(h)", 's/heart', 'heart'),
 | 
        
           |  |  | 7488 |             $this->prepare_emoticon_object("(heart)", 's/heart', 'heart'),
 | 
        
           |  |  | 7489 |             $this->prepare_emoticon_object("(y)", 's/yes', 'yes', 'core'),
 | 
        
           |  |  | 7490 |             $this->prepare_emoticon_object("(n)", 's/no', 'no', 'core'),
 | 
        
           |  |  | 7491 |             $this->prepare_emoticon_object("(martin)", 's/martin', 'martin'),
 | 
        
           |  |  | 7492 |             $this->prepare_emoticon_object("( )", 's/egg', 'egg'),
 | 
        
           |  |  | 7493 |         );
 | 
        
           |  |  | 7494 |     }
 | 
        
           |  |  | 7495 |   | 
        
           |  |  | 7496 |     /**
 | 
        
           |  |  | 7497 |      * Helper method preparing the stdClass with the emoticon properties
 | 
        
           |  |  | 7498 |      *
 | 
        
           |  |  | 7499 |      * @param string|array $text or array of strings
 | 
        
           |  |  | 7500 |      * @param string $imagename to be used by {@link pix_emoticon}
 | 
        
           |  |  | 7501 |      * @param string $altidentifier alternative string identifier, null for no alt
 | 
        
           |  |  | 7502 |      * @param string $altcomponent where the alternative string is defined
 | 
        
           |  |  | 7503 |      * @param string $imagecomponent to be used by {@link pix_emoticon}
 | 
        
           |  |  | 7504 |      * @return stdClass
 | 
        
           |  |  | 7505 |      */
 | 
        
           | 1326 | ariadna | 7506 |     protected function prepare_emoticon_object(
 | 
        
           |  |  | 7507 |         $text,
 | 
        
           |  |  | 7508 |         $imagename,
 | 
        
           |  |  | 7509 |         $altidentifier = null,
 | 
        
           |  |  | 7510 |         $altcomponent = 'core_pix',
 | 
        
           |  |  | 7511 |         $imagecomponent = 'core'
 | 
        
           |  |  | 7512 |     ) {
 | 
        
           | 1 | efrain | 7513 |         return (object)array(
 | 
        
           |  |  | 7514 |             'text'           => $text,
 | 
        
           |  |  | 7515 |             'imagename'      => $imagename,
 | 
        
           |  |  | 7516 |             'imagecomponent' => $imagecomponent,
 | 
        
           |  |  | 7517 |             'altidentifier'  => $altidentifier,
 | 
        
           |  |  | 7518 |             'altcomponent'   => $altcomponent,
 | 
        
           |  |  | 7519 |         );
 | 
        
           |  |  | 7520 |     }
 | 
        
           |  |  | 7521 | }
 | 
        
           |  |  | 7522 |   | 
        
           |  |  | 7523 | // ENCRYPTION.
 | 
        
           |  |  | 7524 |   | 
        
           |  |  | 7525 | /**
 | 
        
           |  |  | 7526 |  * rc4encrypt
 | 
        
           |  |  | 7527 |  *
 | 
        
           |  |  | 7528 |  * @param string $data        Data to encrypt.
 | 
        
           |  |  | 7529 |  * @return string             The now encrypted data.
 | 
        
           |  |  | 7530 |  */
 | 
        
           | 1326 | ariadna | 7531 | function rc4encrypt($data)
 | 
        
           |  |  | 7532 | {
 | 
        
           | 1 | efrain | 7533 |     return endecrypt(get_site_identifier(), $data, '');
 | 
        
           |  |  | 7534 | }
 | 
        
           |  |  | 7535 |   | 
        
           |  |  | 7536 | /**
 | 
        
           |  |  | 7537 |  * rc4decrypt
 | 
        
           |  |  | 7538 |  *
 | 
        
           |  |  | 7539 |  * @param string $data        Data to decrypt.
 | 
        
           |  |  | 7540 |  * @return string             The now decrypted data.
 | 
        
           |  |  | 7541 |  */
 | 
        
           | 1326 | ariadna | 7542 | function rc4decrypt($data)
 | 
        
           |  |  | 7543 | {
 | 
        
           | 1 | efrain | 7544 |     return endecrypt(get_site_identifier(), $data, 'de');
 | 
        
           |  |  | 7545 | }
 | 
        
           |  |  | 7546 |   | 
        
           |  |  | 7547 | /**
 | 
        
           |  |  | 7548 |  * Based on a class by Mukul Sabharwal [mukulsabharwal @ yahoo.com]
 | 
        
           |  |  | 7549 |  *
 | 
        
           |  |  | 7550 |  * @todo Finish documenting this function
 | 
        
           |  |  | 7551 |  *
 | 
        
           |  |  | 7552 |  * @param string $pwd The password to use when encrypting or decrypting
 | 
        
           |  |  | 7553 |  * @param string $data The data to be decrypted/encrypted
 | 
        
           |  |  | 7554 |  * @param string $case Either 'de' for decrypt or '' for encrypt
 | 
        
           |  |  | 7555 |  * @return string
 | 
        
           |  |  | 7556 |  */
 | 
        
           | 1326 | ariadna | 7557 | function endecrypt($pwd, $data, $case)
 | 
        
           |  |  | 7558 | {
 | 
        
           | 1 | efrain | 7559 |   | 
        
           |  |  | 7560 |     if ($case == 'de') {
 | 
        
           |  |  | 7561 |         $data = urldecode($data);
 | 
        
           |  |  | 7562 |     }
 | 
        
           |  |  | 7563 |   | 
        
           |  |  | 7564 |     $key[] = '';
 | 
        
           |  |  | 7565 |     $box[] = '';
 | 
        
           |  |  | 7566 |     $pwdlength = strlen($pwd);
 | 
        
           |  |  | 7567 |   | 
        
           |  |  | 7568 |     for ($i = 0; $i <= 255; $i++) {
 | 
        
           |  |  | 7569 |         $key[$i] = ord(substr($pwd, ($i % $pwdlength), 1));
 | 
        
           |  |  | 7570 |         $box[$i] = $i;
 | 
        
           |  |  | 7571 |     }
 | 
        
           |  |  | 7572 |   | 
        
           |  |  | 7573 |     $x = 0;
 | 
        
           |  |  | 7574 |   | 
        
           |  |  | 7575 |     for ($i = 0; $i <= 255; $i++) {
 | 
        
           |  |  | 7576 |         $x = ($x + $box[$i] + $key[$i]) % 256;
 | 
        
           |  |  | 7577 |         $tempswap = $box[$i];
 | 
        
           |  |  | 7578 |         $box[$i] = $box[$x];
 | 
        
           |  |  | 7579 |         $box[$x] = $tempswap;
 | 
        
           |  |  | 7580 |     }
 | 
        
           |  |  | 7581 |   | 
        
           |  |  | 7582 |     $cipher = '';
 | 
        
           |  |  | 7583 |   | 
        
           |  |  | 7584 |     $a = 0;
 | 
        
           |  |  | 7585 |     $j = 0;
 | 
        
           |  |  | 7586 |   | 
        
           |  |  | 7587 |     for ($i = 0; $i < strlen($data); $i++) {
 | 
        
           |  |  | 7588 |         $a = ($a + 1) % 256;
 | 
        
           |  |  | 7589 |         $j = ($j + $box[$a]) % 256;
 | 
        
           |  |  | 7590 |         $temp = $box[$a];
 | 
        
           |  |  | 7591 |         $box[$a] = $box[$j];
 | 
        
           |  |  | 7592 |         $box[$j] = $temp;
 | 
        
           |  |  | 7593 |         $k = $box[(($box[$a] + $box[$j]) % 256)];
 | 
        
           |  |  | 7594 |         $cipherby = ord(substr($data, $i, 1)) ^ $k;
 | 
        
           |  |  | 7595 |         $cipher .= chr($cipherby);
 | 
        
           |  |  | 7596 |     }
 | 
        
           |  |  | 7597 |   | 
        
           |  |  | 7598 |     if ($case == 'de') {
 | 
        
           |  |  | 7599 |         $cipher = urldecode(urlencode($cipher));
 | 
        
           |  |  | 7600 |     } else {
 | 
        
           |  |  | 7601 |         $cipher = urlencode($cipher);
 | 
        
           |  |  | 7602 |     }
 | 
        
           |  |  | 7603 |   | 
        
           |  |  | 7604 |     return $cipher;
 | 
        
           |  |  | 7605 | }
 | 
        
           |  |  | 7606 |   | 
        
           |  |  | 7607 | // ENVIRONMENT CHECKING.
 | 
        
           |  |  | 7608 |   | 
        
           |  |  | 7609 | /**
 | 
        
           |  |  | 7610 |  * This method validates a plug name. It is much faster than calling clean_param.
 | 
        
           |  |  | 7611 |  *
 | 
        
           |  |  | 7612 |  * @param string $name a string that might be a plugin name.
 | 
        
           |  |  | 7613 |  * @return bool if this string is a valid plugin name.
 | 
        
           |  |  | 7614 |  */
 | 
        
           | 1326 | ariadna | 7615 | function is_valid_plugin_name($name)
 | 
        
           |  |  | 7616 | {
 | 
        
           | 1 | efrain | 7617 |     // This does not work for 'mod', bad luck, use any other type.
 | 
        
           |  |  | 7618 |     return core_component::is_valid_plugin_name('tool', $name);
 | 
        
           |  |  | 7619 | }
 | 
        
           |  |  | 7620 |   | 
        
           |  |  | 7621 | /**
 | 
        
           |  |  | 7622 |  * Get a list of all the plugins of a given type that define a certain API function
 | 
        
           |  |  | 7623 |  * in a certain file. The plugin component names and function names are returned.
 | 
        
           |  |  | 7624 |  *
 | 
        
           |  |  | 7625 |  * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
 | 
        
           |  |  | 7626 |  * @param string $function the part of the name of the function after the
 | 
        
           |  |  | 7627 |  *      frankenstyle prefix. e.g 'hook' if you are looking for functions with
 | 
        
           |  |  | 7628 |  *      names like report_courselist_hook.
 | 
        
           |  |  | 7629 |  * @param string $file the name of file within the plugin that defines the
 | 
        
           |  |  | 7630 |  *      function. Defaults to lib.php.
 | 
        
           |  |  | 7631 |  * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
 | 
        
           |  |  | 7632 |  *      and the function names as values (e.g. 'report_courselist_hook', 'forum_hook').
 | 
        
           |  |  | 7633 |  */
 | 
        
           | 1326 | ariadna | 7634 | function get_plugin_list_with_function($plugintype, $function, $file = 'lib.php')
 | 
        
           |  |  | 7635 | {
 | 
        
           | 1 | efrain | 7636 |     global $CFG;
 | 
        
           |  |  | 7637 |   | 
        
           |  |  | 7638 |     // We don't include here as all plugin types files would be included.
 | 
        
           |  |  | 7639 |     $plugins = get_plugins_with_function($function, $file, false);
 | 
        
           |  |  | 7640 |   | 
        
           |  |  | 7641 |     if (empty($plugins[$plugintype])) {
 | 
        
           |  |  | 7642 |         return array();
 | 
        
           |  |  | 7643 |     }
 | 
        
           |  |  | 7644 |   | 
        
           |  |  | 7645 |     $allplugins = core_component::get_plugin_list($plugintype);
 | 
        
           |  |  | 7646 |   | 
        
           |  |  | 7647 |     // Reformat the array and include the files.
 | 
        
           |  |  | 7648 |     $pluginfunctions = array();
 | 
        
           |  |  | 7649 |     foreach ($plugins[$plugintype] as $pluginname => $functionname) {
 | 
        
           |  |  | 7650 |   | 
        
           |  |  | 7651 |         // Check that it has not been removed and the file is still available.
 | 
        
           |  |  | 7652 |         if (!empty($allplugins[$pluginname])) {
 | 
        
           |  |  | 7653 |   | 
        
           |  |  | 7654 |             $filepath = $allplugins[$pluginname] . DIRECTORY_SEPARATOR . $file;
 | 
        
           |  |  | 7655 |             if (file_exists($filepath)) {
 | 
        
           |  |  | 7656 |                 include_once($filepath);
 | 
        
           |  |  | 7657 |   | 
        
           |  |  | 7658 |                 // Now that the file is loaded, we must verify the function still exists.
 | 
        
           |  |  | 7659 |                 if (function_exists($functionname)) {
 | 
        
           |  |  | 7660 |                     $pluginfunctions[$plugintype . '_' . $pluginname] = $functionname;
 | 
        
           |  |  | 7661 |                 } else {
 | 
        
           |  |  | 7662 |                     // Invalidate the cache for next run.
 | 
        
           |  |  | 7663 |                     \cache_helper::invalidate_by_definition('core', 'plugin_functions');
 | 
        
           |  |  | 7664 |                 }
 | 
        
           |  |  | 7665 |             }
 | 
        
           |  |  | 7666 |         }
 | 
        
           |  |  | 7667 |     }
 | 
        
           |  |  | 7668 |   | 
        
           |  |  | 7669 |     return $pluginfunctions;
 | 
        
           |  |  | 7670 | }
 | 
        
           |  |  | 7671 |   | 
        
           |  |  | 7672 | /**
 | 
        
           |  |  | 7673 |  * Get a list of all the plugins that define a certain API function in a certain file.
 | 
        
           |  |  | 7674 |  *
 | 
        
           |  |  | 7675 |  * @param string $function the part of the name of the function after the
 | 
        
           |  |  | 7676 |  *      frankenstyle prefix. e.g 'hook' if you are looking for functions with
 | 
        
           |  |  | 7677 |  *      names like report_courselist_hook.
 | 
        
           |  |  | 7678 |  * @param string $file the name of file within the plugin that defines the
 | 
        
           |  |  | 7679 |  *      function. Defaults to lib.php.
 | 
        
           |  |  | 7680 |  * @param bool $include Whether to include the files that contain the functions or not.
 | 
        
           |  |  | 7681 |  * @param bool $migratedtohook if true this is a deprecated lib.php callback, if hook callback is present then do nothing
 | 
        
           |  |  | 7682 |  * @return array with [plugintype][plugin] = functionname
 | 
        
           |  |  | 7683 |  */
 | 
        
           | 1326 | ariadna | 7684 | function get_plugins_with_function($function, $file = 'lib.php', $include = true, bool $migratedtohook = false)
 | 
        
           |  |  | 7685 | {
 | 
        
           | 1 | efrain | 7686 |     global $CFG;
 | 
        
           |  |  | 7687 |   | 
        
           |  |  | 7688 |     if (during_initial_install() || isset($CFG->upgraderunning)) {
 | 
        
           |  |  | 7689 |         // API functions _must not_ be called during an installation or upgrade.
 | 
        
           |  |  | 7690 |         return [];
 | 
        
           |  |  | 7691 |     }
 | 
        
           |  |  | 7692 |   | 
        
           |  |  | 7693 |     $plugincallback = $function;
 | 
        
           | 1326 | ariadna | 7694 |     $filtermigrated = function ($plugincallback, $pluginfunctions): array {
 | 
        
           | 1 | efrain | 7695 |         foreach ($pluginfunctions as $plugintype => $plugins) {
 | 
        
           |  |  | 7696 |             foreach ($plugins as $plugin => $unusedfunction) {
 | 
        
           |  |  | 7697 |                 $component = $plugintype . '_' . $plugin;
 | 
        
           |  |  | 7698 |                 if ($hooks = di::get(hook\manager::class)->get_hooks_deprecating_plugin_callback($plugincallback)) {
 | 
        
           |  |  | 7699 |                     if (di::get(hook\manager::class)->is_deprecating_hook_present($component, $plugincallback)) {
 | 
        
           |  |  | 7700 |                         // Ignore the old callback, it is there only for older Moodle versions.
 | 
        
           |  |  | 7701 |                         unset($pluginfunctions[$plugintype][$plugin]);
 | 
        
           |  |  | 7702 |                     } else {
 | 
        
           |  |  | 7703 |                         $hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of  ' . implode(', ', $hooks);
 | 
        
           |  |  | 7704 |                         debugging(
 | 
        
           |  |  | 7705 |                             "Callback $plugincallback in $component component should be migrated to new " .
 | 
        
           |  |  | 7706 |                                 "hook callback for $hookmessage",
 | 
        
           |  |  | 7707 |                             DEBUG_DEVELOPER
 | 
        
           |  |  | 7708 |                         );
 | 
        
           |  |  | 7709 |                     }
 | 
        
           |  |  | 7710 |                 }
 | 
        
           |  |  | 7711 |             }
 | 
        
           |  |  | 7712 |         }
 | 
        
           |  |  | 7713 |         return $pluginfunctions;
 | 
        
           |  |  | 7714 |     };
 | 
        
           |  |  | 7715 |   | 
        
           |  |  | 7716 |     $cache = \cache::make('core', 'plugin_functions');
 | 
        
           |  |  | 7717 |   | 
        
           |  |  | 7718 |     // Including both although I doubt that we will find two functions definitions with the same name.
 | 
        
           |  |  | 7719 |     // Clean the filename as cache_helper::hash_key only allows a-zA-Z0-9_.
 | 
        
           |  |  | 7720 |     $pluginfunctions = false;
 | 
        
           |  |  | 7721 |     if (!empty($CFG->allversionshash)) {
 | 
        
           |  |  | 7722 |         $key = $CFG->allversionshash . '_' . $function . '_' . clean_param($file, PARAM_ALPHA);
 | 
        
           |  |  | 7723 |         $pluginfunctions = $cache->get($key);
 | 
        
           |  |  | 7724 |     }
 | 
        
           |  |  | 7725 |     $dirty = false;
 | 
        
           |  |  | 7726 |   | 
        
           |  |  | 7727 |     // Use the plugin manager to check that plugins are currently installed.
 | 
        
           |  |  | 7728 |     $pluginmanager = \core_plugin_manager::instance();
 | 
        
           |  |  | 7729 |   | 
        
           |  |  | 7730 |     if ($pluginfunctions !== false) {
 | 
        
           |  |  | 7731 |   | 
        
           |  |  | 7732 |         // Checking that the files are still available.
 | 
        
           |  |  | 7733 |         foreach ($pluginfunctions as $plugintype => $plugins) {
 | 
        
           |  |  | 7734 |   | 
        
           |  |  | 7735 |             $allplugins = \core_component::get_plugin_list($plugintype);
 | 
        
           |  |  | 7736 |             $installedplugins = $pluginmanager->get_installed_plugins($plugintype);
 | 
        
           |  |  | 7737 |             foreach ($plugins as $plugin => $function) {
 | 
        
           |  |  | 7738 |                 if (!isset($installedplugins[$plugin])) {
 | 
        
           |  |  | 7739 |                     // Plugin code is still present on disk but it is not installed.
 | 
        
           |  |  | 7740 |                     $dirty = true;
 | 
        
           |  |  | 7741 |                     break 2;
 | 
        
           |  |  | 7742 |                 }
 | 
        
           |  |  | 7743 |   | 
        
           |  |  | 7744 |                 // Cache might be out of sync with the codebase, skip the plugin if it is not available.
 | 
        
           |  |  | 7745 |                 if (empty($allplugins[$plugin])) {
 | 
        
           |  |  | 7746 |                     $dirty = true;
 | 
        
           |  |  | 7747 |                     break 2;
 | 
        
           |  |  | 7748 |                 }
 | 
        
           |  |  | 7749 |   | 
        
           |  |  | 7750 |                 $fileexists = file_exists($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
 | 
        
           |  |  | 7751 |                 if ($include && $fileexists) {
 | 
        
           |  |  | 7752 |                     // Include the files if it was requested.
 | 
        
           |  |  | 7753 |                     include_once($allplugins[$plugin] . DIRECTORY_SEPARATOR . $file);
 | 
        
           |  |  | 7754 |                 } else if (!$fileexists) {
 | 
        
           |  |  | 7755 |                     // If the file is not available any more it should not be returned.
 | 
        
           |  |  | 7756 |                     $dirty = true;
 | 
        
           |  |  | 7757 |                     break 2;
 | 
        
           |  |  | 7758 |                 }
 | 
        
           |  |  | 7759 |   | 
        
           |  |  | 7760 |                 // Check if the function still exists in the file.
 | 
        
           |  |  | 7761 |                 if ($include && !function_exists($function)) {
 | 
        
           |  |  | 7762 |                     $dirty = true;
 | 
        
           |  |  | 7763 |                     break 2;
 | 
        
           |  |  | 7764 |                 }
 | 
        
           |  |  | 7765 |             }
 | 
        
           |  |  | 7766 |         }
 | 
        
           |  |  | 7767 |   | 
        
           |  |  | 7768 |         // If the cache is dirty, we should fall through and let it rebuild.
 | 
        
           |  |  | 7769 |         if (!$dirty) {
 | 
        
           |  |  | 7770 |             if ($migratedtohook && $file === 'lib.php') {
 | 
        
           |  |  | 7771 |                 $pluginfunctions = $filtermigrated($plugincallback, $pluginfunctions);
 | 
        
           |  |  | 7772 |             }
 | 
        
           |  |  | 7773 |             return $pluginfunctions;
 | 
        
           |  |  | 7774 |         }
 | 
        
           |  |  | 7775 |     }
 | 
        
           |  |  | 7776 |   | 
        
           |  |  | 7777 |     $pluginfunctions = array();
 | 
        
           |  |  | 7778 |   | 
        
           |  |  | 7779 |     // To fill the cached. Also, everything should continue working with cache disabled.
 | 
        
           |  |  | 7780 |     $plugintypes = \core_component::get_plugin_types();
 | 
        
           |  |  | 7781 |     foreach ($plugintypes as $plugintype => $unused) {
 | 
        
           |  |  | 7782 |   | 
        
           |  |  | 7783 |         // We need to include files here.
 | 
        
           |  |  | 7784 |         $pluginswithfile = \core_component::get_plugin_list_with_file($plugintype, $file, true);
 | 
        
           |  |  | 7785 |         $installedplugins = $pluginmanager->get_installed_plugins($plugintype);
 | 
        
           |  |  | 7786 |         foreach ($pluginswithfile as $plugin => $notused) {
 | 
        
           |  |  | 7787 |   | 
        
           |  |  | 7788 |             if (!isset($installedplugins[$plugin])) {
 | 
        
           |  |  | 7789 |                 continue;
 | 
        
           |  |  | 7790 |             }
 | 
        
           |  |  | 7791 |   | 
        
           |  |  | 7792 |             $fullfunction = $plugintype . '_' . $plugin . '_' . $function;
 | 
        
           |  |  | 7793 |   | 
        
           |  |  | 7794 |             $pluginfunction = false;
 | 
        
           |  |  | 7795 |             if (function_exists($fullfunction)) {
 | 
        
           |  |  | 7796 |                 // Function exists with standard name. Store, indexed by frankenstyle name of plugin.
 | 
        
           |  |  | 7797 |                 $pluginfunction = $fullfunction;
 | 
        
           |  |  | 7798 |             } else if ($plugintype === 'mod') {
 | 
        
           |  |  | 7799 |                 // For modules, we also allow plugin without full frankenstyle but just starting with the module name.
 | 
        
           |  |  | 7800 |                 $shortfunction = $plugin . '_' . $function;
 | 
        
           |  |  | 7801 |                 if (function_exists($shortfunction)) {
 | 
        
           |  |  | 7802 |                     $pluginfunction = $shortfunction;
 | 
        
           |  |  | 7803 |                 }
 | 
        
           |  |  | 7804 |             }
 | 
        
           |  |  | 7805 |   | 
        
           |  |  | 7806 |             if ($pluginfunction) {
 | 
        
           |  |  | 7807 |                 if (empty($pluginfunctions[$plugintype])) {
 | 
        
           |  |  | 7808 |                     $pluginfunctions[$plugintype] = array();
 | 
        
           |  |  | 7809 |                 }
 | 
        
           |  |  | 7810 |                 $pluginfunctions[$plugintype][$plugin] = $pluginfunction;
 | 
        
           |  |  | 7811 |             }
 | 
        
           |  |  | 7812 |         }
 | 
        
           |  |  | 7813 |     }
 | 
        
           |  |  | 7814 |     if (!empty($CFG->allversionshash)) {
 | 
        
           |  |  | 7815 |         $cache->set($key, $pluginfunctions);
 | 
        
           |  |  | 7816 |     }
 | 
        
           |  |  | 7817 |   | 
        
           |  |  | 7818 |     if ($migratedtohook && $file === 'lib.php') {
 | 
        
           |  |  | 7819 |         $pluginfunctions = $filtermigrated($plugincallback, $pluginfunctions);
 | 
        
           |  |  | 7820 |     }
 | 
        
           |  |  | 7821 |   | 
        
           |  |  | 7822 |     return $pluginfunctions;
 | 
        
           |  |  | 7823 | }
 | 
        
           |  |  | 7824 |   | 
        
           |  |  | 7825 | /**
 | 
        
           |  |  | 7826 |  * Lists plugin-like directories within specified directory
 | 
        
           |  |  | 7827 |  *
 | 
        
           |  |  | 7828 |  * This function was originally used for standard Moodle plugins, please use
 | 
        
           |  |  | 7829 |  * new core_component::get_plugin_list() now.
 | 
        
           |  |  | 7830 |  *
 | 
        
           |  |  | 7831 |  * This function is used for general directory listing and backwards compatility.
 | 
        
           |  |  | 7832 |  *
 | 
        
           |  |  | 7833 |  * @param string $directory relative directory from root
 | 
        
           |  |  | 7834 |  * @param string $exclude dir name to exclude from the list (defaults to none)
 | 
        
           |  |  | 7835 |  * @param string $basedir full path to the base dir where $plugin resides (defaults to $CFG->dirroot)
 | 
        
           |  |  | 7836 |  * @return array Sorted array of directory names found under the requested parameters
 | 
        
           |  |  | 7837 |  */
 | 
        
           | 1326 | ariadna | 7838 | function get_list_of_plugins($directory = 'mod', $exclude = '', $basedir = '')
 | 
        
           |  |  | 7839 | {
 | 
        
           | 1 | efrain | 7840 |     global $CFG;
 | 
        
           |  |  | 7841 |   | 
        
           |  |  | 7842 |     $plugins = array();
 | 
        
           |  |  | 7843 |   | 
        
           |  |  | 7844 |     if (empty($basedir)) {
 | 
        
           | 1326 | ariadna | 7845 |         $basedir = $CFG->dirroot . '/' . $directory;
 | 
        
           | 1 | efrain | 7846 |     } else {
 | 
        
           | 1326 | ariadna | 7847 |         $basedir = $basedir . '/' . $directory;
 | 
        
           | 1 | efrain | 7848 |     }
 | 
        
           |  |  | 7849 |   | 
        
           |  |  | 7850 |     if ($CFG->debugdeveloper and empty($exclude)) {
 | 
        
           |  |  | 7851 |         // Make sure devs do not use this to list normal plugins,
 | 
        
           |  |  | 7852 |         // this is intended for general directories that are not plugins!
 | 
        
           |  |  | 7853 |   | 
        
           |  |  | 7854 |         $subtypes = core_component::get_plugin_types();
 | 
        
           |  |  | 7855 |         if (in_array($basedir, $subtypes)) {
 | 
        
           |  |  | 7856 |             debugging('get_list_of_plugins() should not be used to list real plugins, use core_component::get_plugin_list() instead!', DEBUG_DEVELOPER);
 | 
        
           |  |  | 7857 |         }
 | 
        
           |  |  | 7858 |         unset($subtypes);
 | 
        
           |  |  | 7859 |     }
 | 
        
           |  |  | 7860 |   | 
        
           |  |  | 7861 |     $ignorelist = array_flip(array_filter([
 | 
        
           |  |  | 7862 |         'CVS',
 | 
        
           |  |  | 7863 |         '_vti_cnf',
 | 
        
           |  |  | 7864 |         'amd',
 | 
        
           |  |  | 7865 |         'classes',
 | 
        
           |  |  | 7866 |         'simpletest',
 | 
        
           |  |  | 7867 |         'tests',
 | 
        
           |  |  | 7868 |         'templates',
 | 
        
           |  |  | 7869 |         'yui',
 | 
        
           |  |  | 7870 |         $exclude,
 | 
        
           |  |  | 7871 |     ]));
 | 
        
           |  |  | 7872 |   | 
        
           |  |  | 7873 |     if (file_exists($basedir) && filetype($basedir) == 'dir') {
 | 
        
           |  |  | 7874 |         if (!$dirhandle = opendir($basedir)) {
 | 
        
           |  |  | 7875 |             debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
 | 
        
           |  |  | 7876 |             return array();
 | 
        
           |  |  | 7877 |         }
 | 
        
           |  |  | 7878 |         while (false !== ($dir = readdir($dirhandle))) {
 | 
        
           |  |  | 7879 |             if (strpos($dir, '.') === 0) {
 | 
        
           |  |  | 7880 |                 // Ignore directories starting with .
 | 
        
           |  |  | 7881 |                 // These are treated as hidden directories.
 | 
        
           |  |  | 7882 |                 continue;
 | 
        
           |  |  | 7883 |             }
 | 
        
           |  |  | 7884 |             if (array_key_exists($dir, $ignorelist)) {
 | 
        
           |  |  | 7885 |                 // This directory features on the ignore list.
 | 
        
           |  |  | 7886 |                 continue;
 | 
        
           |  |  | 7887 |             }
 | 
        
           | 1326 | ariadna | 7888 |             if (filetype($basedir . '/' . $dir) != 'dir') {
 | 
        
           | 1 | efrain | 7889 |                 continue;
 | 
        
           |  |  | 7890 |             }
 | 
        
           |  |  | 7891 |             $plugins[] = $dir;
 | 
        
           |  |  | 7892 |         }
 | 
        
           |  |  | 7893 |         closedir($dirhandle);
 | 
        
           |  |  | 7894 |     }
 | 
        
           |  |  | 7895 |     if ($plugins) {
 | 
        
           |  |  | 7896 |         asort($plugins);
 | 
        
           |  |  | 7897 |     }
 | 
        
           |  |  | 7898 |     return $plugins;
 | 
        
           |  |  | 7899 | }
 | 
        
           |  |  | 7900 |   | 
        
           |  |  | 7901 | /**
 | 
        
           |  |  | 7902 |  * Invoke plugin's callback functions
 | 
        
           |  |  | 7903 |  *
 | 
        
           |  |  | 7904 |  * @param string $type plugin type e.g. 'mod'
 | 
        
           |  |  | 7905 |  * @param string $name plugin name
 | 
        
           |  |  | 7906 |  * @param string $feature feature name
 | 
        
           |  |  | 7907 |  * @param string $action feature's action
 | 
        
           |  |  | 7908 |  * @param array $params parameters of callback function, should be an array
 | 
        
           |  |  | 7909 |  * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
 | 
        
           |  |  | 7910 |  * @param bool $migratedtohook if true this is a deprecated callback, if hook callback is present then do nothing
 | 
        
           |  |  | 7911 |  * @return mixed
 | 
        
           |  |  | 7912 |  *
 | 
        
           |  |  | 7913 |  * @todo Decide about to deprecate and drop plugin_callback() - MDL-30743
 | 
        
           |  |  | 7914 |  */
 | 
        
           | 1326 | ariadna | 7915 | function plugin_callback($type, $name, $feature, $action, $params = null, $default = null, bool $migratedtohook = false)
 | 
        
           |  |  | 7916 | {
 | 
        
           | 1 | efrain | 7917 |     return component_callback($type . '_' . $name, $feature . '_' . $action, (array) $params, $default, $migratedtohook);
 | 
        
           |  |  | 7918 | }
 | 
        
           |  |  | 7919 |   | 
        
           |  |  | 7920 | /**
 | 
        
           |  |  | 7921 |  * Invoke component's callback functions
 | 
        
           |  |  | 7922 |  *
 | 
        
           |  |  | 7923 |  * @param string $component frankenstyle component name, e.g. 'mod_quiz'
 | 
        
           |  |  | 7924 |  * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
 | 
        
           |  |  | 7925 |  * @param array $params parameters of callback function
 | 
        
           |  |  | 7926 |  * @param mixed $default default value if callback function hasn't been defined, or if it retursn null.
 | 
        
           |  |  | 7927 |  * @param bool $migratedtohook if true this is a deprecated callback, if hook callback is present then do nothing
 | 
        
           |  |  | 7928 |  * @return mixed
 | 
        
           |  |  | 7929 |  */
 | 
        
           | 1326 | ariadna | 7930 | function component_callback($component, $function, array $params = array(), $default = null, bool $migratedtohook = false)
 | 
        
           |  |  | 7931 | {
 | 
        
           | 1 | efrain | 7932 |     $functionname = component_callback_exists($component, $function);
 | 
        
           |  |  | 7933 |   | 
        
           |  |  | 7934 |     if ($functionname) {
 | 
        
           |  |  | 7935 |         if ($migratedtohook) {
 | 
        
           |  |  | 7936 |             $hookmanager = di::get(hook\manager::class);
 | 
        
           |  |  | 7937 |             if ($hooks = $hookmanager->get_hooks_deprecating_plugin_callback($function)) {
 | 
        
           |  |  | 7938 |                 if ($hookmanager->is_deprecating_hook_present($component, $function)) {
 | 
        
           |  |  | 7939 |                     // Do not call the old lib.php callback,
 | 
        
           |  |  | 7940 |                     // it is there for compatibility with older Moodle versions only.
 | 
        
           |  |  | 7941 |                     return null;
 | 
        
           |  |  | 7942 |                 } else {
 | 
        
           |  |  | 7943 |                     $hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of  ' . implode(', ', $hooks);
 | 
        
           |  |  | 7944 |                     debugging(
 | 
        
           |  |  | 7945 |                         "Callback $function in $component component should be migrated to new hook callback for $hookmessage",
 | 
        
           | 1326 | ariadna | 7946 |                         DEBUG_DEVELOPER
 | 
        
           |  |  | 7947 |                     );
 | 
        
           | 1 | efrain | 7948 |                 }
 | 
        
           |  |  | 7949 |             }
 | 
        
           |  |  | 7950 |         }
 | 
        
           |  |  | 7951 |   | 
        
           |  |  | 7952 |         // Function exists, so just return function result.
 | 
        
           |  |  | 7953 |         $ret = call_user_func_array($functionname, $params);
 | 
        
           |  |  | 7954 |         if (is_null($ret)) {
 | 
        
           |  |  | 7955 |             return $default;
 | 
        
           |  |  | 7956 |         } else {
 | 
        
           |  |  | 7957 |             return $ret;
 | 
        
           |  |  | 7958 |         }
 | 
        
           |  |  | 7959 |     }
 | 
        
           |  |  | 7960 |     return $default;
 | 
        
           |  |  | 7961 | }
 | 
        
           |  |  | 7962 |   | 
        
           |  |  | 7963 | /**
 | 
        
           |  |  | 7964 |  * Determine if a component callback exists and return the function name to call. Note that this
 | 
        
           |  |  | 7965 |  * function will include the required library files so that the functioname returned can be
 | 
        
           |  |  | 7966 |  * called directly.
 | 
        
           |  |  | 7967 |  *
 | 
        
           |  |  | 7968 |  * @param string $component frankenstyle component name, e.g. 'mod_quiz'
 | 
        
           |  |  | 7969 |  * @param string $function the rest of the function name, e.g. 'cron' will end up calling 'mod_quiz_cron'
 | 
        
           |  |  | 7970 |  * @return mixed Complete function name to call if the callback exists or false if it doesn't.
 | 
        
           |  |  | 7971 |  * @throws coding_exception if invalid component specfied
 | 
        
           |  |  | 7972 |  */
 | 
        
           | 1326 | ariadna | 7973 | function component_callback_exists($component, $function)
 | 
        
           |  |  | 7974 | {
 | 
        
           | 1 | efrain | 7975 |     global $CFG; // This is needed for the inclusions.
 | 
        
           |  |  | 7976 |   | 
        
           |  |  | 7977 |     $cleancomponent = clean_param($component, PARAM_COMPONENT);
 | 
        
           |  |  | 7978 |     if (empty($cleancomponent)) {
 | 
        
           |  |  | 7979 |         throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
 | 
        
           |  |  | 7980 |     }
 | 
        
           |  |  | 7981 |     $component = $cleancomponent;
 | 
        
           |  |  | 7982 |   | 
        
           |  |  | 7983 |     list($type, $name) = core_component::normalize_component($component);
 | 
        
           |  |  | 7984 |     $component = $type . '_' . $name;
 | 
        
           |  |  | 7985 |   | 
        
           | 1326 | ariadna | 7986 |     $oldfunction = $name . '_' . $function;
 | 
        
           |  |  | 7987 |     $function = $component . '_' . $function;
 | 
        
           | 1 | efrain | 7988 |   | 
        
           |  |  | 7989 |     $dir = core_component::get_component_directory($component);
 | 
        
           |  |  | 7990 |     if (empty($dir)) {
 | 
        
           |  |  | 7991 |         throw new coding_exception('Invalid component used in plugin/component_callback():' . $component);
 | 
        
           |  |  | 7992 |     }
 | 
        
           |  |  | 7993 |   | 
        
           |  |  | 7994 |     // Load library and look for function.
 | 
        
           | 1326 | ariadna | 7995 |     if (file_exists($dir . '/lib.php')) {
 | 
        
           |  |  | 7996 |         require_once($dir . '/lib.php');
 | 
        
           | 1 | efrain | 7997 |     }
 | 
        
           |  |  | 7998 |   | 
        
           |  |  | 7999 |     if (!function_exists($function) and function_exists($oldfunction)) {
 | 
        
           |  |  | 8000 |         if ($type !== 'mod' and $type !== 'core') {
 | 
        
           |  |  | 8001 |             debugging("Please use new function name $function instead of legacy $oldfunction", DEBUG_DEVELOPER);
 | 
        
           |  |  | 8002 |         }
 | 
        
           |  |  | 8003 |         $function = $oldfunction;
 | 
        
           |  |  | 8004 |     }
 | 
        
           |  |  | 8005 |   | 
        
           |  |  | 8006 |     if (function_exists($function)) {
 | 
        
           |  |  | 8007 |         return $function;
 | 
        
           |  |  | 8008 |     }
 | 
        
           |  |  | 8009 |     return false;
 | 
        
           |  |  | 8010 | }
 | 
        
           |  |  | 8011 |   | 
        
           |  |  | 8012 | /**
 | 
        
           |  |  | 8013 |  * Call the specified callback method on the provided class.
 | 
        
           |  |  | 8014 |  *
 | 
        
           |  |  | 8015 |  * If the callback returns null, then the default value is returned instead.
 | 
        
           |  |  | 8016 |  * If the class does not exist, then the default value is returned.
 | 
        
           |  |  | 8017 |  *
 | 
        
           |  |  | 8018 |  * @param   string      $classname The name of the class to call upon.
 | 
        
           |  |  | 8019 |  * @param   string      $methodname The name of the staticically defined method on the class.
 | 
        
           |  |  | 8020 |  * @param   array       $params The arguments to pass into the method.
 | 
        
           |  |  | 8021 |  * @param   mixed       $default The default value.
 | 
        
           |  |  | 8022 |  * @param   bool        $migratedtohook True if the callback has been migrated to a hook.
 | 
        
           |  |  | 8023 |  * @return  mixed       The return value.
 | 
        
           |  |  | 8024 |  */
 | 
        
           | 1326 | ariadna | 8025 | function component_class_callback($classname, $methodname, array $params, $default = null, bool $migratedtohook = false)
 | 
        
           |  |  | 8026 | {
 | 
        
           | 1 | efrain | 8027 |     if (!class_exists($classname)) {
 | 
        
           |  |  | 8028 |         return $default;
 | 
        
           |  |  | 8029 |     }
 | 
        
           |  |  | 8030 |   | 
        
           |  |  | 8031 |     if (!method_exists($classname, $methodname)) {
 | 
        
           |  |  | 8032 |         return $default;
 | 
        
           |  |  | 8033 |     }
 | 
        
           |  |  | 8034 |   | 
        
           |  |  | 8035 |     $fullfunction = $classname . '::' . $methodname;
 | 
        
           |  |  | 8036 |   | 
        
           |  |  | 8037 |     if ($migratedtohook) {
 | 
        
           |  |  | 8038 |         $functionparts = explode('\\', trim($fullfunction, '\\'));
 | 
        
           |  |  | 8039 |         $component = $functionparts[0];
 | 
        
           |  |  | 8040 |         $callback = end($functionparts);
 | 
        
           |  |  | 8041 |         $hookmanager = di::get(hook\manager::class);
 | 
        
           |  |  | 8042 |         if ($hooks = $hookmanager->get_hooks_deprecating_plugin_callback($callback)) {
 | 
        
           |  |  | 8043 |             if ($hookmanager->is_deprecating_hook_present($component, $callback)) {
 | 
        
           |  |  | 8044 |                 // Do not call the old class callback,
 | 
        
           |  |  | 8045 |                 // it is there for compatibility with older Moodle versions only.
 | 
        
           |  |  | 8046 |                 return null;
 | 
        
           |  |  | 8047 |             } else {
 | 
        
           |  |  | 8048 |                 $hookmessage = count($hooks) == 1 ? reset($hooks) : 'one of  ' . implode(', ', $hooks);
 | 
        
           | 1326 | ariadna | 8049 |                 debugging(
 | 
        
           |  |  | 8050 |                     "Callback $callback in $component component should be migrated to new hook callback for $hookmessage",
 | 
        
           |  |  | 8051 |                     DEBUG_DEVELOPER
 | 
        
           |  |  | 8052 |                 );
 | 
        
           | 1 | efrain | 8053 |             }
 | 
        
           |  |  | 8054 |         }
 | 
        
           |  |  | 8055 |     }
 | 
        
           |  |  | 8056 |   | 
        
           |  |  | 8057 |     $result = call_user_func_array($fullfunction, $params);
 | 
        
           |  |  | 8058 |   | 
        
           |  |  | 8059 |     if (null === $result) {
 | 
        
           |  |  | 8060 |         return $default;
 | 
        
           |  |  | 8061 |     } else {
 | 
        
           |  |  | 8062 |         return $result;
 | 
        
           |  |  | 8063 |     }
 | 
        
           |  |  | 8064 | }
 | 
        
           |  |  | 8065 |   | 
        
           |  |  | 8066 | /**
 | 
        
           |  |  | 8067 |  * Checks whether a plugin supports a specified feature.
 | 
        
           |  |  | 8068 |  *
 | 
        
           |  |  | 8069 |  * @param string $type Plugin type e.g. 'mod'
 | 
        
           |  |  | 8070 |  * @param string $name Plugin name e.g. 'forum'
 | 
        
           |  |  | 8071 |  * @param string $feature Feature code (FEATURE_xx constant)
 | 
        
           |  |  | 8072 |  * @param mixed $default default value if feature support unknown
 | 
        
           |  |  | 8073 |  * @return mixed Feature result (false if not supported, null if feature is unknown,
 | 
        
           |  |  | 8074 |  *         otherwise usually true but may have other feature-specific value such as array)
 | 
        
           |  |  | 8075 |  * @throws coding_exception
 | 
        
           |  |  | 8076 |  */
 | 
        
           | 1326 | ariadna | 8077 | function plugin_supports($type, $name, $feature, $default = null)
 | 
        
           |  |  | 8078 | {
 | 
        
           | 1 | efrain | 8079 |     global $CFG;
 | 
        
           |  |  | 8080 |   | 
        
           |  |  | 8081 |     if ($type === 'mod' and $name === 'NEWMODULE') {
 | 
        
           |  |  | 8082 |         // Somebody forgot to rename the module template.
 | 
        
           |  |  | 8083 |         return false;
 | 
        
           |  |  | 8084 |     }
 | 
        
           |  |  | 8085 |   | 
        
           |  |  | 8086 |     $component = clean_param($type . '_' . $name, PARAM_COMPONENT);
 | 
        
           |  |  | 8087 |     if (empty($component)) {
 | 
        
           |  |  | 8088 |         throw new coding_exception('Invalid component used in plugin_supports():' . $type . '_' . $name);
 | 
        
           |  |  | 8089 |     }
 | 
        
           |  |  | 8090 |   | 
        
           |  |  | 8091 |     $function = null;
 | 
        
           |  |  | 8092 |   | 
        
           |  |  | 8093 |     if ($type === 'mod') {
 | 
        
           |  |  | 8094 |         // We need this special case because we support subplugins in modules,
 | 
        
           |  |  | 8095 |         // otherwise it would end up in infinite loop.
 | 
        
           |  |  | 8096 |         if (file_exists("$CFG->dirroot/mod/$name/lib.php")) {
 | 
        
           |  |  | 8097 |             include_once("$CFG->dirroot/mod/$name/lib.php");
 | 
        
           | 1326 | ariadna | 8098 |             $function = $component . '_supports';
 | 
        
           | 1 | efrain | 8099 |             if (!function_exists($function)) {
 | 
        
           |  |  | 8100 |                 // Legacy non-frankenstyle function name.
 | 
        
           | 1326 | ariadna | 8101 |                 $function = $name . '_supports';
 | 
        
           | 1 | efrain | 8102 |             }
 | 
        
           |  |  | 8103 |         }
 | 
        
           |  |  | 8104 |     } else {
 | 
        
           |  |  | 8105 |         if (!$path = core_component::get_plugin_directory($type, $name)) {
 | 
        
           |  |  | 8106 |             // Non existent plugin type.
 | 
        
           |  |  | 8107 |             return false;
 | 
        
           |  |  | 8108 |         }
 | 
        
           |  |  | 8109 |         if (file_exists("$path/lib.php")) {
 | 
        
           |  |  | 8110 |             include_once("$path/lib.php");
 | 
        
           | 1326 | ariadna | 8111 |             $function = $component . '_supports';
 | 
        
           | 1 | efrain | 8112 |         }
 | 
        
           |  |  | 8113 |     }
 | 
        
           |  |  | 8114 |   | 
        
           |  |  | 8115 |     if ($function and function_exists($function)) {
 | 
        
           |  |  | 8116 |         $supports = $function($feature);
 | 
        
           |  |  | 8117 |         if (is_null($supports)) {
 | 
        
           |  |  | 8118 |             // Plugin does not know - use default.
 | 
        
           |  |  | 8119 |             return $default;
 | 
        
           |  |  | 8120 |         } else {
 | 
        
           |  |  | 8121 |             return $supports;
 | 
        
           |  |  | 8122 |         }
 | 
        
           |  |  | 8123 |     }
 | 
        
           |  |  | 8124 |   | 
        
           |  |  | 8125 |     // Plugin does not care, so use default.
 | 
        
           |  |  | 8126 |     return $default;
 | 
        
           |  |  | 8127 | }
 | 
        
           |  |  | 8128 |   | 
        
           |  |  | 8129 | /**
 | 
        
           |  |  | 8130 |  * Returns true if the current version of PHP is greater that the specified one.
 | 
        
           |  |  | 8131 |  *
 | 
        
           |  |  | 8132 |  * @todo Check PHP version being required here is it too low?
 | 
        
           |  |  | 8133 |  *
 | 
        
           |  |  | 8134 |  * @param string $version The version of php being tested.
 | 
        
           |  |  | 8135 |  * @return bool
 | 
        
           |  |  | 8136 |  */
 | 
        
           | 1326 | ariadna | 8137 | function check_php_version($version = '5.2.4')
 | 
        
           |  |  | 8138 | {
 | 
        
           | 1 | efrain | 8139 |     return (version_compare(phpversion(), $version) >= 0);
 | 
        
           |  |  | 8140 | }
 | 
        
           |  |  | 8141 |   | 
        
           |  |  | 8142 | /**
 | 
        
           |  |  | 8143 |  * Determine if moodle installation requires update.
 | 
        
           |  |  | 8144 |  *
 | 
        
           |  |  | 8145 |  * Checks version numbers of main code and all plugins to see
 | 
        
           |  |  | 8146 |  * if there are any mismatches.
 | 
        
           |  |  | 8147 |  *
 | 
        
           |  |  | 8148 |  * @param bool $checkupgradeflag check the outagelessupgrade flag to see if an upgrade is running.
 | 
        
           |  |  | 8149 |  * @return bool
 | 
        
           |  |  | 8150 |  */
 | 
        
           | 1326 | ariadna | 8151 | function moodle_needs_upgrading($checkupgradeflag = true)
 | 
        
           |  |  | 8152 | {
 | 
        
           | 1 | efrain | 8153 |     global $CFG, $DB;
 | 
        
           |  |  | 8154 |   | 
        
           |  |  | 8155 |     // Say no if there is already an upgrade running.
 | 
        
           |  |  | 8156 |     if ($checkupgradeflag) {
 | 
        
           |  |  | 8157 |         $lock = $DB->get_field('config', 'value', ['name' => 'outagelessupgrade']);
 | 
        
           |  |  | 8158 |         $currentprocessrunningupgrade = (defined('CLI_UPGRADE_RUNNING') && CLI_UPGRADE_RUNNING);
 | 
        
           |  |  | 8159 |         // If we ARE locked, but this PHP process is NOT the process running the upgrade,
 | 
        
           |  |  | 8160 |         // We should always return false.
 | 
        
           |  |  | 8161 |         // This means the upgrade is running from CLI somewhere, or about to.
 | 
        
           |  |  | 8162 |         if (!empty($lock) && !$currentprocessrunningupgrade) {
 | 
        
           |  |  | 8163 |             return false;
 | 
        
           |  |  | 8164 |         }
 | 
        
           |  |  | 8165 |     }
 | 
        
           |  |  | 8166 |   | 
        
           |  |  | 8167 |     if (empty($CFG->version)) {
 | 
        
           |  |  | 8168 |         return true;
 | 
        
           |  |  | 8169 |     }
 | 
        
           |  |  | 8170 |   | 
        
           |  |  | 8171 |     // There is no need to purge plugininfo caches here because
 | 
        
           |  |  | 8172 |     // these caches are not used during upgrade and they are purged after
 | 
        
           |  |  | 8173 |     // every upgrade.
 | 
        
           |  |  | 8174 |   | 
        
           |  |  | 8175 |     if (empty($CFG->allversionshash)) {
 | 
        
           |  |  | 8176 |         return true;
 | 
        
           |  |  | 8177 |     }
 | 
        
           |  |  | 8178 |   | 
        
           |  |  | 8179 |     $hash = core_component::get_all_versions_hash();
 | 
        
           |  |  | 8180 |   | 
        
           |  |  | 8181 |     return ($hash !== $CFG->allversionshash);
 | 
        
           |  |  | 8182 | }
 | 
        
           |  |  | 8183 |   | 
        
           |  |  | 8184 | /**
 | 
        
           |  |  | 8185 |  * Returns the major version of this site
 | 
        
           |  |  | 8186 |  *
 | 
        
           |  |  | 8187 |  * Moodle version numbers consist of three numbers separated by a dot, for
 | 
        
           |  |  | 8188 |  * example 1.9.11 or 2.0.2. The first two numbers, like 1.9 or 2.0, represent so
 | 
        
           |  |  | 8189 |  * called major version. This function extracts the major version from either
 | 
        
           |  |  | 8190 |  * $CFG->release (default) or eventually from the $release variable defined in
 | 
        
           |  |  | 8191 |  * the main version.php.
 | 
        
           |  |  | 8192 |  *
 | 
        
           |  |  | 8193 |  * @param bool $fromdisk should the version if source code files be used
 | 
        
           |  |  | 8194 |  * @return string|false the major version like '2.3', false if could not be determined
 | 
        
           |  |  | 8195 |  */
 | 
        
           | 1326 | ariadna | 8196 | function moodle_major_version($fromdisk = false)
 | 
        
           |  |  | 8197 | {
 | 
        
           | 1 | efrain | 8198 |     global $CFG;
 | 
        
           |  |  | 8199 |   | 
        
           |  |  | 8200 |     if ($fromdisk) {
 | 
        
           |  |  | 8201 |         $release = null;
 | 
        
           | 1326 | ariadna | 8202 |         require($CFG->dirroot . '/version.php');
 | 
        
           | 1 | efrain | 8203 |         if (empty($release)) {
 | 
        
           |  |  | 8204 |             return false;
 | 
        
           |  |  | 8205 |         }
 | 
        
           |  |  | 8206 |     } else {
 | 
        
           |  |  | 8207 |         if (empty($CFG->release)) {
 | 
        
           |  |  | 8208 |             return false;
 | 
        
           |  |  | 8209 |         }
 | 
        
           |  |  | 8210 |         $release = $CFG->release;
 | 
        
           |  |  | 8211 |     }
 | 
        
           |  |  | 8212 |   | 
        
           |  |  | 8213 |     if (preg_match('/^[0-9]+\.[0-9]+/', $release, $matches)) {
 | 
        
           |  |  | 8214 |         return $matches[0];
 | 
        
           |  |  | 8215 |     } else {
 | 
        
           |  |  | 8216 |         return false;
 | 
        
           |  |  | 8217 |     }
 | 
        
           |  |  | 8218 | }
 | 
        
           |  |  | 8219 |   | 
        
           |  |  | 8220 | // MISCELLANEOUS.
 | 
        
           |  |  | 8221 |   | 
        
           |  |  | 8222 | /**
 | 
        
           |  |  | 8223 |  * Gets the system locale
 | 
        
           |  |  | 8224 |  *
 | 
        
           |  |  | 8225 |  * @return string Retuns the current locale.
 | 
        
           |  |  | 8226 |  */
 | 
        
           | 1326 | ariadna | 8227 | function moodle_getlocale()
 | 
        
           |  |  | 8228 | {
 | 
        
           | 1 | efrain | 8229 |     global $CFG;
 | 
        
           |  |  | 8230 |   | 
        
           |  |  | 8231 |     // Fetch the correct locale based on ostype.
 | 
        
           |  |  | 8232 |     if ($CFG->ostype == 'WINDOWS') {
 | 
        
           |  |  | 8233 |         $stringtofetch = 'localewin';
 | 
        
           |  |  | 8234 |     } else {
 | 
        
           |  |  | 8235 |         $stringtofetch = 'locale';
 | 
        
           |  |  | 8236 |     }
 | 
        
           |  |  | 8237 |   | 
        
           |  |  | 8238 |     if (!empty($CFG->locale)) { // Override locale for all language packs.
 | 
        
           |  |  | 8239 |         return $CFG->locale;
 | 
        
           |  |  | 8240 |     }
 | 
        
           |  |  | 8241 |   | 
        
           |  |  | 8242 |     return get_string($stringtofetch, 'langconfig');
 | 
        
           |  |  | 8243 | }
 | 
        
           |  |  | 8244 |   | 
        
           |  |  | 8245 | /**
 | 
        
           |  |  | 8246 |  * Sets the system locale
 | 
        
           |  |  | 8247 |  *
 | 
        
           |  |  | 8248 |  * @category string
 | 
        
           |  |  | 8249 |  * @param string $locale Can be used to force a locale
 | 
        
           |  |  | 8250 |  */
 | 
        
           | 1326 | ariadna | 8251 | function moodle_setlocale($locale = '')
 | 
        
           |  |  | 8252 | {
 | 
        
           | 1 | efrain | 8253 |     global $CFG;
 | 
        
           |  |  | 8254 |   | 
        
           |  |  | 8255 |     static $currentlocale = ''; // Last locale caching.
 | 
        
           |  |  | 8256 |   | 
        
           |  |  | 8257 |     $oldlocale = $currentlocale;
 | 
        
           |  |  | 8258 |   | 
        
           |  |  | 8259 |     // The priority is the same as in get_string() - parameter, config, course, session, user, global language.
 | 
        
           |  |  | 8260 |     if (!empty($locale)) {
 | 
        
           |  |  | 8261 |         $currentlocale = $locale;
 | 
        
           |  |  | 8262 |     } else {
 | 
        
           |  |  | 8263 |         $currentlocale = moodle_getlocale();
 | 
        
           |  |  | 8264 |     }
 | 
        
           |  |  | 8265 |   | 
        
           |  |  | 8266 |     // Do nothing if locale already set up.
 | 
        
           |  |  | 8267 |     if ($oldlocale == $currentlocale) {
 | 
        
           |  |  | 8268 |         return;
 | 
        
           |  |  | 8269 |     }
 | 
        
           |  |  | 8270 |   | 
        
           |  |  | 8271 |     // Due to some strange BUG we cannot set the LC_TIME directly, so we fetch current values,
 | 
        
           |  |  | 8272 |     // set LC_ALL and then set values again. Just wondering why we cannot set LC_ALL only??? - stronk7
 | 
        
           |  |  | 8273 |     // Some day, numeric, monetary and other categories should be set too, I think. :-/.
 | 
        
           |  |  | 8274 |   | 
        
           |  |  | 8275 |     // Get current values.
 | 
        
           | 1326 | ariadna | 8276 |     $monetary = setlocale(LC_MONETARY, 0);
 | 
        
           |  |  | 8277 |     $numeric = setlocale(LC_NUMERIC, 0);
 | 
        
           |  |  | 8278 |     $ctype   = setlocale(LC_CTYPE, 0);
 | 
        
           | 1 | efrain | 8279 |     if ($CFG->ostype != 'WINDOWS') {
 | 
        
           | 1326 | ariadna | 8280 |         $messages = setlocale(LC_MESSAGES, 0);
 | 
        
           | 1 | efrain | 8281 |     }
 | 
        
           |  |  | 8282 |     // Set locale to all.
 | 
        
           | 1326 | ariadna | 8283 |     $result = setlocale(LC_ALL, $currentlocale);
 | 
        
           | 1 | efrain | 8284 |     // If setting of locale fails try the other utf8 or utf-8 variant,
 | 
        
           |  |  | 8285 |     // some operating systems support both (Debian), others just one (OSX).
 | 
        
           |  |  | 8286 |     if ($result === false) {
 | 
        
           |  |  | 8287 |         if (stripos($currentlocale, '.UTF-8') !== false) {
 | 
        
           |  |  | 8288 |             $newlocale = str_ireplace('.UTF-8', '.UTF8', $currentlocale);
 | 
        
           | 1326 | ariadna | 8289 |             setlocale(LC_ALL, $newlocale);
 | 
        
           | 1 | efrain | 8290 |         } else if (stripos($currentlocale, '.UTF8') !== false) {
 | 
        
           |  |  | 8291 |             $newlocale = str_ireplace('.UTF8', '.UTF-8', $currentlocale);
 | 
        
           | 1326 | ariadna | 8292 |             setlocale(LC_ALL, $newlocale);
 | 
        
           | 1 | efrain | 8293 |         }
 | 
        
           |  |  | 8294 |     }
 | 
        
           |  |  | 8295 |     // Set old values.
 | 
        
           | 1326 | ariadna | 8296 |     setlocale(LC_MONETARY, $monetary);
 | 
        
           |  |  | 8297 |     setlocale(LC_NUMERIC, $numeric);
 | 
        
           | 1 | efrain | 8298 |     if ($CFG->ostype != 'WINDOWS') {
 | 
        
           | 1326 | ariadna | 8299 |         setlocale(LC_MESSAGES, $messages);
 | 
        
           | 1 | efrain | 8300 |     }
 | 
        
           |  |  | 8301 |     if ($currentlocale == 'tr_TR' or $currentlocale == 'tr_TR.UTF-8') {
 | 
        
           |  |  | 8302 |         // To workaround a well-known PHP problem with Turkish letter Ii.
 | 
        
           | 1326 | ariadna | 8303 |         setlocale(LC_CTYPE, $ctype);
 | 
        
           | 1 | efrain | 8304 |     }
 | 
        
           |  |  | 8305 | }
 | 
        
           |  |  | 8306 |   | 
        
           |  |  | 8307 | /**
 | 
        
           |  |  | 8308 |  * Count words in a string.
 | 
        
           |  |  | 8309 |  *
 | 
        
           |  |  | 8310 |  * Words are defined as things between whitespace.
 | 
        
           |  |  | 8311 |  *
 | 
        
           |  |  | 8312 |  * @category string
 | 
        
           |  |  | 8313 |  * @param string $string The text to be searched for words. May be HTML.
 | 
        
           |  |  | 8314 |  * @param int|null $format
 | 
        
           |  |  | 8315 |  * @return int The count of words in the specified string
 | 
        
           |  |  | 8316 |  */
 | 
        
           | 1326 | ariadna | 8317 | function count_words($string, $format = null)
 | 
        
           |  |  | 8318 | {
 | 
        
           | 1 | efrain | 8319 |     // Before stripping tags, add a space after the close tag of anything that is not obviously inline.
 | 
        
           |  |  | 8320 |     // Also, br is a special case because it definitely delimits a word, but has no close tag.
 | 
        
           |  |  | 8321 |     $string = preg_replace('~
 | 
        
           |  |  | 8322 |             (                                   # Capture the tag we match.
 | 
        
           |  |  | 8323 |                 </                              # Start of close tag.
 | 
        
           |  |  | 8324 |                 (?!                             # Do not match any of these specific close tag names.
 | 
        
           |  |  | 8325 |                     a> | b> | del> | em> | i> |
 | 
        
           |  |  | 8326 |                     ins> | s> | small> | span> |
 | 
        
           |  |  | 8327 |                     strong> | sub> | sup> | u>
 | 
        
           |  |  | 8328 |                 )
 | 
        
           |  |  | 8329 |                 \w+                             # But, apart from those execptions, match any tag name.
 | 
        
           |  |  | 8330 |                 >                               # End of close tag.
 | 
        
           |  |  | 8331 |             |
 | 
        
           |  |  | 8332 |                 <br> | <br\s*/>                 # Special cases that are not close tags.
 | 
        
           |  |  | 8333 |             )
 | 
        
           |  |  | 8334 |             ~x', '$1 ', $string); // Add a space after the close tag.
 | 
        
           |  |  | 8335 |     if ($format !== null && $format != FORMAT_PLAIN) {
 | 
        
           |  |  | 8336 |         // Match the usual text cleaning before display.
 | 
        
           |  |  | 8337 |         // Ideally we should apply multilang filter only here, other filters might add extra text.
 | 
        
           |  |  | 8338 |         $string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]);
 | 
        
           |  |  | 8339 |     }
 | 
        
           |  |  | 8340 |     // Now remove HTML tags.
 | 
        
           |  |  | 8341 |     $string = strip_tags($string);
 | 
        
           |  |  | 8342 |     // Decode HTML entities.
 | 
        
           |  |  | 8343 |     $string = html_entity_decode($string, ENT_COMPAT);
 | 
        
           |  |  | 8344 |   | 
        
           |  |  | 8345 |     // Now, the word count is the number of blocks of characters separated
 | 
        
           |  |  | 8346 |     // by any sort of space. That seems to be the definition used by all other systems.
 | 
        
           |  |  | 8347 |     // To be precise about what is considered to separate words:
 | 
        
           |  |  | 8348 |     // * Anything that Unicode considers a 'Separator'
 | 
        
           |  |  | 8349 |     // * Anything that Unicode considers a 'Control character'
 | 
        
           |  |  | 8350 |     // * An em- or en- dash.
 | 
        
           |  |  | 8351 |     return count(preg_split('~[\p{Z}\p{Cc}—–]+~u', $string, -1, PREG_SPLIT_NO_EMPTY));
 | 
        
           |  |  | 8352 | }
 | 
        
           |  |  | 8353 |   | 
        
           |  |  | 8354 | /**
 | 
        
           |  |  | 8355 |  * Count letters in a string.
 | 
        
           |  |  | 8356 |  *
 | 
        
           |  |  | 8357 |  * Letters are defined as chars not in tags and different from whitespace.
 | 
        
           |  |  | 8358 |  *
 | 
        
           |  |  | 8359 |  * @category string
 | 
        
           |  |  | 8360 |  * @param string $string The text to be searched for letters. May be HTML.
 | 
        
           |  |  | 8361 |  * @param int|null $format
 | 
        
           |  |  | 8362 |  * @return int The count of letters in the specified text.
 | 
        
           |  |  | 8363 |  */
 | 
        
           | 1326 | ariadna | 8364 | function count_letters($string, $format = null)
 | 
        
           |  |  | 8365 | {
 | 
        
           | 1 | efrain | 8366 |     if ($format !== null && $format != FORMAT_PLAIN) {
 | 
        
           |  |  | 8367 |         // Match the usual text cleaning before display.
 | 
        
           |  |  | 8368 |         // Ideally we should apply multilang filter only here, other filters might add extra text.
 | 
        
           |  |  | 8369 |         $string = format_text($string, $format, ['filter' => false, 'noclean' => false, 'para' => false]);
 | 
        
           |  |  | 8370 |     }
 | 
        
           |  |  | 8371 |     $string = strip_tags($string); // Tags are out now.
 | 
        
           |  |  | 8372 |     $string = html_entity_decode($string, ENT_COMPAT);
 | 
        
           |  |  | 8373 |     $string = preg_replace('/[[:space:]]*/', '', $string); // Whitespace are out now.
 | 
        
           |  |  | 8374 |   | 
        
           |  |  | 8375 |     return core_text::strlen($string);
 | 
        
           |  |  | 8376 | }
 | 
        
           |  |  | 8377 |   | 
        
           |  |  | 8378 | /**
 | 
        
           |  |  | 8379 |  * Generate and return a random string of the specified length.
 | 
        
           |  |  | 8380 |  *
 | 
        
           |  |  | 8381 |  * @param int $length The length of the string to be created.
 | 
        
           |  |  | 8382 |  * @return string
 | 
        
           |  |  | 8383 |  */
 | 
        
           | 1326 | ariadna | 8384 | function random_string($length = 15)
 | 
        
           |  |  | 8385 | {
 | 
        
           | 1 | efrain | 8386 |     $randombytes = random_bytes($length);
 | 
        
           |  |  | 8387 |     $pool  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 | 
        
           |  |  | 8388 |     $pool .= 'abcdefghijklmnopqrstuvwxyz';
 | 
        
           |  |  | 8389 |     $pool .= '0123456789';
 | 
        
           |  |  | 8390 |     $poollen = strlen($pool);
 | 
        
           |  |  | 8391 |     $string = '';
 | 
        
           |  |  | 8392 |     for ($i = 0; $i < $length; $i++) {
 | 
        
           |  |  | 8393 |         $rand = ord($randombytes[$i]);
 | 
        
           | 1326 | ariadna | 8394 |         $string .= substr($pool, ($rand % ($poollen)), 1);
 | 
        
           | 1 | efrain | 8395 |     }
 | 
        
           |  |  | 8396 |     return $string;
 | 
        
           |  |  | 8397 | }
 | 
        
           |  |  | 8398 |   | 
        
           |  |  | 8399 | /**
 | 
        
           |  |  | 8400 |  * Generate a complex random string (useful for md5 salts)
 | 
        
           |  |  | 8401 |  *
 | 
        
           |  |  | 8402 |  * This function is based on the above {@link random_string()} however it uses a
 | 
        
           |  |  | 8403 |  * larger pool of characters and generates a string between 24 and 32 characters
 | 
        
           |  |  | 8404 |  *
 | 
        
           |  |  | 8405 |  * @param int $length Optional if set generates a string to exactly this length
 | 
        
           |  |  | 8406 |  * @return string
 | 
        
           |  |  | 8407 |  */
 | 
        
           | 1326 | ariadna | 8408 | function complex_random_string($length = null)
 | 
        
           |  |  | 8409 | {
 | 
        
           | 1 | efrain | 8410 |     $pool  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
 | 
        
           |  |  | 8411 |     $pool .= '`~!@#%^&*()_+-=[];,./<>?:{} ';
 | 
        
           |  |  | 8412 |     $poollen = strlen($pool);
 | 
        
           | 1326 | ariadna | 8413 |     if ($length === null) {
 | 
        
           | 1 | efrain | 8414 |         $length = floor(rand(24, 32));
 | 
        
           |  |  | 8415 |     }
 | 
        
           |  |  | 8416 |     $randombytes = random_bytes($length);
 | 
        
           |  |  | 8417 |     $string = '';
 | 
        
           |  |  | 8418 |     for ($i = 0; $i < $length; $i++) {
 | 
        
           |  |  | 8419 |         $rand = ord($randombytes[$i]);
 | 
        
           | 1326 | ariadna | 8420 |         $string .= $pool[($rand % $poollen)];
 | 
        
           | 1 | efrain | 8421 |     }
 | 
        
           |  |  | 8422 |     return $string;
 | 
        
           |  |  | 8423 | }
 | 
        
           |  |  | 8424 |   | 
        
           |  |  | 8425 | /**
 | 
        
           |  |  | 8426 |  * Given some text (which may contain HTML) and an ideal length,
 | 
        
           |  |  | 8427 |  * this function truncates the text neatly on a word boundary if possible
 | 
        
           |  |  | 8428 |  *
 | 
        
           |  |  | 8429 |  * @category string
 | 
        
           |  |  | 8430 |  * @param string $text text to be shortened
 | 
        
           |  |  | 8431 |  * @param int $ideal ideal string length
 | 
        
           |  |  | 8432 |  * @param boolean $exact if false, $text will not be cut mid-word
 | 
        
           |  |  | 8433 |  * @param string $ending The string to append if the passed string is truncated
 | 
        
           |  |  | 8434 |  * @return string $truncate shortened string
 | 
        
           |  |  | 8435 |  */
 | 
        
           | 1326 | ariadna | 8436 | function shorten_text($text, $ideal = 30, $exact = false, $ending = '...')
 | 
        
           |  |  | 8437 | {
 | 
        
           | 1 | efrain | 8438 |     // If the plain text is shorter than the maximum length, return the whole text.
 | 
        
           |  |  | 8439 |     if (core_text::strlen(preg_replace('/<.*?>/', '', $text)) <= $ideal) {
 | 
        
           |  |  | 8440 |         return $text;
 | 
        
           |  |  | 8441 |     }
 | 
        
           |  |  | 8442 |   | 
        
           |  |  | 8443 |     // Splits on HTML tags. Each open/close/empty tag will be the first thing
 | 
        
           |  |  | 8444 |     // and only tag in its 'line'.
 | 
        
           |  |  | 8445 |     preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
 | 
        
           |  |  | 8446 |   | 
        
           |  |  | 8447 |     $totallength = core_text::strlen($ending);
 | 
        
           |  |  | 8448 |     $truncate = '';
 | 
        
           |  |  | 8449 |   | 
        
           |  |  | 8450 |     // This array stores information about open and close tags and their position
 | 
        
           |  |  | 8451 |     // in the truncated string. Each item in the array is an object with fields
 | 
        
           |  |  | 8452 |     // ->open (true if open), ->tag (tag name in lower case), and ->pos
 | 
        
           |  |  | 8453 |     // (byte position in truncated text).
 | 
        
           |  |  | 8454 |     $tagdetails = array();
 | 
        
           |  |  | 8455 |   | 
        
           |  |  | 8456 |     foreach ($lines as $linematchings) {
 | 
        
           |  |  | 8457 |         // If there is any html-tag in this line, handle it and add it (uncounted) to the output.
 | 
        
           |  |  | 8458 |         if (!empty($linematchings[1])) {
 | 
        
           |  |  | 8459 |             // If it's an "empty element" with or without xhtml-conform closing slash (f.e. <br/>).
 | 
        
           |  |  | 8460 |             if (!preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $linematchings[1])) {
 | 
        
           |  |  | 8461 |                 if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $linematchings[1], $tagmatchings)) {
 | 
        
           |  |  | 8462 |                     // Record closing tag.
 | 
        
           |  |  | 8463 |                     $tagdetails[] = (object) array(
 | 
        
           | 1326 | ariadna | 8464 |                         'open' => false,
 | 
        
           |  |  | 8465 |                         'tag'  => core_text::strtolower($tagmatchings[1]),
 | 
        
           |  |  | 8466 |                         'pos'  => core_text::strlen($truncate),
 | 
        
           |  |  | 8467 |                     );
 | 
        
           | 1 | efrain | 8468 |                 } else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $linematchings[1], $tagmatchings)) {
 | 
        
           |  |  | 8469 |                     // Record opening tag.
 | 
        
           |  |  | 8470 |                     $tagdetails[] = (object) array(
 | 
        
           | 1326 | ariadna | 8471 |                         'open' => true,
 | 
        
           |  |  | 8472 |                         'tag'  => core_text::strtolower($tagmatchings[1]),
 | 
        
           |  |  | 8473 |                         'pos'  => core_text::strlen($truncate),
 | 
        
           |  |  | 8474 |                     );
 | 
        
           | 1 | efrain | 8475 |                 } else if (preg_match('/^<!--\[if\s.*?\]>$/s', $linematchings[1], $tagmatchings)) {
 | 
        
           |  |  | 8476 |                     $tagdetails[] = (object) array(
 | 
        
           | 1326 | ariadna | 8477 |                         'open' => true,
 | 
        
           |  |  | 8478 |                         'tag'  => core_text::strtolower('if'),
 | 
        
           |  |  | 8479 |                         'pos'  => core_text::strlen($truncate),
 | 
        
           | 1 | efrain | 8480 |                     );
 | 
        
           |  |  | 8481 |                 } else if (preg_match('/^<!--<!\[endif\]-->$/s', $linematchings[1], $tagmatchings)) {
 | 
        
           |  |  | 8482 |                     $tagdetails[] = (object) array(
 | 
        
           | 1326 | ariadna | 8483 |                         'open' => false,
 | 
        
           |  |  | 8484 |                         'tag'  => core_text::strtolower('if'),
 | 
        
           |  |  | 8485 |                         'pos'  => core_text::strlen($truncate),
 | 
        
           | 1 | efrain | 8486 |                     );
 | 
        
           |  |  | 8487 |                 }
 | 
        
           |  |  | 8488 |             }
 | 
        
           |  |  | 8489 |             // Add html-tag to $truncate'd text.
 | 
        
           |  |  | 8490 |             $truncate .= $linematchings[1];
 | 
        
           |  |  | 8491 |         }
 | 
        
           |  |  | 8492 |   | 
        
           |  |  | 8493 |         // Calculate the length of the plain text part of the line; handle entities as one character.
 | 
        
           |  |  | 8494 |         $contentlength = core_text::strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $linematchings[2]));
 | 
        
           |  |  | 8495 |         if ($totallength + $contentlength > $ideal) {
 | 
        
           |  |  | 8496 |             // The number of characters which are left.
 | 
        
           |  |  | 8497 |             $left = $ideal - $totallength;
 | 
        
           |  |  | 8498 |             $entitieslength = 0;
 | 
        
           |  |  | 8499 |             // Search for html entities.
 | 
        
           |  |  | 8500 |             if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $linematchings[2], $entities, PREG_OFFSET_CAPTURE)) {
 | 
        
           |  |  | 8501 |                 // Calculate the real length of all entities in the legal range.
 | 
        
           |  |  | 8502 |                 foreach ($entities[0] as $entity) {
 | 
        
           | 1326 | ariadna | 8503 |                     if ($entity[1] + 1 - $entitieslength <= $left) {
 | 
        
           | 1 | efrain | 8504 |                         $left--;
 | 
        
           |  |  | 8505 |                         $entitieslength += core_text::strlen($entity[0]);
 | 
        
           |  |  | 8506 |                     } else {
 | 
        
           |  |  | 8507 |                         // No more characters left.
 | 
        
           |  |  | 8508 |                         break;
 | 
        
           |  |  | 8509 |                     }
 | 
        
           |  |  | 8510 |                 }
 | 
        
           |  |  | 8511 |             }
 | 
        
           |  |  | 8512 |             $breakpos = $left + $entitieslength;
 | 
        
           |  |  | 8513 |   | 
        
           |  |  | 8514 |             // If the words shouldn't be cut in the middle...
 | 
        
           |  |  | 8515 |             if (!$exact) {
 | 
        
           |  |  | 8516 |                 // Search the last occurence of a space.
 | 
        
           |  |  | 8517 |                 for (; $breakpos > 0; $breakpos--) {
 | 
        
           |  |  | 8518 |                     if ($char = core_text::substr($linematchings[2], $breakpos, 1)) {
 | 
        
           |  |  | 8519 |                         if ($char === '.' or $char === ' ') {
 | 
        
           |  |  | 8520 |                             $breakpos += 1;
 | 
        
           |  |  | 8521 |                             break;
 | 
        
           |  |  | 8522 |                         } else if (strlen($char) > 2) {
 | 
        
           |  |  | 8523 |                             // Chinese/Japanese/Korean text can be truncated at any UTF-8 character boundary.
 | 
        
           |  |  | 8524 |                             $breakpos += 1;
 | 
        
           |  |  | 8525 |                             break;
 | 
        
           |  |  | 8526 |                         }
 | 
        
           |  |  | 8527 |                     }
 | 
        
           |  |  | 8528 |                 }
 | 
        
           |  |  | 8529 |             }
 | 
        
           |  |  | 8530 |             if ($breakpos == 0) {
 | 
        
           |  |  | 8531 |                 // This deals with the test_shorten_text_no_spaces case.
 | 
        
           |  |  | 8532 |                 $breakpos = $left + $entitieslength;
 | 
        
           |  |  | 8533 |             } else if ($breakpos > $left + $entitieslength) {
 | 
        
           |  |  | 8534 |                 // This deals with the previous for loop breaking on the first char.
 | 
        
           |  |  | 8535 |                 $breakpos = $left + $entitieslength;
 | 
        
           |  |  | 8536 |             }
 | 
        
           |  |  | 8537 |   | 
        
           |  |  | 8538 |             $truncate .= core_text::substr($linematchings[2], 0, $breakpos);
 | 
        
           |  |  | 8539 |             // Maximum length is reached, so get off the loop.
 | 
        
           |  |  | 8540 |             break;
 | 
        
           |  |  | 8541 |         } else {
 | 
        
           |  |  | 8542 |             $truncate .= $linematchings[2];
 | 
        
           |  |  | 8543 |             $totallength += $contentlength;
 | 
        
           |  |  | 8544 |         }
 | 
        
           |  |  | 8545 |   | 
        
           |  |  | 8546 |         // If the maximum length is reached, get off the loop.
 | 
        
           |  |  | 8547 |         if ($totallength >= $ideal) {
 | 
        
           |  |  | 8548 |             break;
 | 
        
           |  |  | 8549 |         }
 | 
        
           |  |  | 8550 |     }
 | 
        
           |  |  | 8551 |   | 
        
           |  |  | 8552 |     // Add the defined ending to the text.
 | 
        
           |  |  | 8553 |     $truncate .= $ending;
 | 
        
           |  |  | 8554 |   | 
        
           |  |  | 8555 |     // Now calculate the list of open html tags based on the truncate position.
 | 
        
           |  |  | 8556 |     $opentags = array();
 | 
        
           |  |  | 8557 |     foreach ($tagdetails as $taginfo) {
 | 
        
           |  |  | 8558 |         if ($taginfo->open) {
 | 
        
           |  |  | 8559 |             // Add tag to the beginning of $opentags list.
 | 
        
           |  |  | 8560 |             array_unshift($opentags, $taginfo->tag);
 | 
        
           |  |  | 8561 |         } else {
 | 
        
           |  |  | 8562 |             // Can have multiple exact same open tags, close the last one.
 | 
        
           |  |  | 8563 |             $pos = array_search($taginfo->tag, array_reverse($opentags, true));
 | 
        
           |  |  | 8564 |             if ($pos !== false) {
 | 
        
           |  |  | 8565 |                 unset($opentags[$pos]);
 | 
        
           |  |  | 8566 |             }
 | 
        
           |  |  | 8567 |         }
 | 
        
           |  |  | 8568 |     }
 | 
        
           |  |  | 8569 |   | 
        
           |  |  | 8570 |     // Close all unclosed html-tags.
 | 
        
           |  |  | 8571 |     foreach ($opentags as $tag) {
 | 
        
           |  |  | 8572 |         if ($tag === 'if') {
 | 
        
           |  |  | 8573 |             $truncate .= '<!--<![endif]-->';
 | 
        
           |  |  | 8574 |         } else {
 | 
        
           |  |  | 8575 |             $truncate .= '</' . $tag . '>';
 | 
        
           |  |  | 8576 |         }
 | 
        
           |  |  | 8577 |     }
 | 
        
           |  |  | 8578 |   | 
        
           |  |  | 8579 |     return $truncate;
 | 
        
           |  |  | 8580 | }
 | 
        
           |  |  | 8581 |   | 
        
           |  |  | 8582 | /**
 | 
        
           |  |  | 8583 |  * Shortens a given filename by removing characters positioned after the ideal string length.
 | 
        
           |  |  | 8584 |  * When the filename is too long, the file cannot be created on the filesystem due to exceeding max byte size.
 | 
        
           |  |  | 8585 |  * Limiting the filename to a certain size (considering multibyte characters) will prevent this.
 | 
        
           |  |  | 8586 |  *
 | 
        
           |  |  | 8587 |  * @param string $filename file name
 | 
        
           |  |  | 8588 |  * @param int $length ideal string length
 | 
        
           |  |  | 8589 |  * @param bool $includehash Whether to include a file hash in the shortened version. This ensures uniqueness.
 | 
        
           |  |  | 8590 |  * @return string $shortened shortened file name
 | 
        
           |  |  | 8591 |  */
 | 
        
           | 1326 | ariadna | 8592 | function shorten_filename($filename, $length = MAX_FILENAME_SIZE, $includehash = false)
 | 
        
           |  |  | 8593 | {
 | 
        
           | 1 | efrain | 8594 |     $shortened = $filename;
 | 
        
           |  |  | 8595 |     // Extract a part of the filename if it's char size exceeds the ideal string length.
 | 
        
           |  |  | 8596 |     if (core_text::strlen($filename) > $length) {
 | 
        
           |  |  | 8597 |         // Exclude extension if present in filename.
 | 
        
           |  |  | 8598 |         $mimetypes = get_mimetypes_array();
 | 
        
           |  |  | 8599 |         $extension = pathinfo($filename, PATHINFO_EXTENSION);
 | 
        
           |  |  | 8600 |         if ($extension && !empty($mimetypes[$extension])) {
 | 
        
           |  |  | 8601 |             $basename = pathinfo($filename, PATHINFO_FILENAME);
 | 
        
           |  |  | 8602 |             $hash = empty($includehash) ? '' : ' - ' . substr(sha1($basename), 0, 10);
 | 
        
           |  |  | 8603 |             $shortened = core_text::substr($basename, 0, $length - strlen($hash)) . $hash;
 | 
        
           |  |  | 8604 |             $shortened .= '.' . $extension;
 | 
        
           |  |  | 8605 |         } else {
 | 
        
           |  |  | 8606 |             $hash = empty($includehash) ? '' : ' - ' . substr(sha1($filename), 0, 10);
 | 
        
           |  |  | 8607 |             $shortened = core_text::substr($filename, 0, $length - strlen($hash)) . $hash;
 | 
        
           |  |  | 8608 |         }
 | 
        
           |  |  | 8609 |     }
 | 
        
           |  |  | 8610 |     return $shortened;
 | 
        
           |  |  | 8611 | }
 | 
        
           |  |  | 8612 |   | 
        
           |  |  | 8613 | /**
 | 
        
           |  |  | 8614 |  * Shortens a given array of filenames by removing characters positioned after the ideal string length.
 | 
        
           |  |  | 8615 |  *
 | 
        
           |  |  | 8616 |  * @param array $path The paths to reduce the length.
 | 
        
           |  |  | 8617 |  * @param int $length Ideal string length
 | 
        
           |  |  | 8618 |  * @param bool $includehash Whether to include a file hash in the shortened version. This ensures uniqueness.
 | 
        
           |  |  | 8619 |  * @return array $result Shortened paths in array.
 | 
        
           |  |  | 8620 |  */
 | 
        
           | 1326 | ariadna | 8621 | function shorten_filenames(array $path, $length = MAX_FILENAME_SIZE, $includehash = false)
 | 
        
           |  |  | 8622 | {
 | 
        
           | 1 | efrain | 8623 |     $result = null;
 | 
        
           |  |  | 8624 |   | 
        
           | 1326 | ariadna | 8625 |     $result = array_reduce($path, function ($carry, $singlepath) use ($length, $includehash) {
 | 
        
           | 1 | efrain | 8626 |         $carry[] = shorten_filename($singlepath, $length, $includehash);
 | 
        
           |  |  | 8627 |         return $carry;
 | 
        
           |  |  | 8628 |     }, []);
 | 
        
           |  |  | 8629 |   | 
        
           |  |  | 8630 |     return $result;
 | 
        
           |  |  | 8631 | }
 | 
        
           |  |  | 8632 |   | 
        
           |  |  | 8633 | /**
 | 
        
           |  |  | 8634 |  * Given dates in seconds, how many weeks is the date from startdate
 | 
        
           |  |  | 8635 |  * The first week is 1, the second 2 etc ...
 | 
        
           |  |  | 8636 |  *
 | 
        
           |  |  | 8637 |  * @param int $startdate Timestamp for the start date
 | 
        
           |  |  | 8638 |  * @param int $thedate Timestamp for the end date
 | 
        
           |  |  | 8639 |  * @return string
 | 
        
           |  |  | 8640 |  */
 | 
        
           | 1326 | ariadna | 8641 | function getweek($startdate, $thedate)
 | 
        
           |  |  | 8642 | {
 | 
        
           | 1 | efrain | 8643 |     if ($thedate < $startdate) {
 | 
        
           |  |  | 8644 |         return 0;
 | 
        
           |  |  | 8645 |     }
 | 
        
           |  |  | 8646 |   | 
        
           |  |  | 8647 |     return floor(($thedate - $startdate) / WEEKSECS) + 1;
 | 
        
           |  |  | 8648 | }
 | 
        
           |  |  | 8649 |   | 
        
           |  |  | 8650 | /**
 | 
        
           |  |  | 8651 |  * Returns a randomly generated password of length $maxlen.  inspired by
 | 
        
           |  |  | 8652 |  *
 | 
        
           |  |  | 8653 |  * {@link http://www.phpbuilder.com/columns/jesus19990502.php3} and
 | 
        
           |  |  | 8654 |  * {@link http://es2.php.net/manual/en/function.str-shuffle.php#73254}
 | 
        
           |  |  | 8655 |  *
 | 
        
           |  |  | 8656 |  * @param int $maxlen  The maximum size of the password being generated.
 | 
        
           |  |  | 8657 |  * @return string
 | 
        
           |  |  | 8658 |  */
 | 
        
           | 1326 | ariadna | 8659 | function generate_password($maxlen = 10)
 | 
        
           |  |  | 8660 | {
 | 
        
           | 1 | efrain | 8661 |     global $CFG;
 | 
        
           |  |  | 8662 |   | 
        
           |  |  | 8663 |     if (empty($CFG->passwordpolicy)) {
 | 
        
           |  |  | 8664 |         $fillers = PASSWORD_DIGITS;
 | 
        
           |  |  | 8665 |         $wordlist = file($CFG->wordlist);
 | 
        
           |  |  | 8666 |         $word1 = trim($wordlist[rand(0, count($wordlist) - 1)]);
 | 
        
           |  |  | 8667 |         $word2 = trim($wordlist[rand(0, count($wordlist) - 1)]);
 | 
        
           |  |  | 8668 |         $filler1 = $fillers[rand(0, strlen($fillers) - 1)];
 | 
        
           |  |  | 8669 |         $password = $word1 . $filler1 . $word2;
 | 
        
           |  |  | 8670 |     } else {
 | 
        
           |  |  | 8671 |         $minlen = !empty($CFG->minpasswordlength) ? $CFG->minpasswordlength : 0;
 | 
        
           |  |  | 8672 |         $digits = $CFG->minpassworddigits;
 | 
        
           |  |  | 8673 |         $lower = $CFG->minpasswordlower;
 | 
        
           |  |  | 8674 |         $upper = $CFG->minpasswordupper;
 | 
        
           |  |  | 8675 |         $nonalphanum = $CFG->minpasswordnonalphanum;
 | 
        
           |  |  | 8676 |         $total = $lower + $upper + $digits + $nonalphanum;
 | 
        
           |  |  | 8677 |         // Var minlength should be the greater one of the two ( $minlen and $total ).
 | 
        
           |  |  | 8678 |         $minlen = $minlen < $total ? $total : $minlen;
 | 
        
           |  |  | 8679 |         // Var maxlen can never be smaller than minlen.
 | 
        
           |  |  | 8680 |         $maxlen = $minlen > $maxlen ? $minlen : $maxlen;
 | 
        
           |  |  | 8681 |         $additional = $maxlen - $total;
 | 
        
           |  |  | 8682 |   | 
        
           |  |  | 8683 |         // Make sure we have enough characters to fulfill
 | 
        
           |  |  | 8684 |         // complexity requirements.
 | 
        
           |  |  | 8685 |         $passworddigits = PASSWORD_DIGITS;
 | 
        
           |  |  | 8686 |         while ($digits > strlen($passworddigits)) {
 | 
        
           |  |  | 8687 |             $passworddigits .= PASSWORD_DIGITS;
 | 
        
           |  |  | 8688 |         }
 | 
        
           |  |  | 8689 |         $passwordlower = PASSWORD_LOWER;
 | 
        
           |  |  | 8690 |         while ($lower > strlen($passwordlower)) {
 | 
        
           |  |  | 8691 |             $passwordlower .= PASSWORD_LOWER;
 | 
        
           |  |  | 8692 |         }
 | 
        
           |  |  | 8693 |         $passwordupper = PASSWORD_UPPER;
 | 
        
           |  |  | 8694 |         while ($upper > strlen($passwordupper)) {
 | 
        
           |  |  | 8695 |             $passwordupper .= PASSWORD_UPPER;
 | 
        
           |  |  | 8696 |         }
 | 
        
           |  |  | 8697 |         $passwordnonalphanum = PASSWORD_NONALPHANUM;
 | 
        
           |  |  | 8698 |         while ($nonalphanum > strlen($passwordnonalphanum)) {
 | 
        
           |  |  | 8699 |             $passwordnonalphanum .= PASSWORD_NONALPHANUM;
 | 
        
           |  |  | 8700 |         }
 | 
        
           |  |  | 8701 |   | 
        
           |  |  | 8702 |         // Now mix and shuffle it all.
 | 
        
           | 1326 | ariadna | 8703 |         $password = str_shuffle(substr(str_shuffle($passwordlower), 0, $lower) .
 | 
        
           |  |  | 8704 |             substr(str_shuffle($passwordupper), 0, $upper) .
 | 
        
           |  |  | 8705 |             substr(str_shuffle($passworddigits), 0, $digits) .
 | 
        
           |  |  | 8706 |             substr(str_shuffle($passwordnonalphanum), 0, $nonalphanum) .
 | 
        
           |  |  | 8707 |             substr(str_shuffle($passwordlower .
 | 
        
           |  |  | 8708 |                 $passwordupper .
 | 
        
           |  |  | 8709 |                 $passworddigits .
 | 
        
           |  |  | 8710 |                 $passwordnonalphanum), 0, $additional));
 | 
        
           | 1 | efrain | 8711 |     }
 | 
        
           |  |  | 8712 |   | 
        
           | 1326 | ariadna | 8713 |     return substr($password, 0, $maxlen);
 | 
        
           | 1 | efrain | 8714 | }
 | 
        
           |  |  | 8715 |   | 
        
           |  |  | 8716 | /**
 | 
        
           |  |  | 8717 |  * Given a float, prints it nicely.
 | 
        
           |  |  | 8718 |  * Localized floats must not be used in calculations!
 | 
        
           |  |  | 8719 |  *
 | 
        
           |  |  | 8720 |  * The stripzeros feature is intended for making numbers look nicer in small
 | 
        
           |  |  | 8721 |  * areas where it is not necessary to indicate the degree of accuracy by showing
 | 
        
           |  |  | 8722 |  * ending zeros. If you turn it on with $decimalpoints set to 3, for example,
 | 
        
           |  |  | 8723 |  * then it will display '5.4' instead of '5.400' or '5' instead of '5.000'.
 | 
        
           |  |  | 8724 |  *
 | 
        
           |  |  | 8725 |  * @param float $float The float to print
 | 
        
           |  |  | 8726 |  * @param int $decimalpoints The number of decimal places to print. -1 is a special value for auto detect (full precision).
 | 
        
           |  |  | 8727 |  * @param bool $localized use localized decimal separator
 | 
        
           |  |  | 8728 |  * @param bool $stripzeros If true, removes final zeros after decimal point. It will be ignored and the trailing zeros after
 | 
        
           |  |  | 8729 |  *                         the decimal point are always striped if $decimalpoints is -1.
 | 
        
           |  |  | 8730 |  * @return string locale float
 | 
        
           |  |  | 8731 |  */
 | 
        
           | 1326 | ariadna | 8732 | function format_float($float, $decimalpoints = 1, $localized = true, $stripzeros = false)
 | 
        
           |  |  | 8733 | {
 | 
        
           | 1 | efrain | 8734 |     if (is_null($float)) {
 | 
        
           |  |  | 8735 |         return '';
 | 
        
           |  |  | 8736 |     }
 | 
        
           |  |  | 8737 |     if ($localized) {
 | 
        
           |  |  | 8738 |         $separator = get_string('decsep', 'langconfig');
 | 
        
           |  |  | 8739 |     } else {
 | 
        
           |  |  | 8740 |         $separator = '.';
 | 
        
           |  |  | 8741 |     }
 | 
        
           |  |  | 8742 |     if ($decimalpoints == -1) {
 | 
        
           |  |  | 8743 |         // The following counts the number of decimals.
 | 
        
           |  |  | 8744 |         // It is safe as both floatval() and round() functions have same behaviour when non-numeric values are provided.
 | 
        
           |  |  | 8745 |         $floatval = floatval($float);
 | 
        
           |  |  | 8746 |         for ($decimalpoints = 0; $floatval != round($float, $decimalpoints); $decimalpoints++);
 | 
        
           |  |  | 8747 |     }
 | 
        
           |  |  | 8748 |   | 
        
           |  |  | 8749 |     $result = number_format($float, $decimalpoints, $separator, '');
 | 
        
           |  |  | 8750 |     if ($stripzeros && $decimalpoints > 0) {
 | 
        
           |  |  | 8751 |         // Remove zeros and final dot if not needed.
 | 
        
           |  |  | 8752 |         // However, only do this if there is a decimal point!
 | 
        
           |  |  | 8753 |         $result = preg_replace('~(' . preg_quote($separator, '~') . ')?0+$~', '', $result);
 | 
        
           |  |  | 8754 |     }
 | 
        
           |  |  | 8755 |     return $result;
 | 
        
           |  |  | 8756 | }
 | 
        
           |  |  | 8757 |   | 
        
           |  |  | 8758 | /**
 | 
        
           |  |  | 8759 |  * Converts locale specific floating point/comma number back to standard PHP float value
 | 
        
           |  |  | 8760 |  * Do NOT try to do any math operations before this conversion on any user submitted floats!
 | 
        
           |  |  | 8761 |  *
 | 
        
           |  |  | 8762 |  * @param string $localefloat locale aware float representation
 | 
        
           |  |  | 8763 |  * @param bool $strict If true, then check the input and return false if it is not a valid number.
 | 
        
           |  |  | 8764 |  * @return mixed float|bool - false or the parsed float.
 | 
        
           |  |  | 8765 |  */
 | 
        
           | 1326 | ariadna | 8766 | function unformat_float($localefloat, $strict = false)
 | 
        
           |  |  | 8767 | {
 | 
        
           | 1 | efrain | 8768 |     $localefloat = trim((string)$localefloat);
 | 
        
           |  |  | 8769 |   | 
        
           |  |  | 8770 |     if ($localefloat == '') {
 | 
        
           |  |  | 8771 |         return null;
 | 
        
           |  |  | 8772 |     }
 | 
        
           |  |  | 8773 |   | 
        
           |  |  | 8774 |     $localefloat = str_replace(' ', '', $localefloat); // No spaces - those might be used as thousand separators.
 | 
        
           |  |  | 8775 |     $localefloat = str_replace(get_string('decsep', 'langconfig'), '.', $localefloat);
 | 
        
           |  |  | 8776 |   | 
        
           |  |  | 8777 |     if ($strict && !is_numeric($localefloat)) {
 | 
        
           |  |  | 8778 |         return false;
 | 
        
           |  |  | 8779 |     }
 | 
        
           |  |  | 8780 |   | 
        
           |  |  | 8781 |     return (float)$localefloat;
 | 
        
           |  |  | 8782 | }
 | 
        
           |  |  | 8783 |   | 
        
           |  |  | 8784 | /**
 | 
        
           |  |  | 8785 |  * Given a simple array, this shuffles it up just like shuffle()
 | 
        
           |  |  | 8786 |  * Unlike PHP's shuffle() this function works on any machine.
 | 
        
           |  |  | 8787 |  *
 | 
        
           |  |  | 8788 |  * @param array $array The array to be rearranged
 | 
        
           |  |  | 8789 |  * @return array
 | 
        
           |  |  | 8790 |  */
 | 
        
           | 1326 | ariadna | 8791 | function swapshuffle($array)
 | 
        
           |  |  | 8792 | {
 | 
        
           | 1 | efrain | 8793 |   | 
        
           |  |  | 8794 |     $last = count($array) - 1;
 | 
        
           |  |  | 8795 |     for ($i = 0; $i <= $last; $i++) {
 | 
        
           |  |  | 8796 |         $from = rand(0, $last);
 | 
        
           |  |  | 8797 |         $curr = $array[$i];
 | 
        
           |  |  | 8798 |         $array[$i] = $array[$from];
 | 
        
           |  |  | 8799 |         $array[$from] = $curr;
 | 
        
           |  |  | 8800 |     }
 | 
        
           |  |  | 8801 |     return $array;
 | 
        
           |  |  | 8802 | }
 | 
        
           |  |  | 8803 |   | 
        
           |  |  | 8804 | /**
 | 
        
           |  |  | 8805 |  * Like {@link swapshuffle()}, but works on associative arrays
 | 
        
           |  |  | 8806 |  *
 | 
        
           |  |  | 8807 |  * @param array $array The associative array to be rearranged
 | 
        
           |  |  | 8808 |  * @return array
 | 
        
           |  |  | 8809 |  */
 | 
        
           | 1326 | ariadna | 8810 | function swapshuffle_assoc($array)
 | 
        
           |  |  | 8811 | {
 | 
        
           | 1 | efrain | 8812 |   | 
        
           |  |  | 8813 |     $newarray = array();
 | 
        
           |  |  | 8814 |     $newkeys = swapshuffle(array_keys($array));
 | 
        
           |  |  | 8815 |   | 
        
           |  |  | 8816 |     foreach ($newkeys as $newkey) {
 | 
        
           |  |  | 8817 |         $newarray[$newkey] = $array[$newkey];
 | 
        
           |  |  | 8818 |     }
 | 
        
           |  |  | 8819 |     return $newarray;
 | 
        
           |  |  | 8820 | }
 | 
        
           |  |  | 8821 |   | 
        
           |  |  | 8822 | /**
 | 
        
           |  |  | 8823 |  * Given an arbitrary array, and a number of draws,
 | 
        
           |  |  | 8824 |  * this function returns an array with that amount
 | 
        
           |  |  | 8825 |  * of items.  The indexes are retained.
 | 
        
           |  |  | 8826 |  *
 | 
        
           |  |  | 8827 |  * @todo Finish documenting this function
 | 
        
           |  |  | 8828 |  *
 | 
        
           |  |  | 8829 |  * @param array $array
 | 
        
           |  |  | 8830 |  * @param int $draws
 | 
        
           |  |  | 8831 |  * @return array
 | 
        
           |  |  | 8832 |  */
 | 
        
           | 1326 | ariadna | 8833 | function draw_rand_array($array, $draws)
 | 
        
           |  |  | 8834 | {
 | 
        
           | 1 | efrain | 8835 |   | 
        
           |  |  | 8836 |     $return = array();
 | 
        
           |  |  | 8837 |   | 
        
           |  |  | 8838 |     $last = count($array);
 | 
        
           |  |  | 8839 |   | 
        
           |  |  | 8840 |     if ($draws > $last) {
 | 
        
           |  |  | 8841 |         $draws = $last;
 | 
        
           |  |  | 8842 |     }
 | 
        
           |  |  | 8843 |   | 
        
           |  |  | 8844 |     while ($draws > 0) {
 | 
        
           |  |  | 8845 |         $last--;
 | 
        
           |  |  | 8846 |   | 
        
           |  |  | 8847 |         $keys = array_keys($array);
 | 
        
           |  |  | 8848 |         $rand = rand(0, $last);
 | 
        
           |  |  | 8849 |   | 
        
           |  |  | 8850 |         $return[$keys[$rand]] = $array[$keys[$rand]];
 | 
        
           |  |  | 8851 |         unset($array[$keys[$rand]]);
 | 
        
           |  |  | 8852 |   | 
        
           |  |  | 8853 |         $draws--;
 | 
        
           |  |  | 8854 |     }
 | 
        
           |  |  | 8855 |   | 
        
           |  |  | 8856 |     return $return;
 | 
        
           |  |  | 8857 | }
 | 
        
           |  |  | 8858 |   | 
        
           |  |  | 8859 | /**
 | 
        
           |  |  | 8860 |  * Calculate the difference between two microtimes
 | 
        
           |  |  | 8861 |  *
 | 
        
           |  |  | 8862 |  * @param string $a The first Microtime
 | 
        
           |  |  | 8863 |  * @param string $b The second Microtime
 | 
        
           |  |  | 8864 |  * @return string
 | 
        
           |  |  | 8865 |  */
 | 
        
           | 1326 | ariadna | 8866 | function microtime_diff($a, $b)
 | 
        
           |  |  | 8867 | {
 | 
        
           | 1 | efrain | 8868 |     list($adec, $asec) = explode(' ', $a);
 | 
        
           |  |  | 8869 |     list($bdec, $bsec) = explode(' ', $b);
 | 
        
           |  |  | 8870 |     return $bsec - $asec + $bdec - $adec;
 | 
        
           |  |  | 8871 | }
 | 
        
           |  |  | 8872 |   | 
        
           |  |  | 8873 | /**
 | 
        
           |  |  | 8874 |  * Given a list (eg a,b,c,d,e) this function returns
 | 
        
           |  |  | 8875 |  * an array of 1->a, 2->b, 3->c etc
 | 
        
           |  |  | 8876 |  *
 | 
        
           |  |  | 8877 |  * @param string $list The string to explode into array bits
 | 
        
           |  |  | 8878 |  * @param string $separator The separator used within the list string
 | 
        
           |  |  | 8879 |  * @return array The now assembled array
 | 
        
           |  |  | 8880 |  */
 | 
        
           | 1326 | ariadna | 8881 | function make_menu_from_list($list, $separator = ',')
 | 
        
           |  |  | 8882 | {
 | 
        
           | 1 | efrain | 8883 |   | 
        
           |  |  | 8884 |     $array = array_reverse(explode($separator, $list), true);
 | 
        
           |  |  | 8885 |     foreach ($array as $key => $item) {
 | 
        
           | 1326 | ariadna | 8886 |         $outarray[$key + 1] = trim($item);
 | 
        
           | 1 | efrain | 8887 |     }
 | 
        
           |  |  | 8888 |     return $outarray;
 | 
        
           |  |  | 8889 | }
 | 
        
           |  |  | 8890 |   | 
        
           |  |  | 8891 | /**
 | 
        
           |  |  | 8892 |  * Creates an array that represents all the current grades that
 | 
        
           |  |  | 8893 |  * can be chosen using the given grading type.
 | 
        
           |  |  | 8894 |  *
 | 
        
           |  |  | 8895 |  * Negative numbers
 | 
        
           |  |  | 8896 |  * are scales, zero is no grade, and positive numbers are maximum
 | 
        
           |  |  | 8897 |  * grades.
 | 
        
           |  |  | 8898 |  *
 | 
        
           |  |  | 8899 |  * @todo Finish documenting this function or better deprecated this completely!
 | 
        
           |  |  | 8900 |  *
 | 
        
           |  |  | 8901 |  * @param int $gradingtype
 | 
        
           |  |  | 8902 |  * @return array
 | 
        
           |  |  | 8903 |  */
 | 
        
           | 1326 | ariadna | 8904 | function make_grades_menu($gradingtype)
 | 
        
           |  |  | 8905 | {
 | 
        
           | 1 | efrain | 8906 |     global $DB;
 | 
        
           |  |  | 8907 |   | 
        
           |  |  | 8908 |     $grades = array();
 | 
        
           |  |  | 8909 |     if ($gradingtype < 0) {
 | 
        
           | 1326 | ariadna | 8910 |         if ($scale = $DB->get_record('scale', array('id' => (-$gradingtype)))) {
 | 
        
           | 1 | efrain | 8911 |             return make_menu_from_list($scale->scale);
 | 
        
           |  |  | 8912 |         }
 | 
        
           |  |  | 8913 |     } else if ($gradingtype > 0) {
 | 
        
           | 1326 | ariadna | 8914 |         for ($i = $gradingtype; $i >= 0; $i--) {
 | 
        
           |  |  | 8915 |             $grades[$i] = $i . ' / ' . $gradingtype;
 | 
        
           | 1 | efrain | 8916 |         }
 | 
        
           |  |  | 8917 |         return $grades;
 | 
        
           |  |  | 8918 |     }
 | 
        
           |  |  | 8919 |     return $grades;
 | 
        
           |  |  | 8920 | }
 | 
        
           |  |  | 8921 |   | 
        
           |  |  | 8922 | /**
 | 
        
           |  |  | 8923 |  * make_unique_id_code
 | 
        
           |  |  | 8924 |  *
 | 
        
           |  |  | 8925 |  * @todo Finish documenting this function
 | 
        
           |  |  | 8926 |  *
 | 
        
           |  |  | 8927 |  * @uses $_SERVER
 | 
        
           |  |  | 8928 |  * @param string $extra Extra string to append to the end of the code
 | 
        
           |  |  | 8929 |  * @return string
 | 
        
           |  |  | 8930 |  */
 | 
        
           | 1326 | ariadna | 8931 | function make_unique_id_code($extra = '')
 | 
        
           |  |  | 8932 | {
 | 
        
           | 1 | efrain | 8933 |   | 
        
           |  |  | 8934 |     $hostname = 'unknownhost';
 | 
        
           |  |  | 8935 |     if (!empty($_SERVER['HTTP_HOST'])) {
 | 
        
           |  |  | 8936 |         $hostname = $_SERVER['HTTP_HOST'];
 | 
        
           |  |  | 8937 |     } else if (!empty($_ENV['HTTP_HOST'])) {
 | 
        
           |  |  | 8938 |         $hostname = $_ENV['HTTP_HOST'];
 | 
        
           |  |  | 8939 |     } else if (!empty($_SERVER['SERVER_NAME'])) {
 | 
        
           |  |  | 8940 |         $hostname = $_SERVER['SERVER_NAME'];
 | 
        
           |  |  | 8941 |     } else if (!empty($_ENV['SERVER_NAME'])) {
 | 
        
           |  |  | 8942 |         $hostname = $_ENV['SERVER_NAME'];
 | 
        
           |  |  | 8943 |     }
 | 
        
           |  |  | 8944 |   | 
        
           |  |  | 8945 |     $date = gmdate("ymdHis");
 | 
        
           |  |  | 8946 |   | 
        
           |  |  | 8947 |     $random =  random_string(6);
 | 
        
           |  |  | 8948 |   | 
        
           |  |  | 8949 |     if ($extra) {
 | 
        
           | 1326 | ariadna | 8950 |         return $hostname . '+' . $date . '+' . $random . '+' . $extra;
 | 
        
           | 1 | efrain | 8951 |     } else {
 | 
        
           | 1326 | ariadna | 8952 |         return $hostname . '+' . $date . '+' . $random;
 | 
        
           | 1 | efrain | 8953 |     }
 | 
        
           |  |  | 8954 | }
 | 
        
           |  |  | 8955 |   | 
        
           |  |  | 8956 |   | 
        
           |  |  | 8957 | /**
 | 
        
           |  |  | 8958 |  * Function to check the passed address is within the passed subnet
 | 
        
           |  |  | 8959 |  *
 | 
        
           |  |  | 8960 |  * The parameter is a comma separated string of subnet definitions.
 | 
        
           |  |  | 8961 |  * Subnet strings can be in one of three formats:
 | 
        
           |  |  | 8962 |  *   1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn          (number of bits in net mask)
 | 
        
           |  |  | 8963 |  *   2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
 | 
        
           |  |  | 8964 |  *   3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.                  (incomplete address, a bit non-technical ;-)
 | 
        
           |  |  | 8965 |  * Code for type 1 modified from user posted comments by mediator at
 | 
        
           |  |  | 8966 |  * {@link http://au.php.net/manual/en/function.ip2long.php}
 | 
        
           |  |  | 8967 |  *
 | 
        
           |  |  | 8968 |  * @param string $addr    The address you are checking
 | 
        
           |  |  | 8969 |  * @param string $subnetstr    The string of subnet addresses
 | 
        
           |  |  | 8970 |  * @param bool $checkallzeros    The state to whether check for 0.0.0.0
 | 
        
           |  |  | 8971 |  * @return bool
 | 
        
           |  |  | 8972 |  */
 | 
        
           | 1326 | ariadna | 8973 | function address_in_subnet($addr, $subnetstr, $checkallzeros = false)
 | 
        
           |  |  | 8974 | {
 | 
        
           | 1 | efrain | 8975 |   | 
        
           |  |  | 8976 |     if ($addr == '0.0.0.0' && !$checkallzeros) {
 | 
        
           |  |  | 8977 |         return false;
 | 
        
           |  |  | 8978 |     }
 | 
        
           |  |  | 8979 |     $subnets = explode(',', $subnetstr);
 | 
        
           |  |  | 8980 |     $found = false;
 | 
        
           |  |  | 8981 |     $addr = trim($addr);
 | 
        
           |  |  | 8982 |     $addr = cleanremoteaddr($addr, false); // Normalise.
 | 
        
           |  |  | 8983 |     if ($addr === null) {
 | 
        
           |  |  | 8984 |         return false;
 | 
        
           |  |  | 8985 |     }
 | 
        
           |  |  | 8986 |     $addrparts = explode(':', $addr);
 | 
        
           |  |  | 8987 |   | 
        
           |  |  | 8988 |     $ipv6 = strpos($addr, ':');
 | 
        
           |  |  | 8989 |   | 
        
           |  |  | 8990 |     foreach ($subnets as $subnet) {
 | 
        
           |  |  | 8991 |         $subnet = trim($subnet);
 | 
        
           |  |  | 8992 |         if ($subnet === '') {
 | 
        
           |  |  | 8993 |             continue;
 | 
        
           |  |  | 8994 |         }
 | 
        
           |  |  | 8995 |   | 
        
           |  |  | 8996 |         if (strpos($subnet, '/') !== false) {
 | 
        
           |  |  | 8997 |             // 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn.
 | 
        
           |  |  | 8998 |             list($ip, $mask) = explode('/', $subnet);
 | 
        
           |  |  | 8999 |             $mask = trim($mask);
 | 
        
           |  |  | 9000 |             if (!is_number($mask)) {
 | 
        
           |  |  | 9001 |                 continue; // Incorect mask number, eh?
 | 
        
           |  |  | 9002 |             }
 | 
        
           |  |  | 9003 |             $ip = cleanremoteaddr($ip, false); // Normalise.
 | 
        
           |  |  | 9004 |             if ($ip === null) {
 | 
        
           |  |  | 9005 |                 continue;
 | 
        
           |  |  | 9006 |             }
 | 
        
           |  |  | 9007 |             if (strpos($ip, ':') !== false) {
 | 
        
           |  |  | 9008 |                 // IPv6.
 | 
        
           |  |  | 9009 |                 if (!$ipv6) {
 | 
        
           |  |  | 9010 |                     continue;
 | 
        
           |  |  | 9011 |                 }
 | 
        
           |  |  | 9012 |                 if ($mask > 128 or $mask < 0) {
 | 
        
           |  |  | 9013 |                     continue; // Nonsense.
 | 
        
           |  |  | 9014 |                 }
 | 
        
           |  |  | 9015 |                 if ($mask == 0) {
 | 
        
           |  |  | 9016 |                     return true; // Any address.
 | 
        
           |  |  | 9017 |                 }
 | 
        
           |  |  | 9018 |                 if ($mask == 128) {
 | 
        
           |  |  | 9019 |                     if ($ip === $addr) {
 | 
        
           |  |  | 9020 |                         return true;
 | 
        
           |  |  | 9021 |                     }
 | 
        
           |  |  | 9022 |                     continue;
 | 
        
           |  |  | 9023 |                 }
 | 
        
           |  |  | 9024 |                 $ipparts = explode(':', $ip);
 | 
        
           |  |  | 9025 |                 $modulo  = $mask % 16;
 | 
        
           | 1326 | ariadna | 9026 |                 $ipnet   = array_slice($ipparts, 0, ($mask - $modulo) / 16);
 | 
        
           |  |  | 9027 |                 $addrnet = array_slice($addrparts, 0, ($mask - $modulo) / 16);
 | 
        
           | 1 | efrain | 9028 |                 if (implode(':', $ipnet) === implode(':', $addrnet)) {
 | 
        
           |  |  | 9029 |                     if ($modulo == 0) {
 | 
        
           |  |  | 9030 |                         return true;
 | 
        
           |  |  | 9031 |                     }
 | 
        
           | 1326 | ariadna | 9032 |                     $pos     = ($mask - $modulo) / 16;
 | 
        
           | 1 | efrain | 9033 |                     $ipnet   = hexdec($ipparts[$pos]);
 | 
        
           |  |  | 9034 |                     $addrnet = hexdec($addrparts[$pos]);
 | 
        
           |  |  | 9035 |                     $mask    = 0xffff << (16 - $modulo);
 | 
        
           |  |  | 9036 |                     if (($addrnet & $mask) == ($ipnet & $mask)) {
 | 
        
           |  |  | 9037 |                         return true;
 | 
        
           |  |  | 9038 |                     }
 | 
        
           |  |  | 9039 |                 }
 | 
        
           |  |  | 9040 |             } else {
 | 
        
           |  |  | 9041 |                 // IPv4.
 | 
        
           |  |  | 9042 |                 if ($ipv6) {
 | 
        
           |  |  | 9043 |                     continue;
 | 
        
           |  |  | 9044 |                 }
 | 
        
           |  |  | 9045 |                 if ($mask > 32 or $mask < 0) {
 | 
        
           |  |  | 9046 |                     continue; // Nonsense.
 | 
        
           |  |  | 9047 |                 }
 | 
        
           |  |  | 9048 |                 if ($mask == 0) {
 | 
        
           |  |  | 9049 |                     return true;
 | 
        
           |  |  | 9050 |                 }
 | 
        
           |  |  | 9051 |                 if ($mask == 32) {
 | 
        
           |  |  | 9052 |                     if ($ip === $addr) {
 | 
        
           |  |  | 9053 |                         return true;
 | 
        
           |  |  | 9054 |                     }
 | 
        
           |  |  | 9055 |                     continue;
 | 
        
           |  |  | 9056 |                 }
 | 
        
           |  |  | 9057 |                 $mask = 0xffffffff << (32 - $mask);
 | 
        
           |  |  | 9058 |                 if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
 | 
        
           |  |  | 9059 |                     return true;
 | 
        
           |  |  | 9060 |                 }
 | 
        
           |  |  | 9061 |             }
 | 
        
           |  |  | 9062 |         } else if (strpos($subnet, '-') !== false) {
 | 
        
           |  |  | 9063 |             // 2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy. A range of IP addresses in the last group.
 | 
        
           |  |  | 9064 |             $parts = explode('-', $subnet);
 | 
        
           |  |  | 9065 |             if (count($parts) != 2) {
 | 
        
           |  |  | 9066 |                 continue;
 | 
        
           |  |  | 9067 |             }
 | 
        
           |  |  | 9068 |   | 
        
           |  |  | 9069 |             if (strpos($subnet, ':') !== false) {
 | 
        
           |  |  | 9070 |                 // IPv6.
 | 
        
           |  |  | 9071 |                 if (!$ipv6) {
 | 
        
           |  |  | 9072 |                     continue;
 | 
        
           |  |  | 9073 |                 }
 | 
        
           |  |  | 9074 |                 $ipstart = cleanremoteaddr(trim($parts[0]), false); // Normalise.
 | 
        
           |  |  | 9075 |                 if ($ipstart === null) {
 | 
        
           |  |  | 9076 |                     continue;
 | 
        
           |  |  | 9077 |                 }
 | 
        
           |  |  | 9078 |                 $ipparts = explode(':', $ipstart);
 | 
        
           |  |  | 9079 |                 $start = hexdec(array_pop($ipparts));
 | 
        
           |  |  | 9080 |                 $ipparts[] = trim($parts[1]);
 | 
        
           |  |  | 9081 |                 $ipend = cleanremoteaddr(implode(':', $ipparts), false); // Normalise.
 | 
        
           |  |  | 9082 |                 if ($ipend === null) {
 | 
        
           |  |  | 9083 |                     continue;
 | 
        
           |  |  | 9084 |                 }
 | 
        
           |  |  | 9085 |                 $ipparts[7] = '';
 | 
        
           |  |  | 9086 |                 $ipnet = implode(':', $ipparts);
 | 
        
           |  |  | 9087 |                 if (strpos($addr, $ipnet) !== 0) {
 | 
        
           |  |  | 9088 |                     continue;
 | 
        
           |  |  | 9089 |                 }
 | 
        
           |  |  | 9090 |                 $ipparts = explode(':', $ipend);
 | 
        
           |  |  | 9091 |                 $end = hexdec($ipparts[7]);
 | 
        
           |  |  | 9092 |   | 
        
           |  |  | 9093 |                 $addrend = hexdec($addrparts[7]);
 | 
        
           |  |  | 9094 |   | 
        
           |  |  | 9095 |                 if (($addrend >= $start) and ($addrend <= $end)) {
 | 
        
           |  |  | 9096 |                     return true;
 | 
        
           |  |  | 9097 |                 }
 | 
        
           |  |  | 9098 |             } else {
 | 
        
           |  |  | 9099 |                 // IPv4.
 | 
        
           |  |  | 9100 |                 if ($ipv6) {
 | 
        
           |  |  | 9101 |                     continue;
 | 
        
           |  |  | 9102 |                 }
 | 
        
           |  |  | 9103 |                 $ipstart = cleanremoteaddr(trim($parts[0]), false); // Normalise.
 | 
        
           |  |  | 9104 |                 if ($ipstart === null) {
 | 
        
           |  |  | 9105 |                     continue;
 | 
        
           |  |  | 9106 |                 }
 | 
        
           |  |  | 9107 |                 $ipparts = explode('.', $ipstart);
 | 
        
           |  |  | 9108 |                 $ipparts[3] = trim($parts[1]);
 | 
        
           |  |  | 9109 |                 $ipend = cleanremoteaddr(implode('.', $ipparts), false); // Normalise.
 | 
        
           |  |  | 9110 |                 if ($ipend === null) {
 | 
        
           |  |  | 9111 |                     continue;
 | 
        
           |  |  | 9112 |                 }
 | 
        
           |  |  | 9113 |   | 
        
           |  |  | 9114 |                 if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
 | 
        
           |  |  | 9115 |                     return true;
 | 
        
           |  |  | 9116 |                 }
 | 
        
           |  |  | 9117 |             }
 | 
        
           |  |  | 9118 |         } else {
 | 
        
           |  |  | 9119 |             // 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
 | 
        
           |  |  | 9120 |             if (strpos($subnet, ':') !== false) {
 | 
        
           |  |  | 9121 |                 // IPv6.
 | 
        
           |  |  | 9122 |                 if (!$ipv6) {
 | 
        
           |  |  | 9123 |                     continue;
 | 
        
           |  |  | 9124 |                 }
 | 
        
           |  |  | 9125 |                 $parts = explode(':', $subnet);
 | 
        
           |  |  | 9126 |                 $count = count($parts);
 | 
        
           | 1326 | ariadna | 9127 |                 if ($parts[$count - 1] === '') {
 | 
        
           |  |  | 9128 |                     unset($parts[$count - 1]); // Trim trailing :'s.
 | 
        
           | 1 | efrain | 9129 |                     $count--;
 | 
        
           |  |  | 9130 |                     $subnet = implode('.', $parts);
 | 
        
           |  |  | 9131 |                 }
 | 
        
           |  |  | 9132 |                 $isip = cleanremoteaddr($subnet, false); // Normalise.
 | 
        
           |  |  | 9133 |                 if ($isip !== null) {
 | 
        
           |  |  | 9134 |                     if ($isip === $addr) {
 | 
        
           |  |  | 9135 |                         return true;
 | 
        
           |  |  | 9136 |                     }
 | 
        
           |  |  | 9137 |                     continue;
 | 
        
           |  |  | 9138 |                 } else if ($count > 8) {
 | 
        
           |  |  | 9139 |                     continue;
 | 
        
           |  |  | 9140 |                 }
 | 
        
           | 1326 | ariadna | 9141 |                 $zeros = array_fill(0, 8 - $count, '0');
 | 
        
           |  |  | 9142 |                 $subnet = $subnet . ':' . implode(':', $zeros) . '/' . ($count * 16);
 | 
        
           | 1 | efrain | 9143 |                 if (address_in_subnet($addr, $subnet)) {
 | 
        
           |  |  | 9144 |                     return true;
 | 
        
           |  |  | 9145 |                 }
 | 
        
           |  |  | 9146 |             } else {
 | 
        
           |  |  | 9147 |                 // IPv4.
 | 
        
           |  |  | 9148 |                 if ($ipv6) {
 | 
        
           |  |  | 9149 |                     continue;
 | 
        
           |  |  | 9150 |                 }
 | 
        
           |  |  | 9151 |                 $parts = explode('.', $subnet);
 | 
        
           |  |  | 9152 |                 $count = count($parts);
 | 
        
           | 1326 | ariadna | 9153 |                 if ($parts[$count - 1] === '') {
 | 
        
           |  |  | 9154 |                     unset($parts[$count - 1]); // Trim trailing .
 | 
        
           | 1 | efrain | 9155 |                     $count--;
 | 
        
           |  |  | 9156 |                     $subnet = implode('.', $parts);
 | 
        
           |  |  | 9157 |                 }
 | 
        
           |  |  | 9158 |                 if ($count == 4) {
 | 
        
           |  |  | 9159 |                     $subnet = cleanremoteaddr($subnet, false); // Normalise.
 | 
        
           |  |  | 9160 |                     if ($subnet === $addr) {
 | 
        
           |  |  | 9161 |                         return true;
 | 
        
           |  |  | 9162 |                     }
 | 
        
           |  |  | 9163 |                     continue;
 | 
        
           |  |  | 9164 |                 } else if ($count > 4) {
 | 
        
           |  |  | 9165 |                     continue;
 | 
        
           |  |  | 9166 |                 }
 | 
        
           | 1326 | ariadna | 9167 |                 $zeros = array_fill(0, 4 - $count, '0');
 | 
        
           |  |  | 9168 |                 $subnet = $subnet . '.' . implode('.', $zeros) . '/' . ($count * 8);
 | 
        
           | 1 | efrain | 9169 |                 if (address_in_subnet($addr, $subnet)) {
 | 
        
           |  |  | 9170 |                     return true;
 | 
        
           |  |  | 9171 |                 }
 | 
        
           |  |  | 9172 |             }
 | 
        
           |  |  | 9173 |         }
 | 
        
           |  |  | 9174 |     }
 | 
        
           |  |  | 9175 |   | 
        
           |  |  | 9176 |     return false;
 | 
        
           |  |  | 9177 | }
 | 
        
           |  |  | 9178 |   | 
        
           |  |  | 9179 | /**
 | 
        
           |  |  | 9180 |  * For outputting debugging info
 | 
        
           |  |  | 9181 |  *
 | 
        
           |  |  | 9182 |  * @param string $string The string to write
 | 
        
           |  |  | 9183 |  * @param string $eol The end of line char(s) to use
 | 
        
           |  |  | 9184 |  * @param string $sleep Period to make the application sleep
 | 
        
           |  |  | 9185 |  *                      This ensures any messages have time to display before redirect
 | 
        
           |  |  | 9186 |  */
 | 
        
           | 1326 | ariadna | 9187 | function mtrace($string, $eol = "\n", $sleep = 0)
 | 
        
           |  |  | 9188 | {
 | 
        
           | 1 | efrain | 9189 |     global $CFG;
 | 
        
           |  |  | 9190 |   | 
        
           |  |  | 9191 |     if (isset($CFG->mtrace_wrapper) && function_exists($CFG->mtrace_wrapper)) {
 | 
        
           |  |  | 9192 |         $fn = $CFG->mtrace_wrapper;
 | 
        
           |  |  | 9193 |         $fn($string, $eol);
 | 
        
           |  |  | 9194 |         return;
 | 
        
           |  |  | 9195 |     } else if (defined('STDOUT') && !PHPUNIT_TEST && !defined('BEHAT_TEST')) {
 | 
        
           |  |  | 9196 |         // We must explicitly call the add_line function here.
 | 
        
           |  |  | 9197 |         // Uses of fwrite to STDOUT are not picked up by ob_start.
 | 
        
           |  |  | 9198 |         if ($output = \core\task\logmanager::add_line("{$string}{$eol}")) {
 | 
        
           |  |  | 9199 |             fwrite(STDOUT, $output);
 | 
        
           |  |  | 9200 |         }
 | 
        
           |  |  | 9201 |     } else {
 | 
        
           |  |  | 9202 |         echo $string . $eol;
 | 
        
           |  |  | 9203 |     }
 | 
        
           |  |  | 9204 |   | 
        
           |  |  | 9205 |     // Flush again.
 | 
        
           |  |  | 9206 |     flush();
 | 
        
           |  |  | 9207 |   | 
        
           |  |  | 9208 |     // Delay to keep message on user's screen in case of subsequent redirect.
 | 
        
           |  |  | 9209 |     if ($sleep) {
 | 
        
           |  |  | 9210 |         sleep($sleep);
 | 
        
           |  |  | 9211 |     }
 | 
        
           |  |  | 9212 | }
 | 
        
           |  |  | 9213 |   | 
        
           |  |  | 9214 | /**
 | 
        
           |  |  | 9215 |  * Helper to {@see mtrace()} an exception or throwable, including all relevant information.
 | 
        
           |  |  | 9216 |  *
 | 
        
           |  |  | 9217 |  * @param Throwable $e the error to ouptput.
 | 
        
           |  |  | 9218 |  */
 | 
        
           | 1326 | ariadna | 9219 | function mtrace_exception(Throwable $e): void
 | 
        
           |  |  | 9220 | {
 | 
        
           | 1 | efrain | 9221 |     $info = get_exception_info($e);
 | 
        
           |  |  | 9222 |   | 
        
           |  |  | 9223 |     $message = $info->message;
 | 
        
           |  |  | 9224 |     if ($info->debuginfo) {
 | 
        
           |  |  | 9225 |         $message .= "\n\n" . $info->debuginfo;
 | 
        
           |  |  | 9226 |     }
 | 
        
           |  |  | 9227 |     if ($info->backtrace) {
 | 
        
           |  |  | 9228 |         $message .= "\n\n" . format_backtrace($info->backtrace, true);
 | 
        
           |  |  | 9229 |     }
 | 
        
           |  |  | 9230 |   | 
        
           |  |  | 9231 |     mtrace($message);
 | 
        
           |  |  | 9232 | }
 | 
        
           |  |  | 9233 |   | 
        
           |  |  | 9234 | /**
 | 
        
           |  |  | 9235 |  * Replace 1 or more slashes or backslashes to 1 slash
 | 
        
           |  |  | 9236 |  *
 | 
        
           |  |  | 9237 |  * @param string $path The path to strip
 | 
        
           |  |  | 9238 |  * @return string the path with double slashes removed
 | 
        
           |  |  | 9239 |  */
 | 
        
           | 1326 | ariadna | 9240 | function cleardoubleslashes($path)
 | 
        
           |  |  | 9241 | {
 | 
        
           | 1 | efrain | 9242 |     return preg_replace('/(\/|\\\){1,}/', '/', $path);
 | 
        
           |  |  | 9243 | }
 | 
        
           |  |  | 9244 |   | 
        
           |  |  | 9245 | /**
 | 
        
           |  |  | 9246 |  * Is the current ip in a given list?
 | 
        
           |  |  | 9247 |  *
 | 
        
           |  |  | 9248 |  * @param string $list
 | 
        
           |  |  | 9249 |  * @return bool
 | 
        
           |  |  | 9250 |  */
 | 
        
           | 1326 | ariadna | 9251 | function remoteip_in_list($list)
 | 
        
           |  |  | 9252 | {
 | 
        
           | 1 | efrain | 9253 |     $clientip = getremoteaddr(null);
 | 
        
           |  |  | 9254 |   | 
        
           |  |  | 9255 |     if (!$clientip) {
 | 
        
           |  |  | 9256 |         // Ensure access on cli.
 | 
        
           |  |  | 9257 |         return true;
 | 
        
           |  |  | 9258 |     }
 | 
        
           |  |  | 9259 |     return \core\ip_utils::is_ip_in_subnet_list($clientip, $list);
 | 
        
           |  |  | 9260 | }
 | 
        
           |  |  | 9261 |   | 
        
           |  |  | 9262 | /**
 | 
        
           |  |  | 9263 |  * Returns most reliable client address
 | 
        
           |  |  | 9264 |  *
 | 
        
           |  |  | 9265 |  * @param string $default If an address can't be determined, then return this
 | 
        
           |  |  | 9266 |  * @return string The remote IP address
 | 
        
           |  |  | 9267 |  */
 | 
        
           | 1326 | ariadna | 9268 | function getremoteaddr($default = '0.0.0.0')
 | 
        
           |  |  | 9269 | {
 | 
        
           | 1 | efrain | 9270 |     global $CFG;
 | 
        
           |  |  | 9271 |   | 
        
           |  |  | 9272 |     if (!isset($CFG->getremoteaddrconf)) {
 | 
        
           |  |  | 9273 |         // This will happen, for example, before just after the upgrade, as the
 | 
        
           |  |  | 9274 |         // user is redirected to the admin screen.
 | 
        
           |  |  | 9275 |         $variablestoskip = GETREMOTEADDR_SKIP_DEFAULT;
 | 
        
           |  |  | 9276 |     } else {
 | 
        
           |  |  | 9277 |         $variablestoskip = $CFG->getremoteaddrconf;
 | 
        
           |  |  | 9278 |     }
 | 
        
           |  |  | 9279 |     if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_CLIENT_IP)) {
 | 
        
           |  |  | 9280 |         if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
 | 
        
           |  |  | 9281 |             $address = cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
 | 
        
           |  |  | 9282 |             return $address ? $address : $default;
 | 
        
           |  |  | 9283 |         }
 | 
        
           |  |  | 9284 |     }
 | 
        
           |  |  | 9285 |     if (!($variablestoskip & GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR)) {
 | 
        
           |  |  | 9286 |         if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
 | 
        
           |  |  | 9287 |             $forwardedaddresses = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']);
 | 
        
           |  |  | 9288 |   | 
        
           | 1326 | ariadna | 9289 |             $forwardedaddresses = array_filter($forwardedaddresses, function ($ip) {
 | 
        
           | 1 | efrain | 9290 |                 global $CFG;
 | 
        
           |  |  | 9291 |                 return !\core\ip_utils::is_ip_in_subnet_list($ip, $CFG->reverseproxyignore ?? '', ',');
 | 
        
           |  |  | 9292 |             });
 | 
        
           |  |  | 9293 |   | 
        
           |  |  | 9294 |             // Multiple proxies can append values to this header including an
 | 
        
           |  |  | 9295 |             // untrusted original request header so we must only trust the last ip.
 | 
        
           |  |  | 9296 |             $address = end($forwardedaddresses);
 | 
        
           |  |  | 9297 |   | 
        
           |  |  | 9298 |             if (substr_count($address, ":") > 1) {
 | 
        
           |  |  | 9299 |                 // Remove port and brackets from IPv6.
 | 
        
           |  |  | 9300 |                 if (preg_match("/\[(.*)\]:/", $address, $matches)) {
 | 
        
           |  |  | 9301 |                     $address = $matches[1];
 | 
        
           |  |  | 9302 |                 }
 | 
        
           |  |  | 9303 |             } else {
 | 
        
           |  |  | 9304 |                 // Remove port from IPv4.
 | 
        
           |  |  | 9305 |                 if (substr_count($address, ":") == 1) {
 | 
        
           |  |  | 9306 |                     $parts = explode(":", $address);
 | 
        
           |  |  | 9307 |                     $address = $parts[0];
 | 
        
           |  |  | 9308 |                 }
 | 
        
           |  |  | 9309 |             }
 | 
        
           |  |  | 9310 |   | 
        
           |  |  | 9311 |             $address = cleanremoteaddr($address);
 | 
        
           |  |  | 9312 |             return $address ? $address : $default;
 | 
        
           |  |  | 9313 |         }
 | 
        
           |  |  | 9314 |     }
 | 
        
           |  |  | 9315 |     if (!empty($_SERVER['REMOTE_ADDR'])) {
 | 
        
           |  |  | 9316 |         $address = cleanremoteaddr($_SERVER['REMOTE_ADDR']);
 | 
        
           |  |  | 9317 |         return $address ? $address : $default;
 | 
        
           |  |  | 9318 |     } else {
 | 
        
           |  |  | 9319 |         return $default;
 | 
        
           |  |  | 9320 |     }
 | 
        
           |  |  | 9321 | }
 | 
        
           |  |  | 9322 |   | 
        
           |  |  | 9323 | /**
 | 
        
           |  |  | 9324 |  * Cleans an ip address. Internal addresses are now allowed.
 | 
        
           |  |  | 9325 |  * (Originally local addresses were not allowed.)
 | 
        
           |  |  | 9326 |  *
 | 
        
           |  |  | 9327 |  * @param string $addr IPv4 or IPv6 address
 | 
        
           |  |  | 9328 |  * @param bool $compress use IPv6 address compression
 | 
        
           |  |  | 9329 |  * @return string normalised ip address string, null if error
 | 
        
           |  |  | 9330 |  */
 | 
        
           | 1326 | ariadna | 9331 | function cleanremoteaddr($addr, $compress = false)
 | 
        
           |  |  | 9332 | {
 | 
        
           | 1 | efrain | 9333 |     $addr = trim($addr);
 | 
        
           |  |  | 9334 |   | 
        
           |  |  | 9335 |     if (strpos($addr, ':') !== false) {
 | 
        
           |  |  | 9336 |         // Can be only IPv6.
 | 
        
           |  |  | 9337 |         $parts = explode(':', $addr);
 | 
        
           |  |  | 9338 |         $count = count($parts);
 | 
        
           |  |  | 9339 |   | 
        
           | 1326 | ariadna | 9340 |         if (strpos($parts[$count - 1], '.') !== false) {
 | 
        
           | 1 | efrain | 9341 |             // Legacy ipv4 notation.
 | 
        
           |  |  | 9342 |             $last = array_pop($parts);
 | 
        
           |  |  | 9343 |             $ipv4 = cleanremoteaddr($last, true);
 | 
        
           |  |  | 9344 |             if ($ipv4 === null) {
 | 
        
           |  |  | 9345 |                 return null;
 | 
        
           |  |  | 9346 |             }
 | 
        
           |  |  | 9347 |             $bits = explode('.', $ipv4);
 | 
        
           | 1326 | ariadna | 9348 |             $parts[] = dechex($bits[0]) . dechex($bits[1]);
 | 
        
           |  |  | 9349 |             $parts[] = dechex($bits[2]) . dechex($bits[3]);
 | 
        
           | 1 | efrain | 9350 |             $count = count($parts);
 | 
        
           |  |  | 9351 |             $addr = implode(':', $parts);
 | 
        
           |  |  | 9352 |         }
 | 
        
           |  |  | 9353 |   | 
        
           |  |  | 9354 |         if ($count < 3 or $count > 8) {
 | 
        
           |  |  | 9355 |             return null; // Severly malformed.
 | 
        
           |  |  | 9356 |         }
 | 
        
           |  |  | 9357 |   | 
        
           |  |  | 9358 |         if ($count != 8) {
 | 
        
           |  |  | 9359 |             if (strpos($addr, '::') === false) {
 | 
        
           |  |  | 9360 |                 return null; // Malformed.
 | 
        
           |  |  | 9361 |             }
 | 
        
           |  |  | 9362 |             // Uncompress.
 | 
        
           |  |  | 9363 |             $insertat = array_search('', $parts, true);
 | 
        
           |  |  | 9364 |             $missing = array_fill(0, 1 + 8 - $count, '0');
 | 
        
           |  |  | 9365 |             array_splice($parts, $insertat, 1, $missing);
 | 
        
           |  |  | 9366 |             foreach ($parts as $key => $part) {
 | 
        
           |  |  | 9367 |                 if ($part === '') {
 | 
        
           |  |  | 9368 |                     $parts[$key] = '0';
 | 
        
           |  |  | 9369 |                 }
 | 
        
           |  |  | 9370 |             }
 | 
        
           |  |  | 9371 |         }
 | 
        
           |  |  | 9372 |   | 
        
           |  |  | 9373 |         $adr = implode(':', $parts);
 | 
        
           |  |  | 9374 |         if (!preg_match('/^([0-9a-f]{1,4})(:[0-9a-f]{1,4})*$/i', $adr)) {
 | 
        
           |  |  | 9375 |             return null; // Incorrect format - sorry.
 | 
        
           |  |  | 9376 |         }
 | 
        
           |  |  | 9377 |   | 
        
           |  |  | 9378 |         // Normalise 0s and case.
 | 
        
           |  |  | 9379 |         $parts = array_map('hexdec', $parts);
 | 
        
           |  |  | 9380 |         $parts = array_map('dechex', $parts);
 | 
        
           |  |  | 9381 |   | 
        
           |  |  | 9382 |         $result = implode(':', $parts);
 | 
        
           |  |  | 9383 |   | 
        
           |  |  | 9384 |         if (!$compress) {
 | 
        
           |  |  | 9385 |             return $result;
 | 
        
           |  |  | 9386 |         }
 | 
        
           |  |  | 9387 |   | 
        
           |  |  | 9388 |         if ($result === '0:0:0:0:0:0:0:0') {
 | 
        
           |  |  | 9389 |             return '::'; // All addresses.
 | 
        
           |  |  | 9390 |         }
 | 
        
           |  |  | 9391 |   | 
        
           |  |  | 9392 |         $compressed = preg_replace('/(:0)+:0$/', '::', $result, 1);
 | 
        
           |  |  | 9393 |         if ($compressed !== $result) {
 | 
        
           |  |  | 9394 |             return $compressed;
 | 
        
           |  |  | 9395 |         }
 | 
        
           |  |  | 9396 |   | 
        
           |  |  | 9397 |         $compressed = preg_replace('/^(0:){2,7}/', '::', $result, 1);
 | 
        
           |  |  | 9398 |         if ($compressed !== $result) {
 | 
        
           |  |  | 9399 |             return $compressed;
 | 
        
           |  |  | 9400 |         }
 | 
        
           |  |  | 9401 |   | 
        
           |  |  | 9402 |         $compressed = preg_replace('/(:0){2,6}:/', '::', $result, 1);
 | 
        
           |  |  | 9403 |         if ($compressed !== $result) {
 | 
        
           |  |  | 9404 |             return $compressed;
 | 
        
           |  |  | 9405 |         }
 | 
        
           |  |  | 9406 |   | 
        
           |  |  | 9407 |         return $result;
 | 
        
           |  |  | 9408 |     }
 | 
        
           |  |  | 9409 |   | 
        
           |  |  | 9410 |     // First get all things that look like IPv4 addresses.
 | 
        
           |  |  | 9411 |     $parts = array();
 | 
        
           |  |  | 9412 |     if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $addr, $parts)) {
 | 
        
           |  |  | 9413 |         return null;
 | 
        
           |  |  | 9414 |     }
 | 
        
           |  |  | 9415 |     unset($parts[0]);
 | 
        
           |  |  | 9416 |   | 
        
           |  |  | 9417 |     foreach ($parts as $key => $match) {
 | 
        
           |  |  | 9418 |         if ($match > 255) {
 | 
        
           |  |  | 9419 |             return null;
 | 
        
           |  |  | 9420 |         }
 | 
        
           |  |  | 9421 |         $parts[$key] = (int)$match; // Normalise 0s.
 | 
        
           |  |  | 9422 |     }
 | 
        
           |  |  | 9423 |   | 
        
           |  |  | 9424 |     return implode('.', $parts);
 | 
        
           |  |  | 9425 | }
 | 
        
           |  |  | 9426 |   | 
        
           |  |  | 9427 |   | 
        
           |  |  | 9428 | /**
 | 
        
           |  |  | 9429 |  * Is IP address a public address?
 | 
        
           |  |  | 9430 |  *
 | 
        
           |  |  | 9431 |  * @param string $ip The ip to check
 | 
        
           |  |  | 9432 |  * @return bool true if the ip is public
 | 
        
           |  |  | 9433 |  */
 | 
        
           | 1326 | ariadna | 9434 | function ip_is_public($ip)
 | 
        
           |  |  | 9435 | {
 | 
        
           | 1 | efrain | 9436 |     return (bool) filter_var($ip, FILTER_VALIDATE_IP, (FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE));
 | 
        
           |  |  | 9437 | }
 | 
        
           |  |  | 9438 |   | 
        
           |  |  | 9439 | /**
 | 
        
           |  |  | 9440 |  * This function will make a complete copy of anything it's given,
 | 
        
           |  |  | 9441 |  * regardless of whether it's an object or not.
 | 
        
           |  |  | 9442 |  *
 | 
        
           |  |  | 9443 |  * @param mixed $thing Something you want cloned
 | 
        
           |  |  | 9444 |  * @return mixed What ever it is you passed it
 | 
        
           |  |  | 9445 |  */
 | 
        
           | 1326 | ariadna | 9446 | function fullclone($thing)
 | 
        
           |  |  | 9447 | {
 | 
        
           | 1 | efrain | 9448 |     return unserialize(serialize($thing));
 | 
        
           |  |  | 9449 | }
 | 
        
           |  |  | 9450 |   | 
        
           |  |  | 9451 | /**
 | 
        
           |  |  | 9452 |  * Used to make sure that $min <= $value <= $max
 | 
        
           |  |  | 9453 |  *
 | 
        
           |  |  | 9454 |  * Make sure that value is between min, and max
 | 
        
           |  |  | 9455 |  *
 | 
        
           |  |  | 9456 |  * @param int $min The minimum value
 | 
        
           |  |  | 9457 |  * @param int $value The value to check
 | 
        
           |  |  | 9458 |  * @param int $max The maximum value
 | 
        
           |  |  | 9459 |  * @return int
 | 
        
           |  |  | 9460 |  */
 | 
        
           | 1326 | ariadna | 9461 | function bounded_number($min, $value, $max)
 | 
        
           |  |  | 9462 | {
 | 
        
           | 1 | efrain | 9463 |     if ($value < $min) {
 | 
        
           |  |  | 9464 |         return $min;
 | 
        
           |  |  | 9465 |     }
 | 
        
           |  |  | 9466 |     if ($value > $max) {
 | 
        
           |  |  | 9467 |         return $max;
 | 
        
           |  |  | 9468 |     }
 | 
        
           |  |  | 9469 |     return $value;
 | 
        
           |  |  | 9470 | }
 | 
        
           |  |  | 9471 |   | 
        
           |  |  | 9472 | /**
 | 
        
           |  |  | 9473 |  * Check if there is a nested array within the passed array
 | 
        
           |  |  | 9474 |  *
 | 
        
           |  |  | 9475 |  * @param array $array
 | 
        
           |  |  | 9476 |  * @return bool true if there is a nested array false otherwise
 | 
        
           |  |  | 9477 |  */
 | 
        
           | 1326 | ariadna | 9478 | function array_is_nested($array)
 | 
        
           |  |  | 9479 | {
 | 
        
           | 1 | efrain | 9480 |     foreach ($array as $value) {
 | 
        
           |  |  | 9481 |         if (is_array($value)) {
 | 
        
           |  |  | 9482 |             return true;
 | 
        
           |  |  | 9483 |         }
 | 
        
           |  |  | 9484 |     }
 | 
        
           |  |  | 9485 |     return false;
 | 
        
           |  |  | 9486 | }
 | 
        
           |  |  | 9487 |   | 
        
           |  |  | 9488 | /**
 | 
        
           |  |  | 9489 |  * get_performance_info() pairs up with init_performance_info()
 | 
        
           |  |  | 9490 |  * loaded in setup.php. Returns an array with 'html' and 'txt'
 | 
        
           |  |  | 9491 |  * values ready for use, and each of the individual stats provided
 | 
        
           |  |  | 9492 |  * separately as well.
 | 
        
           |  |  | 9493 |  *
 | 
        
           |  |  | 9494 |  * @return array
 | 
        
           |  |  | 9495 |  */
 | 
        
           | 1326 | ariadna | 9496 | function get_performance_info()
 | 
        
           |  |  | 9497 | {
 | 
        
           | 1 | efrain | 9498 |     global $CFG, $PERF, $DB, $PAGE;
 | 
        
           |  |  | 9499 |   | 
        
           |  |  | 9500 |     $info = array();
 | 
        
           |  |  | 9501 |     $info['txt']  = me() . ' '; // Holds log-friendly representation.
 | 
        
           |  |  | 9502 |   | 
        
           |  |  | 9503 |     $info['html'] = '';
 | 
        
           |  |  | 9504 |     if (!empty($CFG->themedesignermode)) {
 | 
        
           |  |  | 9505 |         // Attempt to avoid devs debugging peformance issues, when its caused by css building and so on.
 | 
        
           |  |  | 9506 |         $info['html'] .= '<p><strong>Warning: Theme designer mode is enabled.</strong></p>';
 | 
        
           |  |  | 9507 |     }
 | 
        
           |  |  | 9508 |     $info['html'] .= '<ul class="list-unstyled row mx-md-0">';         // Holds userfriendly HTML representation.
 | 
        
           |  |  | 9509 |   | 
        
           |  |  | 9510 |     $info['realtime'] = microtime_diff($PERF->starttime, microtime());
 | 
        
           |  |  | 9511 |   | 
        
           | 1326 | ariadna | 9512 |     $info['html'] .= '<li class="timeused col-sm-4">' . $info['realtime'] . ' secs</li> ';
 | 
        
           |  |  | 9513 |     $info['txt'] .= 'time: ' . $info['realtime'] . 's ';
 | 
        
           | 1 | efrain | 9514 |   | 
        
           |  |  | 9515 |     // GET/POST (or NULL if $_SERVER['REQUEST_METHOD'] is undefined) is useful for txt logged information.
 | 
        
           |  |  | 9516 |     $info['txt'] .= 'method: ' . ($_SERVER['REQUEST_METHOD'] ?? "NULL") . ' ';
 | 
        
           |  |  | 9517 |   | 
        
           |  |  | 9518 |     if (function_exists('memory_get_usage')) {
 | 
        
           |  |  | 9519 |         $info['memory_total'] = memory_get_usage();
 | 
        
           |  |  | 9520 |         $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
 | 
        
           | 1326 | ariadna | 9521 |         $info['html'] .= '<li class="memoryused col-sm-4">RAM: ' . display_size($info['memory_total']) . '</li> ';
 | 
        
           |  |  | 9522 |         $info['txt']  .= 'memory_total: ' . $info['memory_total'] . 'B (' . display_size($info['memory_total']) . ') memory_growth: ' .
 | 
        
           |  |  | 9523 |             $info['memory_growth'] . 'B (' . display_size($info['memory_growth']) . ') ';
 | 
        
           | 1 | efrain | 9524 |     }
 | 
        
           |  |  | 9525 |   | 
        
           |  |  | 9526 |     if (function_exists('memory_get_peak_usage')) {
 | 
        
           |  |  | 9527 |         $info['memory_peak'] = memory_get_peak_usage();
 | 
        
           | 1326 | ariadna | 9528 |         $info['html'] .= '<li class="memoryused col-sm-4">RAM peak: ' . display_size($info['memory_peak']) . '</li> ';
 | 
        
           |  |  | 9529 |         $info['txt']  .= 'memory_peak: ' . $info['memory_peak'] . 'B (' . display_size($info['memory_peak']) . ') ';
 | 
        
           | 1 | efrain | 9530 |     }
 | 
        
           |  |  | 9531 |   | 
        
           |  |  | 9532 |     $info['html'] .= '</ul><ul class="list-unstyled row mx-md-0">';
 | 
        
           |  |  | 9533 |     $inc = get_included_files();
 | 
        
           |  |  | 9534 |     $info['includecount'] = count($inc);
 | 
        
           | 1326 | ariadna | 9535 |     $info['html'] .= '<li class="included col-sm-4">Included ' . $info['includecount'] . ' files</li> ';
 | 
        
           |  |  | 9536 |     $info['txt']  .= 'includecount: ' . $info['includecount'] . ' ';
 | 
        
           | 1 | efrain | 9537 |   | 
        
           |  |  | 9538 |     if (!empty($CFG->early_install_lang) or empty($PAGE)) {
 | 
        
           |  |  | 9539 |         // We can not track more performance before installation or before PAGE init, sorry.
 | 
        
           |  |  | 9540 |         return $info;
 | 
        
           |  |  | 9541 |     }
 | 
        
           |  |  | 9542 |   | 
        
           |  |  | 9543 |     $filtermanager = filter_manager::instance();
 | 
        
           |  |  | 9544 |     if (method_exists($filtermanager, 'get_performance_summary')) {
 | 
        
           |  |  | 9545 |         list($filterinfo, $nicenames) = $filtermanager->get_performance_summary();
 | 
        
           |  |  | 9546 |         $info = array_merge($filterinfo, $info);
 | 
        
           |  |  | 9547 |         foreach ($filterinfo as $key => $value) {
 | 
        
           |  |  | 9548 |             $info['html'] .= "<li class='$key col-sm-4'>$nicenames[$key]: $value </li> ";
 | 
        
           |  |  | 9549 |             $info['txt'] .= "$key: $value ";
 | 
        
           |  |  | 9550 |         }
 | 
        
           |  |  | 9551 |     }
 | 
        
           |  |  | 9552 |   | 
        
           |  |  | 9553 |     $stringmanager = get_string_manager();
 | 
        
           |  |  | 9554 |     if (method_exists($stringmanager, 'get_performance_summary')) {
 | 
        
           |  |  | 9555 |         list($filterinfo, $nicenames) = $stringmanager->get_performance_summary();
 | 
        
           |  |  | 9556 |         $info = array_merge($filterinfo, $info);
 | 
        
           |  |  | 9557 |         foreach ($filterinfo as $key => $value) {
 | 
        
           |  |  | 9558 |             $info['html'] .= "<li class='$key col-sm-4'>$nicenames[$key]: $value </li> ";
 | 
        
           |  |  | 9559 |             $info['txt'] .= "$key: $value ";
 | 
        
           |  |  | 9560 |         }
 | 
        
           |  |  | 9561 |     }
 | 
        
           |  |  | 9562 |   | 
        
           | 1326 | ariadna | 9563 |     $info['dbqueries'] = $DB->perf_get_reads() . '/' . $DB->perf_get_writes();
 | 
        
           |  |  | 9564 |     $info['html'] .= '<li class="dbqueries col-sm-4">DB reads/writes: ' . $info['dbqueries'] . '</li> ';
 | 
        
           |  |  | 9565 |     $info['txt'] .= 'db reads/writes: ' . $info['dbqueries'] . ' ';
 | 
        
           | 1 | efrain | 9566 |   | 
        
           |  |  | 9567 |     if ($DB->want_read_slave()) {
 | 
        
           |  |  | 9568 |         $info['dbreads_slave'] = $DB->perf_get_reads_slave();
 | 
        
           | 1326 | ariadna | 9569 |         $info['html'] .= '<li class="dbqueries col-sm-4">DB reads from slave: ' . $info['dbreads_slave'] . '</li> ';
 | 
        
           |  |  | 9570 |         $info['txt'] .= 'db reads from slave: ' . $info['dbreads_slave'] . ' ';
 | 
        
           | 1 | efrain | 9571 |     }
 | 
        
           |  |  | 9572 |   | 
        
           |  |  | 9573 |     $info['dbtime'] = round($DB->perf_get_queries_time(), 5);
 | 
        
           | 1326 | ariadna | 9574 |     $info['html'] .= '<li class="dbtime col-sm-4">DB queries time: ' . $info['dbtime'] . ' secs</li> ';
 | 
        
           | 1 | efrain | 9575 |     $info['txt'] .= 'db queries time: ' . $info['dbtime'] . 's ';
 | 
        
           |  |  | 9576 |   | 
        
           |  |  | 9577 |     if (function_exists('posix_times')) {
 | 
        
           |  |  | 9578 |         $ptimes = posix_times();
 | 
        
           |  |  | 9579 |         if (is_array($ptimes)) {
 | 
        
           |  |  | 9580 |             foreach ($ptimes as $key => $val) {
 | 
        
           |  |  | 9581 |                 $info[$key] = $ptimes[$key] -  $PERF->startposixtimes[$key];
 | 
        
           |  |  | 9582 |             }
 | 
        
           |  |  | 9583 |             $info['html'] .= "<li class=\"posixtimes col-sm-4\">ticks: $info[ticks] user: $info[utime]";
 | 
        
           |  |  | 9584 |             $info['html'] .= "sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</li> ";
 | 
        
           |  |  | 9585 |             $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
 | 
        
           |  |  | 9586 |         }
 | 
        
           |  |  | 9587 |     }
 | 
        
           |  |  | 9588 |   | 
        
           |  |  | 9589 |     // Grab the load average for the last minute.
 | 
        
           |  |  | 9590 |     // /proc will only work under some linux configurations
 | 
        
           |  |  | 9591 |     // while uptime is there under MacOSX/Darwin and other unices.
 | 
        
           |  |  | 9592 |     if (is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
 | 
        
           |  |  | 9593 |         list($serverload) = explode(' ', $loadavg[0]);
 | 
        
           |  |  | 9594 |         unset($loadavg);
 | 
        
           | 1326 | ariadna | 9595 |     } else if (function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime`) {
 | 
        
           | 1 | efrain | 9596 |         if (preg_match('/load averages?: (\d+[\.,:]\d+)/', $loadavg, $matches)) {
 | 
        
           |  |  | 9597 |             $serverload = $matches[1];
 | 
        
           |  |  | 9598 |         } else {
 | 
        
           |  |  | 9599 |             trigger_error('Could not parse uptime output!');
 | 
        
           |  |  | 9600 |         }
 | 
        
           |  |  | 9601 |     }
 | 
        
           |  |  | 9602 |     if (!empty($serverload)) {
 | 
        
           |  |  | 9603 |         $info['serverload'] = $serverload;
 | 
        
           | 1326 | ariadna | 9604 |         $info['html'] .= '<li class="serverload col-sm-4">Load average: ' . $info['serverload'] . '</li> ';
 | 
        
           | 1 | efrain | 9605 |         $info['txt'] .= "serverload: {$info['serverload']} ";
 | 
        
           |  |  | 9606 |     }
 | 
        
           |  |  | 9607 |   | 
        
           |  |  | 9608 |     // Display size of session if session started.
 | 
        
           |  |  | 9609 |     if ($si = \core\session\manager::get_performance_info()) {
 | 
        
           |  |  | 9610 |         $info['sessionsize'] = $si['size'];
 | 
        
           |  |  | 9611 |         $info['html'] .= "<li class=\"serverload col-sm-4\">" . $si['html'] . "</li>";
 | 
        
           |  |  | 9612 |         $info['txt'] .= $si['txt'];
 | 
        
           |  |  | 9613 |     }
 | 
        
           |  |  | 9614 |   | 
        
           |  |  | 9615 |     // Display time waiting for session if applicable.
 | 
        
           |  |  | 9616 |     if (!empty($PERF->sessionlock['wait'])) {
 | 
        
           |  |  | 9617 |         $sessionwait = number_format($PERF->sessionlock['wait'], 3) . ' secs';
 | 
        
           |  |  | 9618 |         $info['html'] .= html_writer::tag('li', 'Session wait: ' . $sessionwait, [
 | 
        
           |  |  | 9619 |             'class' => 'sessionwait col-sm-4'
 | 
        
           |  |  | 9620 |         ]);
 | 
        
           |  |  | 9621 |         $info['txt'] .= 'sessionwait: ' . $sessionwait . ' ';
 | 
        
           |  |  | 9622 |     }
 | 
        
           |  |  | 9623 |   | 
        
           |  |  | 9624 |     $info['html'] .= '</ul>';
 | 
        
           |  |  | 9625 |     $html = '';
 | 
        
           |  |  | 9626 |     if ($stats = cache_helper::get_stats()) {
 | 
        
           |  |  | 9627 |   | 
        
           |  |  | 9628 |         $table = new html_table();
 | 
        
           |  |  | 9629 |         $table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
 | 
        
           |  |  | 9630 |         $table->head = ['Mode', 'Cache item', 'Static', 'H', 'M', get_string('mappingprimary', 'cache'), 'H', 'M', 'S', 'I/O'];
 | 
        
           |  |  | 9631 |         $table->data = [];
 | 
        
           |  |  | 9632 |         $table->align = ['left', 'left', 'left', 'right', 'right', 'left', 'right', 'right', 'right', 'right'];
 | 
        
           |  |  | 9633 |   | 
        
           |  |  | 9634 |         $text = 'Caches used (hits/misses/sets): ';
 | 
        
           |  |  | 9635 |         $hits = 0;
 | 
        
           |  |  | 9636 |         $misses = 0;
 | 
        
           |  |  | 9637 |         $sets = 0;
 | 
        
           |  |  | 9638 |         $maxstores = 0;
 | 
        
           |  |  | 9639 |   | 
        
           |  |  | 9640 |         // We want to align static caches into their own column.
 | 
        
           |  |  | 9641 |         $hasstatic = false;
 | 
        
           |  |  | 9642 |         foreach ($stats as $definition => $details) {
 | 
        
           |  |  | 9643 |             $numstores = count($details['stores']);
 | 
        
           |  |  | 9644 |             $first = key($details['stores']);
 | 
        
           |  |  | 9645 |             if ($first !== cache_store::STATIC_ACCEL) {
 | 
        
           |  |  | 9646 |                 $numstores++; // Add a blank space for the missing static store.
 | 
        
           |  |  | 9647 |             }
 | 
        
           |  |  | 9648 |             $maxstores = max($maxstores, $numstores);
 | 
        
           |  |  | 9649 |         }
 | 
        
           |  |  | 9650 |   | 
        
           |  |  | 9651 |         $storec = 0;
 | 
        
           |  |  | 9652 |   | 
        
           |  |  | 9653 |         while ($storec++ < ($maxstores - 2)) {
 | 
        
           |  |  | 9654 |             if ($storec == ($maxstores - 2)) {
 | 
        
           |  |  | 9655 |                 $table->head[] = get_string('mappingfinal', 'cache');
 | 
        
           |  |  | 9656 |             } else {
 | 
        
           |  |  | 9657 |                 $table->head[] = "Store $storec";
 | 
        
           |  |  | 9658 |             }
 | 
        
           |  |  | 9659 |             $table->align[] = 'left';
 | 
        
           |  |  | 9660 |             $table->align[] = 'right';
 | 
        
           |  |  | 9661 |             $table->align[] = 'right';
 | 
        
           |  |  | 9662 |             $table->align[] = 'right';
 | 
        
           |  |  | 9663 |             $table->align[] = 'right';
 | 
        
           |  |  | 9664 |             $table->head[] = 'H';
 | 
        
           |  |  | 9665 |             $table->head[] = 'M';
 | 
        
           |  |  | 9666 |             $table->head[] = 'S';
 | 
        
           |  |  | 9667 |             $table->head[] = 'I/O';
 | 
        
           |  |  | 9668 |         }
 | 
        
           |  |  | 9669 |   | 
        
           |  |  | 9670 |         ksort($stats);
 | 
        
           |  |  | 9671 |   | 
        
           |  |  | 9672 |         foreach ($stats as $definition => $details) {
 | 
        
           |  |  | 9673 |             switch ($details['mode']) {
 | 
        
           |  |  | 9674 |                 case cache_store::MODE_APPLICATION:
 | 
        
           |  |  | 9675 |                     $modeclass = 'application';
 | 
        
           |  |  | 9676 |                     $mode = ' <span title="application cache">App</span>';
 | 
        
           |  |  | 9677 |                     break;
 | 
        
           |  |  | 9678 |                 case cache_store::MODE_SESSION:
 | 
        
           |  |  | 9679 |                     $modeclass = 'session';
 | 
        
           |  |  | 9680 |                     $mode = ' <span title="session cache">Ses</span>';
 | 
        
           |  |  | 9681 |                     break;
 | 
        
           |  |  | 9682 |                 case cache_store::MODE_REQUEST:
 | 
        
           |  |  | 9683 |                     $modeclass = 'request';
 | 
        
           |  |  | 9684 |                     $mode = ' <span title="request cache">Req</span>';
 | 
        
           |  |  | 9685 |                     break;
 | 
        
           |  |  | 9686 |             }
 | 
        
           |  |  | 9687 |             $row = [$mode, $definition];
 | 
        
           |  |  | 9688 |   | 
        
           |  |  | 9689 |             $text .= "$definition {";
 | 
        
           |  |  | 9690 |   | 
        
           |  |  | 9691 |             $storec = 0;
 | 
        
           |  |  | 9692 |             foreach ($details['stores'] as $store => $data) {
 | 
        
           |  |  | 9693 |   | 
        
           |  |  | 9694 |                 if ($storec == 0 && $store !== cache_store::STATIC_ACCEL) {
 | 
        
           |  |  | 9695 |                     $row[] = '';
 | 
        
           |  |  | 9696 |                     $row[] = '';
 | 
        
           |  |  | 9697 |                     $row[] = '';
 | 
        
           |  |  | 9698 |                     $storec++;
 | 
        
           |  |  | 9699 |                 }
 | 
        
           |  |  | 9700 |   | 
        
           |  |  | 9701 |                 $hits   += $data['hits'];
 | 
        
           |  |  | 9702 |                 $misses += $data['misses'];
 | 
        
           |  |  | 9703 |                 $sets   += $data['sets'];
 | 
        
           |  |  | 9704 |                 if ($data['hits'] == 0 and $data['misses'] > 0) {
 | 
        
           |  |  | 9705 |                     $cachestoreclass = 'nohits bg-danger';
 | 
        
           |  |  | 9706 |                 } else if ($data['hits'] < $data['misses']) {
 | 
        
           |  |  | 9707 |                     $cachestoreclass = 'lowhits bg-warning text-dark';
 | 
        
           |  |  | 9708 |                 } else {
 | 
        
           |  |  | 9709 |                     $cachestoreclass = 'hihits';
 | 
        
           |  |  | 9710 |                 }
 | 
        
           |  |  | 9711 |                 $text .= "$store($data[hits]/$data[misses]/$data[sets]) ";
 | 
        
           |  |  | 9712 |                 $cell = new html_table_cell($store);
 | 
        
           |  |  | 9713 |                 $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9714 |                 $row[] = $cell;
 | 
        
           |  |  | 9715 |                 $cell = new html_table_cell($data['hits']);
 | 
        
           |  |  | 9716 |                 $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9717 |                 $row[] = $cell;
 | 
        
           |  |  | 9718 |                 $cell = new html_table_cell($data['misses']);
 | 
        
           |  |  | 9719 |                 $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9720 |                 $row[] = $cell;
 | 
        
           |  |  | 9721 |   | 
        
           |  |  | 9722 |                 if ($store !== cache_store::STATIC_ACCEL) {
 | 
        
           |  |  | 9723 |                     // The static cache is never set.
 | 
        
           |  |  | 9724 |                     $cell = new html_table_cell($data['sets']);
 | 
        
           |  |  | 9725 |                     $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9726 |                     $row[] = $cell;
 | 
        
           |  |  | 9727 |   | 
        
           |  |  | 9728 |                     if ($data['hits'] || $data['sets']) {
 | 
        
           |  |  | 9729 |                         if ($data['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
 | 
        
           |  |  | 9730 |                             $size = '-';
 | 
        
           |  |  | 9731 |                         } else {
 | 
        
           |  |  | 9732 |                             $size = display_size($data['iobytes'], 1, 'KB');
 | 
        
           |  |  | 9733 |                             if ($data['iobytes'] >= 10 * 1024) {
 | 
        
           |  |  | 9734 |                                 $cachestoreclass = ' bg-warning text-dark';
 | 
        
           |  |  | 9735 |                             }
 | 
        
           |  |  | 9736 |                         }
 | 
        
           |  |  | 9737 |                     } else {
 | 
        
           |  |  | 9738 |                         $size = '';
 | 
        
           |  |  | 9739 |                     }
 | 
        
           |  |  | 9740 |                     $cell = new html_table_cell($size);
 | 
        
           |  |  | 9741 |                     $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9742 |                     $row[] = $cell;
 | 
        
           |  |  | 9743 |                 }
 | 
        
           |  |  | 9744 |                 $storec++;
 | 
        
           |  |  | 9745 |             }
 | 
        
           |  |  | 9746 |             while ($storec++ < $maxstores) {
 | 
        
           |  |  | 9747 |                 $row[] = '';
 | 
        
           |  |  | 9748 |                 $row[] = '';
 | 
        
           |  |  | 9749 |                 $row[] = '';
 | 
        
           |  |  | 9750 |                 $row[] = '';
 | 
        
           |  |  | 9751 |                 $row[] = '';
 | 
        
           |  |  | 9752 |             }
 | 
        
           |  |  | 9753 |             $text .= '} ';
 | 
        
           |  |  | 9754 |   | 
        
           |  |  | 9755 |             $table->data[] = $row;
 | 
        
           |  |  | 9756 |         }
 | 
        
           |  |  | 9757 |   | 
        
           |  |  | 9758 |         $html .= html_writer::table($table);
 | 
        
           |  |  | 9759 |   | 
        
           |  |  | 9760 |         // Now lets also show sub totals for each cache store.
 | 
        
           |  |  | 9761 |         $storetotals = [];
 | 
        
           |  |  | 9762 |         $storetotal = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
 | 
        
           |  |  | 9763 |         foreach ($stats as $definition => $details) {
 | 
        
           |  |  | 9764 |             foreach ($details['stores'] as $store => $data) {
 | 
        
           |  |  | 9765 |                 if (!array_key_exists($store, $storetotals)) {
 | 
        
           |  |  | 9766 |                     $storetotals[$store] = ['hits' => 0, 'misses' => 0, 'sets' => 0, 'iobytes' => 0];
 | 
        
           |  |  | 9767 |                 }
 | 
        
           |  |  | 9768 |                 $storetotals[$store]['class']   = $data['class'];
 | 
        
           |  |  | 9769 |                 $storetotals[$store]['hits']   += $data['hits'];
 | 
        
           |  |  | 9770 |                 $storetotals[$store]['misses'] += $data['misses'];
 | 
        
           |  |  | 9771 |                 $storetotals[$store]['sets']   += $data['sets'];
 | 
        
           |  |  | 9772 |                 $storetotal['hits']   += $data['hits'];
 | 
        
           |  |  | 9773 |                 $storetotal['misses'] += $data['misses'];
 | 
        
           |  |  | 9774 |                 $storetotal['sets']   += $data['sets'];
 | 
        
           |  |  | 9775 |                 if ($data['iobytes'] !== cache_store::IO_BYTES_NOT_SUPPORTED) {
 | 
        
           |  |  | 9776 |                     $storetotals[$store]['iobytes'] += $data['iobytes'];
 | 
        
           |  |  | 9777 |                     $storetotal['iobytes'] += $data['iobytes'];
 | 
        
           |  |  | 9778 |                 }
 | 
        
           |  |  | 9779 |             }
 | 
        
           |  |  | 9780 |         }
 | 
        
           |  |  | 9781 |   | 
        
           |  |  | 9782 |         $table = new html_table();
 | 
        
           |  |  | 9783 |         $table->attributes['class'] = 'cachesused table table-dark table-sm w-auto table-bordered';
 | 
        
           |  |  | 9784 |         $table->head = [get_string('storename', 'cache'), get_string('type_cachestore', 'plugin'), 'H', 'M', 'S', 'I/O'];
 | 
        
           |  |  | 9785 |         $table->data = [];
 | 
        
           |  |  | 9786 |         $table->align = ['left', 'left', 'right', 'right', 'right', 'right'];
 | 
        
           |  |  | 9787 |   | 
        
           |  |  | 9788 |         ksort($storetotals);
 | 
        
           |  |  | 9789 |   | 
        
           |  |  | 9790 |         foreach ($storetotals as $store => $data) {
 | 
        
           |  |  | 9791 |             $row = [];
 | 
        
           |  |  | 9792 |             if ($data['hits'] == 0 and $data['misses'] > 0) {
 | 
        
           |  |  | 9793 |                 $cachestoreclass = 'nohits bg-danger';
 | 
        
           |  |  | 9794 |             } else if ($data['hits'] < $data['misses']) {
 | 
        
           |  |  | 9795 |                 $cachestoreclass = 'lowhits bg-warning text-dark';
 | 
        
           |  |  | 9796 |             } else {
 | 
        
           |  |  | 9797 |                 $cachestoreclass = 'hihits';
 | 
        
           |  |  | 9798 |             }
 | 
        
           |  |  | 9799 |             $cell = new html_table_cell($store);
 | 
        
           |  |  | 9800 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9801 |             $row[] = $cell;
 | 
        
           |  |  | 9802 |             $cell = new html_table_cell($data['class']);
 | 
        
           |  |  | 9803 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9804 |             $row[] = $cell;
 | 
        
           |  |  | 9805 |             $cell = new html_table_cell($data['hits']);
 | 
        
           |  |  | 9806 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9807 |             $row[] = $cell;
 | 
        
           |  |  | 9808 |             $cell = new html_table_cell($data['misses']);
 | 
        
           |  |  | 9809 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9810 |             $row[] = $cell;
 | 
        
           |  |  | 9811 |             $cell = new html_table_cell($data['sets']);
 | 
        
           |  |  | 9812 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9813 |             $row[] = $cell;
 | 
        
           |  |  | 9814 |             if ($data['hits'] || $data['sets']) {
 | 
        
           |  |  | 9815 |                 if ($data['iobytes']) {
 | 
        
           |  |  | 9816 |                     $size = display_size($data['iobytes'], 1, 'KB');
 | 
        
           |  |  | 9817 |                 } else {
 | 
        
           |  |  | 9818 |                     $size = '-';
 | 
        
           |  |  | 9819 |                 }
 | 
        
           |  |  | 9820 |             } else {
 | 
        
           |  |  | 9821 |                 $size = '';
 | 
        
           |  |  | 9822 |             }
 | 
        
           |  |  | 9823 |             $cell = new html_table_cell($size);
 | 
        
           |  |  | 9824 |             $cell->attributes = ['class' => $cachestoreclass];
 | 
        
           |  |  | 9825 |             $row[] = $cell;
 | 
        
           |  |  | 9826 |             $table->data[] = $row;
 | 
        
           |  |  | 9827 |         }
 | 
        
           |  |  | 9828 |         if (!empty($storetotal['iobytes'])) {
 | 
        
           |  |  | 9829 |             $size = display_size($storetotal['iobytes'], 1, 'KB');
 | 
        
           |  |  | 9830 |         } else if (!empty($storetotal['hits']) || !empty($storetotal['sets'])) {
 | 
        
           |  |  | 9831 |             $size = '-';
 | 
        
           |  |  | 9832 |         } else {
 | 
        
           |  |  | 9833 |             $size = '';
 | 
        
           |  |  | 9834 |         }
 | 
        
           |  |  | 9835 |         $row = [
 | 
        
           |  |  | 9836 |             get_string('total'),
 | 
        
           |  |  | 9837 |             '',
 | 
        
           |  |  | 9838 |             $storetotal['hits'],
 | 
        
           |  |  | 9839 |             $storetotal['misses'],
 | 
        
           |  |  | 9840 |             $storetotal['sets'],
 | 
        
           |  |  | 9841 |             $size,
 | 
        
           |  |  | 9842 |         ];
 | 
        
           |  |  | 9843 |         $table->data[] = $row;
 | 
        
           |  |  | 9844 |   | 
        
           |  |  | 9845 |         $html .= html_writer::table($table);
 | 
        
           |  |  | 9846 |   | 
        
           |  |  | 9847 |         $info['cachesused'] = "$hits / $misses / $sets";
 | 
        
           |  |  | 9848 |         $info['html'] .= $html;
 | 
        
           | 1326 | ariadna | 9849 |         $info['txt'] .= $text . '. ';
 | 
        
           | 1 | efrain | 9850 |     } else {
 | 
        
           |  |  | 9851 |         $info['cachesused'] = '0 / 0 / 0';
 | 
        
           |  |  | 9852 |         $info['html'] .= '<div class="cachesused">Caches used (hits/misses/sets): 0/0/0</div>';
 | 
        
           |  |  | 9853 |         $info['txt'] .= 'Caches used (hits/misses/sets): 0/0/0 ';
 | 
        
           |  |  | 9854 |     }
 | 
        
           |  |  | 9855 |   | 
        
           |  |  | 9856 |     // Display lock information if any.
 | 
        
           |  |  | 9857 |     if (!empty($PERF->locks)) {
 | 
        
           |  |  | 9858 |         $table = new html_table();
 | 
        
           |  |  | 9859 |         $table->attributes['class'] = 'locktimings table table-dark table-sm w-auto table-bordered';
 | 
        
           |  |  | 9860 |         $table->head = ['Lock', 'Waited (s)', 'Obtained', 'Held for (s)'];
 | 
        
           |  |  | 9861 |         $table->align = ['left', 'right', 'center', 'right'];
 | 
        
           |  |  | 9862 |         $table->data = [];
 | 
        
           |  |  | 9863 |         $text = 'Locks (waited/obtained/held):';
 | 
        
           |  |  | 9864 |         foreach ($PERF->locks as $locktiming) {
 | 
        
           |  |  | 9865 |             $row = [];
 | 
        
           |  |  | 9866 |             $row[] = s($locktiming->type . '/' . $locktiming->resource);
 | 
        
           |  |  | 9867 |             $text .= ' ' . $locktiming->type . '/' . $locktiming->resource . ' (';
 | 
        
           |  |  | 9868 |   | 
        
           |  |  | 9869 |             // The time we had to wait to get the lock.
 | 
        
           |  |  | 9870 |             $roundedtime = number_format($locktiming->wait, 1);
 | 
        
           |  |  | 9871 |             $cell = new html_table_cell($roundedtime);
 | 
        
           |  |  | 9872 |             if ($locktiming->wait > 0.5) {
 | 
        
           |  |  | 9873 |                 $cell->attributes = ['class' => 'bg-warning text-dark'];
 | 
        
           |  |  | 9874 |             }
 | 
        
           |  |  | 9875 |             $row[] = $cell;
 | 
        
           |  |  | 9876 |             $text .= $roundedtime . '/';
 | 
        
           |  |  | 9877 |   | 
        
           |  |  | 9878 |             // Show a tick or cross for success.
 | 
        
           |  |  | 9879 |             $row[] = $locktiming->success ? '✓' : '❌';
 | 
        
           |  |  | 9880 |             $text .= ($locktiming->success ? 'y' : 'n') . '/';
 | 
        
           |  |  | 9881 |   | 
        
           |  |  | 9882 |             // If applicable, show how long we held the lock before releasing it.
 | 
        
           |  |  | 9883 |             if (property_exists($locktiming, 'held')) {
 | 
        
           |  |  | 9884 |                 $roundedtime = number_format($locktiming->held, 1);
 | 
        
           |  |  | 9885 |                 $cell = new html_table_cell($roundedtime);
 | 
        
           |  |  | 9886 |                 if ($locktiming->held > 0.5) {
 | 
        
           |  |  | 9887 |                     $cell->attributes = ['class' => 'bg-warning text-dark'];
 | 
        
           |  |  | 9888 |                 }
 | 
        
           |  |  | 9889 |                 $row[] = $cell;
 | 
        
           |  |  | 9890 |                 $text .= $roundedtime;
 | 
        
           |  |  | 9891 |             } else {
 | 
        
           |  |  | 9892 |                 $row[] = '-';
 | 
        
           |  |  | 9893 |                 $text .= '-';
 | 
        
           |  |  | 9894 |             }
 | 
        
           |  |  | 9895 |             $text .= ')';
 | 
        
           |  |  | 9896 |   | 
        
           |  |  | 9897 |             $table->data[] = $row;
 | 
        
           |  |  | 9898 |         }
 | 
        
           |  |  | 9899 |         $info['html'] .= html_writer::table($table);
 | 
        
           |  |  | 9900 |         $info['txt'] .= $text . '. ';
 | 
        
           |  |  | 9901 |     }
 | 
        
           |  |  | 9902 |   | 
        
           | 1326 | ariadna | 9903 |     $info['html'] = '<div class="performanceinfo siteinfo container-fluid px-md-0 overflow-auto pt-3">' . $info['html'] . '</div>';
 | 
        
           | 1 | efrain | 9904 |     return $info;
 | 
        
           |  |  | 9905 | }
 | 
        
           |  |  | 9906 |   | 
        
           |  |  | 9907 | /**
 | 
        
           |  |  | 9908 |  * Renames a file or directory to a unique name within the same directory.
 | 
        
           |  |  | 9909 |  *
 | 
        
           |  |  | 9910 |  * This function is designed to avoid any potential race conditions, and select an unused name.
 | 
        
           |  |  | 9911 |  *
 | 
        
           |  |  | 9912 |  * @param string $filepath Original filepath
 | 
        
           |  |  | 9913 |  * @param string $prefix Prefix to use for the temporary name
 | 
        
           |  |  | 9914 |  * @return string|bool New file path or false if failed
 | 
        
           |  |  | 9915 |  * @since Moodle 3.10
 | 
        
           |  |  | 9916 |  */
 | 
        
           | 1326 | ariadna | 9917 | function rename_to_unused_name(string $filepath, string $prefix = '_temp_')
 | 
        
           |  |  | 9918 | {
 | 
        
           | 1 | efrain | 9919 |     $dir = dirname($filepath);
 | 
        
           |  |  | 9920 |     $basename = $dir . '/' . $prefix;
 | 
        
           |  |  | 9921 |     $limit = 0;
 | 
        
           |  |  | 9922 |     while ($limit < 100) {
 | 
        
           |  |  | 9923 |         // Select a new name based on a random number.
 | 
        
           |  |  | 9924 |         $newfilepath = $basename . md5(mt_rand());
 | 
        
           |  |  | 9925 |   | 
        
           |  |  | 9926 |         // Attempt a rename to that new name.
 | 
        
           |  |  | 9927 |         if (@rename($filepath, $newfilepath)) {
 | 
        
           |  |  | 9928 |             return $newfilepath;
 | 
        
           |  |  | 9929 |         }
 | 
        
           |  |  | 9930 |   | 
        
           |  |  | 9931 |         // The first time, do some sanity checks, maybe it is failing for a good reason and there
 | 
        
           |  |  | 9932 |         // is no point trying 100 times if so.
 | 
        
           |  |  | 9933 |         if ($limit === 0 && (!file_exists($filepath) || !is_writable($dir))) {
 | 
        
           |  |  | 9934 |             return false;
 | 
        
           |  |  | 9935 |         }
 | 
        
           |  |  | 9936 |         $limit++;
 | 
        
           |  |  | 9937 |     }
 | 
        
           |  |  | 9938 |     return false;
 | 
        
           |  |  | 9939 | }
 | 
        
           |  |  | 9940 |   | 
        
           |  |  | 9941 | /**
 | 
        
           |  |  | 9942 |  * Delete directory or only its content
 | 
        
           |  |  | 9943 |  *
 | 
        
           |  |  | 9944 |  * @param string $dir directory path
 | 
        
           |  |  | 9945 |  * @param bool $contentonly
 | 
        
           |  |  | 9946 |  * @return bool success, true also if dir does not exist
 | 
        
           |  |  | 9947 |  */
 | 
        
           | 1326 | ariadna | 9948 | function remove_dir($dir, $contentonly = false)
 | 
        
           |  |  | 9949 | {
 | 
        
           | 1 | efrain | 9950 |     if (!is_dir($dir)) {
 | 
        
           |  |  | 9951 |         // Nothing to do.
 | 
        
           |  |  | 9952 |         return true;
 | 
        
           |  |  | 9953 |     }
 | 
        
           |  |  | 9954 |   | 
        
           |  |  | 9955 |     if (!$contentonly) {
 | 
        
           |  |  | 9956 |         // Start by renaming the directory; this will guarantee that other processes don't write to it
 | 
        
           |  |  | 9957 |         // while it is in the process of being deleted.
 | 
        
           |  |  | 9958 |         $tempdir = rename_to_unused_name($dir);
 | 
        
           |  |  | 9959 |         if ($tempdir) {
 | 
        
           |  |  | 9960 |             // If the rename was successful then delete the $tempdir instead.
 | 
        
           |  |  | 9961 |             $dir = $tempdir;
 | 
        
           |  |  | 9962 |         }
 | 
        
           |  |  | 9963 |         // If the rename fails, we will continue through and attempt to delete the directory
 | 
        
           |  |  | 9964 |         // without renaming it since that is likely to at least delete most of the files.
 | 
        
           |  |  | 9965 |     }
 | 
        
           |  |  | 9966 |   | 
        
           |  |  | 9967 |     if (!$handle = opendir($dir)) {
 | 
        
           |  |  | 9968 |         return false;
 | 
        
           |  |  | 9969 |     }
 | 
        
           |  |  | 9970 |     $result = true;
 | 
        
           | 1326 | ariadna | 9971 |     while (false !== ($item = readdir($handle))) {
 | 
        
           | 1 | efrain | 9972 |         if ($item != '.' && $item != '..') {
 | 
        
           | 1326 | ariadna | 9973 |             if (is_dir($dir . '/' . $item)) {
 | 
        
           |  |  | 9974 |                 $result = remove_dir($dir . '/' . $item) && $result;
 | 
        
           | 1 | efrain | 9975 |             } else {
 | 
        
           | 1326 | ariadna | 9976 |                 $result = unlink($dir . '/' . $item) && $result;
 | 
        
           | 1 | efrain | 9977 |             }
 | 
        
           |  |  | 9978 |         }
 | 
        
           |  |  | 9979 |     }
 | 
        
           |  |  | 9980 |     closedir($handle);
 | 
        
           |  |  | 9981 |     if ($contentonly) {
 | 
        
           |  |  | 9982 |         clearstatcache(); // Make sure file stat cache is properly invalidated.
 | 
        
           |  |  | 9983 |         return $result;
 | 
        
           |  |  | 9984 |     }
 | 
        
           |  |  | 9985 |     $result = rmdir($dir); // If anything left the result will be false, no need for && $result.
 | 
        
           |  |  | 9986 |     clearstatcache(); // Make sure file stat cache is properly invalidated.
 | 
        
           |  |  | 9987 |     return $result;
 | 
        
           |  |  | 9988 | }
 | 
        
           |  |  | 9989 |   | 
        
           |  |  | 9990 | /**
 | 
        
           |  |  | 9991 |  * Detect if an object or a class contains a given property
 | 
        
           |  |  | 9992 |  * will take an actual object or the name of a class
 | 
        
           |  |  | 9993 |  *
 | 
        
           |  |  | 9994 |  * @param mixed $obj Name of class or real object to test
 | 
        
           |  |  | 9995 |  * @param string $property name of property to find
 | 
        
           |  |  | 9996 |  * @return bool true if property exists
 | 
        
           |  |  | 9997 |  */
 | 
        
           | 1326 | ariadna | 9998 | function object_property_exists($obj, $property)
 | 
        
           |  |  | 9999 | {
 | 
        
           |  |  | 10000 |     if (is_string($obj)) {
 | 
        
           |  |  | 10001 |         $properties = get_class_vars($obj);
 | 
        
           | 1 | efrain | 10002 |     } else {
 | 
        
           | 1326 | ariadna | 10003 |         $properties = get_object_vars($obj);
 | 
        
           | 1 | efrain | 10004 |     }
 | 
        
           | 1326 | ariadna | 10005 |     return array_key_exists($property, $properties);
 | 
        
           | 1 | efrain | 10006 | }
 | 
        
           |  |  | 10007 |   | 
        
           |  |  | 10008 | /**
 | 
        
           |  |  | 10009 |  * Converts an object into an associative array
 | 
        
           |  |  | 10010 |  *
 | 
        
           |  |  | 10011 |  * This function converts an object into an associative array by iterating
 | 
        
           |  |  | 10012 |  * over its public properties. Because this function uses the foreach
 | 
        
           |  |  | 10013 |  * construct, Iterators are respected. It works recursively on arrays of objects.
 | 
        
           |  |  | 10014 |  * Arrays and simple values are returned as is.
 | 
        
           |  |  | 10015 |  *
 | 
        
           |  |  | 10016 |  * If class has magic properties, it can implement IteratorAggregate
 | 
        
           |  |  | 10017 |  * and return all available properties in getIterator()
 | 
        
           |  |  | 10018 |  *
 | 
        
           |  |  | 10019 |  * @param mixed $var
 | 
        
           |  |  | 10020 |  * @return array
 | 
        
           |  |  | 10021 |  */
 | 
        
           | 1326 | ariadna | 10022 | function convert_to_array($var)
 | 
        
           |  |  | 10023 | {
 | 
        
           | 1 | efrain | 10024 |     $result = array();
 | 
        
           |  |  | 10025 |   | 
        
           |  |  | 10026 |     // Loop over elements/properties.
 | 
        
           |  |  | 10027 |     foreach ($var as $key => $value) {
 | 
        
           |  |  | 10028 |         // Recursively convert objects.
 | 
        
           |  |  | 10029 |         if (is_object($value) || is_array($value)) {
 | 
        
           |  |  | 10030 |             $result[$key] = convert_to_array($value);
 | 
        
           |  |  | 10031 |         } else {
 | 
        
           |  |  | 10032 |             // Simple values are untouched.
 | 
        
           |  |  | 10033 |             $result[$key] = $value;
 | 
        
           |  |  | 10034 |         }
 | 
        
           |  |  | 10035 |     }
 | 
        
           |  |  | 10036 |     return $result;
 | 
        
           |  |  | 10037 | }
 | 
        
           |  |  | 10038 |   | 
        
           |  |  | 10039 | /**
 | 
        
           |  |  | 10040 |  * Detect a custom script replacement in the data directory that will
 | 
        
           |  |  | 10041 |  * replace an existing moodle script
 | 
        
           |  |  | 10042 |  *
 | 
        
           |  |  | 10043 |  * @return string|bool full path name if a custom script exists, false if no custom script exists
 | 
        
           |  |  | 10044 |  */
 | 
        
           | 1326 | ariadna | 10045 | function custom_script_path()
 | 
        
           |  |  | 10046 | {
 | 
        
           | 1 | efrain | 10047 |     global $CFG, $SCRIPT;
 | 
        
           |  |  | 10048 |   | 
        
           |  |  | 10049 |     if ($SCRIPT === null) {
 | 
        
           |  |  | 10050 |         // Probably some weird external script.
 | 
        
           |  |  | 10051 |         return false;
 | 
        
           |  |  | 10052 |     }
 | 
        
           |  |  | 10053 |   | 
        
           |  |  | 10054 |     $scriptpath = $CFG->customscripts . $SCRIPT;
 | 
        
           |  |  | 10055 |   | 
        
           |  |  | 10056 |     // Check the custom script exists.
 | 
        
           |  |  | 10057 |     if (file_exists($scriptpath) and is_file($scriptpath)) {
 | 
        
           |  |  | 10058 |         return $scriptpath;
 | 
        
           |  |  | 10059 |     } else {
 | 
        
           |  |  | 10060 |         return false;
 | 
        
           |  |  | 10061 |     }
 | 
        
           |  |  | 10062 | }
 | 
        
           |  |  | 10063 |   | 
        
           |  |  | 10064 | /**
 | 
        
           |  |  | 10065 |  * Returns whether or not the user object is a remote MNET user. This function
 | 
        
           |  |  | 10066 |  * is in moodlelib because it does not rely on loading any of the MNET code.
 | 
        
           |  |  | 10067 |  *
 | 
        
           |  |  | 10068 |  * @param object $user A valid user object
 | 
        
           |  |  | 10069 |  * @return bool        True if the user is from a remote Moodle.
 | 
        
           |  |  | 10070 |  */
 | 
        
           | 1326 | ariadna | 10071 | function is_mnet_remote_user($user)
 | 
        
           |  |  | 10072 | {
 | 
        
           | 1 | efrain | 10073 |     global $CFG;
 | 
        
           |  |  | 10074 |   | 
        
           |  |  | 10075 |     if (!isset($CFG->mnet_localhost_id)) {
 | 
        
           |  |  | 10076 |         include_once($CFG->dirroot . '/mnet/lib.php');
 | 
        
           |  |  | 10077 |         $env = new mnet_environment();
 | 
        
           |  |  | 10078 |         $env->init();
 | 
        
           |  |  | 10079 |         unset($env);
 | 
        
           |  |  | 10080 |     }
 | 
        
           |  |  | 10081 |   | 
        
           |  |  | 10082 |     return (!empty($user->mnethostid) && $user->mnethostid != $CFG->mnet_localhost_id);
 | 
        
           |  |  | 10083 | }
 | 
        
           |  |  | 10084 |   | 
        
           |  |  | 10085 | /**
 | 
        
           |  |  | 10086 |  * This function will search for browser prefereed languages, setting Moodle
 | 
        
           |  |  | 10087 |  * to use the best one available if $SESSION->lang is undefined
 | 
        
           |  |  | 10088 |  */
 | 
        
           | 1326 | ariadna | 10089 | function setup_lang_from_browser()
 | 
        
           |  |  | 10090 | {
 | 
        
           | 1 | efrain | 10091 |     global $CFG, $SESSION, $USER;
 | 
        
           |  |  | 10092 |   | 
        
           |  |  | 10093 |     if (!empty($SESSION->lang) or !empty($USER->lang) or empty($CFG->autolang)) {
 | 
        
           |  |  | 10094 |         // Lang is defined in session or user profile, nothing to do.
 | 
        
           |  |  | 10095 |         return;
 | 
        
           |  |  | 10096 |     }
 | 
        
           |  |  | 10097 |   | 
        
           |  |  | 10098 |     if (!isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { // There isn't list of browser langs, nothing to do.
 | 
        
           |  |  | 10099 |         return;
 | 
        
           |  |  | 10100 |     }
 | 
        
           |  |  | 10101 |   | 
        
           |  |  | 10102 |     // Extract and clean langs from headers.
 | 
        
           |  |  | 10103 |     $rawlangs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
 | 
        
           |  |  | 10104 |     $rawlangs = str_replace('-', '_', $rawlangs);         // We are using underscores.
 | 
        
           |  |  | 10105 |     $rawlangs = explode(',', $rawlangs);                  // Convert to array.
 | 
        
           |  |  | 10106 |     $langs = array();
 | 
        
           |  |  | 10107 |   | 
        
           |  |  | 10108 |     $order = 1.0;
 | 
        
           |  |  | 10109 |     foreach ($rawlangs as $lang) {
 | 
        
           |  |  | 10110 |         if (strpos($lang, ';') === false) {
 | 
        
           |  |  | 10111 |             $langs[(string)$order] = $lang;
 | 
        
           | 1326 | ariadna | 10112 |             $order = $order - 0.01;
 | 
        
           | 1 | efrain | 10113 |         } else {
 | 
        
           |  |  | 10114 |             $parts = explode(';', $lang);
 | 
        
           |  |  | 10115 |             $pos = strpos($parts[1], '=');
 | 
        
           | 1326 | ariadna | 10116 |             $langs[substr($parts[1], $pos + 1)] = $parts[0];
 | 
        
           | 1 | efrain | 10117 |         }
 | 
        
           |  |  | 10118 |     }
 | 
        
           |  |  | 10119 |     krsort($langs, SORT_NUMERIC);
 | 
        
           |  |  | 10120 |   | 
        
           |  |  | 10121 |     // Look for such langs under standard locations.
 | 
        
           |  |  | 10122 |     foreach ($langs as $lang) {
 | 
        
           |  |  | 10123 |         // Clean it properly for include.
 | 
        
           |  |  | 10124 |         $lang = strtolower(clean_param($lang, PARAM_SAFEDIR));
 | 
        
           |  |  | 10125 |         if (get_string_manager()->translation_exists($lang, false)) {
 | 
        
           |  |  | 10126 |             // If the translation for this language exists then try to set it
 | 
        
           |  |  | 10127 |             // for the rest of the session, if this is a read only session then
 | 
        
           |  |  | 10128 |             // we can only set it temporarily in $CFG.
 | 
        
           |  |  | 10129 |             if (defined('READ_ONLY_SESSION') && !empty($CFG->enable_read_only_sessions)) {
 | 
        
           |  |  | 10130 |                 $CFG->lang = $lang;
 | 
        
           |  |  | 10131 |             } else {
 | 
        
           |  |  | 10132 |                 $SESSION->lang = $lang;
 | 
        
           |  |  | 10133 |             }
 | 
        
           |  |  | 10134 |             // We have finished. Go out.
 | 
        
           |  |  | 10135 |             break;
 | 
        
           |  |  | 10136 |         }
 | 
        
           |  |  | 10137 |     }
 | 
        
           |  |  | 10138 |     return;
 | 
        
           |  |  | 10139 | }
 | 
        
           |  |  | 10140 |   | 
        
           |  |  | 10141 | /**
 | 
        
           |  |  | 10142 |  * Check if $url matches anything in proxybypass list
 | 
        
           |  |  | 10143 |  *
 | 
        
           |  |  | 10144 |  * Any errors just result in the proxy being used (least bad)
 | 
        
           |  |  | 10145 |  *
 | 
        
           |  |  | 10146 |  * @param string $url url to check
 | 
        
           |  |  | 10147 |  * @return boolean true if we should bypass the proxy
 | 
        
           |  |  | 10148 |  */
 | 
        
           | 1326 | ariadna | 10149 | function is_proxybypass($url)
 | 
        
           |  |  | 10150 | {
 | 
        
           | 1 | efrain | 10151 |     global $CFG;
 | 
        
           |  |  | 10152 |   | 
        
           |  |  | 10153 |     // Sanity check.
 | 
        
           |  |  | 10154 |     if (empty($CFG->proxyhost) or empty($CFG->proxybypass)) {
 | 
        
           |  |  | 10155 |         return false;
 | 
        
           |  |  | 10156 |     }
 | 
        
           |  |  | 10157 |   | 
        
           |  |  | 10158 |     // Get the host part out of the url.
 | 
        
           | 1326 | ariadna | 10159 |     if (!$host = parse_url($url, PHP_URL_HOST)) {
 | 
        
           | 1 | efrain | 10160 |         return false;
 | 
        
           |  |  | 10161 |     }
 | 
        
           |  |  | 10162 |   | 
        
           |  |  | 10163 |     // Get the possible bypass hosts into an array.
 | 
        
           | 1326 | ariadna | 10164 |     $matches = explode(',', $CFG->proxybypass);
 | 
        
           | 1 | efrain | 10165 |   | 
        
           |  |  | 10166 |     // Check for a exact match on the IP or in the domains.
 | 
        
           |  |  | 10167 |     $isdomaininallowedlist = \core\ip_utils::is_domain_in_allowed_list($host, $matches);
 | 
        
           |  |  | 10168 |     $isipinsubnetlist = \core\ip_utils::is_ip_in_subnet_list($host, $CFG->proxybypass, ',');
 | 
        
           |  |  | 10169 |   | 
        
           |  |  | 10170 |     if ($isdomaininallowedlist || $isipinsubnetlist) {
 | 
        
           |  |  | 10171 |         return true;
 | 
        
           |  |  | 10172 |     }
 | 
        
           |  |  | 10173 |   | 
        
           |  |  | 10174 |     // Nothing matched.
 | 
        
           |  |  | 10175 |     return false;
 | 
        
           |  |  | 10176 | }
 | 
        
           |  |  | 10177 |   | 
        
           |  |  | 10178 | /**
 | 
        
           |  |  | 10179 |  * Check if the passed navigation is of the new style
 | 
        
           |  |  | 10180 |  *
 | 
        
           |  |  | 10181 |  * @param mixed $navigation
 | 
        
           |  |  | 10182 |  * @return bool true for yes false for no
 | 
        
           |  |  | 10183 |  */
 | 
        
           | 1326 | ariadna | 10184 | function is_newnav($navigation)
 | 
        
           |  |  | 10185 | {
 | 
        
           | 1 | efrain | 10186 |     if (is_array($navigation) && !empty($navigation['newnav'])) {
 | 
        
           |  |  | 10187 |         return true;
 | 
        
           |  |  | 10188 |     } else {
 | 
        
           |  |  | 10189 |         return false;
 | 
        
           |  |  | 10190 |     }
 | 
        
           |  |  | 10191 | }
 | 
        
           |  |  | 10192 |   | 
        
           |  |  | 10193 | /**
 | 
        
           |  |  | 10194 |  * Checks whether the given variable name is defined as a variable within the given object.
 | 
        
           |  |  | 10195 |  *
 | 
        
           |  |  | 10196 |  * This will NOT work with stdClass objects, which have no class variables.
 | 
        
           |  |  | 10197 |  *
 | 
        
           |  |  | 10198 |  * @param string $var The variable name
 | 
        
           |  |  | 10199 |  * @param object $object The object to check
 | 
        
           |  |  | 10200 |  * @return boolean
 | 
        
           |  |  | 10201 |  */
 | 
        
           | 1326 | ariadna | 10202 | function in_object_vars($var, $object)
 | 
        
           |  |  | 10203 | {
 | 
        
           | 1 | efrain | 10204 |     $classvars = get_class_vars(get_class($object));
 | 
        
           |  |  | 10205 |     $classvars = array_keys($classvars);
 | 
        
           |  |  | 10206 |     return in_array($var, $classvars);
 | 
        
           |  |  | 10207 | }
 | 
        
           |  |  | 10208 |   | 
        
           |  |  | 10209 | /**
 | 
        
           |  |  | 10210 |  * Returns an array without repeated objects.
 | 
        
           |  |  | 10211 |  * This function is similar to array_unique, but for arrays that have objects as values
 | 
        
           |  |  | 10212 |  *
 | 
        
           |  |  | 10213 |  * @param array $array
 | 
        
           |  |  | 10214 |  * @param bool $keepkeyassoc
 | 
        
           |  |  | 10215 |  * @return array
 | 
        
           |  |  | 10216 |  */
 | 
        
           | 1326 | ariadna | 10217 | function object_array_unique($array, $keepkeyassoc = true)
 | 
        
           |  |  | 10218 | {
 | 
        
           | 1 | efrain | 10219 |     $duplicatekeys = array();
 | 
        
           |  |  | 10220 |     $tmp         = array();
 | 
        
           |  |  | 10221 |   | 
        
           |  |  | 10222 |     foreach ($array as $key => $val) {
 | 
        
           |  |  | 10223 |         // Convert objects to arrays, in_array() does not support objects.
 | 
        
           |  |  | 10224 |         if (is_object($val)) {
 | 
        
           |  |  | 10225 |             $val = (array)$val;
 | 
        
           |  |  | 10226 |         }
 | 
        
           |  |  | 10227 |   | 
        
           |  |  | 10228 |         if (!in_array($val, $tmp)) {
 | 
        
           |  |  | 10229 |             $tmp[] = $val;
 | 
        
           |  |  | 10230 |         } else {
 | 
        
           |  |  | 10231 |             $duplicatekeys[] = $key;
 | 
        
           |  |  | 10232 |         }
 | 
        
           |  |  | 10233 |     }
 | 
        
           |  |  | 10234 |   | 
        
           |  |  | 10235 |     foreach ($duplicatekeys as $key) {
 | 
        
           |  |  | 10236 |         unset($array[$key]);
 | 
        
           |  |  | 10237 |     }
 | 
        
           |  |  | 10238 |   | 
        
           |  |  | 10239 |     return $keepkeyassoc ? $array : array_values($array);
 | 
        
           |  |  | 10240 | }
 | 
        
           |  |  | 10241 |   | 
        
           |  |  | 10242 | /**
 | 
        
           |  |  | 10243 |  * Is a userid the primary administrator?
 | 
        
           |  |  | 10244 |  *
 | 
        
           |  |  | 10245 |  * @param int $userid int id of user to check
 | 
        
           |  |  | 10246 |  * @return boolean
 | 
        
           |  |  | 10247 |  */
 | 
        
           | 1326 | ariadna | 10248 | function is_primary_admin($userid)
 | 
        
           |  |  | 10249 | {
 | 
        
           | 1 | efrain | 10250 |     $primaryadmin =  get_admin();
 | 
        
           |  |  | 10251 |   | 
        
           |  |  | 10252 |     if ($userid == $primaryadmin->id) {
 | 
        
           |  |  | 10253 |         return true;
 | 
        
           |  |  | 10254 |     } else {
 | 
        
           |  |  | 10255 |         return false;
 | 
        
           |  |  | 10256 |     }
 | 
        
           |  |  | 10257 | }
 | 
        
           |  |  | 10258 |   | 
        
           |  |  | 10259 | /**
 | 
        
           |  |  | 10260 |  * Returns the site identifier
 | 
        
           |  |  | 10261 |  *
 | 
        
           |  |  | 10262 |  * @return string $CFG->siteidentifier, first making sure it is properly initialised.
 | 
        
           |  |  | 10263 |  */
 | 
        
           | 1326 | ariadna | 10264 | function get_site_identifier()
 | 
        
           |  |  | 10265 | {
 | 
        
           | 1 | efrain | 10266 |     global $CFG;
 | 
        
           |  |  | 10267 |     // Check to see if it is missing. If so, initialise it.
 | 
        
           |  |  | 10268 |     if (empty($CFG->siteidentifier)) {
 | 
        
           |  |  | 10269 |         set_config('siteidentifier', random_string(32) . $_SERVER['HTTP_HOST']);
 | 
        
           |  |  | 10270 |     }
 | 
        
           |  |  | 10271 |     // Return it.
 | 
        
           |  |  | 10272 |     return $CFG->siteidentifier;
 | 
        
           |  |  | 10273 | }
 | 
        
           |  |  | 10274 |   | 
        
           |  |  | 10275 | /**
 | 
        
           |  |  | 10276 |  * Check whether the given password has no more than the specified
 | 
        
           |  |  | 10277 |  * number of consecutive identical characters.
 | 
        
           |  |  | 10278 |  *
 | 
        
           |  |  | 10279 |  * @param string $password   password to be checked against the password policy
 | 
        
           |  |  | 10280 |  * @param integer $maxchars  maximum number of consecutive identical characters
 | 
        
           |  |  | 10281 |  * @return bool
 | 
        
           |  |  | 10282 |  */
 | 
        
           | 1326 | ariadna | 10283 | function check_consecutive_identical_characters($password, $maxchars)
 | 
        
           |  |  | 10284 | {
 | 
        
           | 1 | efrain | 10285 |   | 
        
           |  |  | 10286 |     if ($maxchars < 1) {
 | 
        
           |  |  | 10287 |         return true; // Zero 0 is to disable this check.
 | 
        
           |  |  | 10288 |     }
 | 
        
           |  |  | 10289 |     if (strlen($password) <= $maxchars) {
 | 
        
           |  |  | 10290 |         return true; // Too short to fail this test.
 | 
        
           |  |  | 10291 |     }
 | 
        
           |  |  | 10292 |   | 
        
           |  |  | 10293 |     $previouschar = '';
 | 
        
           |  |  | 10294 |     $consecutivecount = 1;
 | 
        
           |  |  | 10295 |     foreach (str_split($password) as $char) {
 | 
        
           |  |  | 10296 |         if ($char != $previouschar) {
 | 
        
           |  |  | 10297 |             $consecutivecount = 1;
 | 
        
           |  |  | 10298 |         } else {
 | 
        
           |  |  | 10299 |             $consecutivecount++;
 | 
        
           |  |  | 10300 |             if ($consecutivecount > $maxchars) {
 | 
        
           |  |  | 10301 |                 return false; // Check failed already.
 | 
        
           |  |  | 10302 |             }
 | 
        
           |  |  | 10303 |         }
 | 
        
           |  |  | 10304 |   | 
        
           |  |  | 10305 |         $previouschar = $char;
 | 
        
           |  |  | 10306 |     }
 | 
        
           |  |  | 10307 |   | 
        
           |  |  | 10308 |     return true;
 | 
        
           |  |  | 10309 | }
 | 
        
           |  |  | 10310 |   | 
        
           |  |  | 10311 | /**
 | 
        
           |  |  | 10312 |  * Helper function to do partial function binding.
 | 
        
           |  |  | 10313 |  * so we can use it for preg_replace_callback, for example
 | 
        
           |  |  | 10314 |  * this works with php functions, user functions, static methods and class methods
 | 
        
           |  |  | 10315 |  * it returns you a callback that you can pass on like so:
 | 
        
           |  |  | 10316 |  *
 | 
        
           |  |  | 10317 |  * $callback = partial('somefunction', $arg1, $arg2);
 | 
        
           |  |  | 10318 |  *     or
 | 
        
           |  |  | 10319 |  * $callback = partial(array('someclass', 'somestaticmethod'), $arg1, $arg2);
 | 
        
           |  |  | 10320 |  *     or even
 | 
        
           |  |  | 10321 |  * $obj = new someclass();
 | 
        
           |  |  | 10322 |  * $callback = partial(array($obj, 'somemethod'), $arg1, $arg2);
 | 
        
           |  |  | 10323 |  *
 | 
        
           |  |  | 10324 |  * and then the arguments that are passed through at calltime are appended to the argument list.
 | 
        
           |  |  | 10325 |  *
 | 
        
           |  |  | 10326 |  * @param mixed $function a php callback
 | 
        
           |  |  | 10327 |  * @param mixed $arg1,... $argv arguments to partially bind with
 | 
        
           |  |  | 10328 |  * @return array Array callback
 | 
        
           |  |  | 10329 |  */
 | 
        
           | 1326 | ariadna | 10330 | function partial()
 | 
        
           |  |  | 10331 | {
 | 
        
           | 1 | efrain | 10332 |     if (!class_exists('partial')) {
 | 
        
           |  |  | 10333 |         /**
 | 
        
           |  |  | 10334 |          * Used to manage function binding.
 | 
        
           |  |  | 10335 |          * @copyright  2009 Penny Leach
 | 
        
           |  |  | 10336 |          * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 10337 |          */
 | 
        
           | 1326 | ariadna | 10338 |         class partial
 | 
        
           |  |  | 10339 |         {
 | 
        
           | 1 | efrain | 10340 |             /** @var array */
 | 
        
           |  |  | 10341 |             public $values = array();
 | 
        
           |  |  | 10342 |             /** @var string The function to call as a callback. */
 | 
        
           |  |  | 10343 |             public $func;
 | 
        
           |  |  | 10344 |             /**
 | 
        
           |  |  | 10345 |              * Constructor
 | 
        
           |  |  | 10346 |              * @param string $func
 | 
        
           |  |  | 10347 |              * @param array $args
 | 
        
           |  |  | 10348 |              */
 | 
        
           | 1326 | ariadna | 10349 |             public function __construct($func, $args)
 | 
        
           |  |  | 10350 |             {
 | 
        
           | 1 | efrain | 10351 |                 $this->values = $args;
 | 
        
           |  |  | 10352 |                 $this->func = $func;
 | 
        
           |  |  | 10353 |             }
 | 
        
           |  |  | 10354 |             /**
 | 
        
           |  |  | 10355 |              * Calls the callback function.
 | 
        
           |  |  | 10356 |              * @return mixed
 | 
        
           |  |  | 10357 |              */
 | 
        
           | 1326 | ariadna | 10358 |             public function method()
 | 
        
           |  |  | 10359 |             {
 | 
        
           | 1 | efrain | 10360 |                 $args = func_get_args();
 | 
        
           |  |  | 10361 |                 return call_user_func_array($this->func, array_merge($this->values, $args));
 | 
        
           |  |  | 10362 |             }
 | 
        
           |  |  | 10363 |         }
 | 
        
           |  |  | 10364 |     }
 | 
        
           |  |  | 10365 |     $args = func_get_args();
 | 
        
           |  |  | 10366 |     $func = array_shift($args);
 | 
        
           |  |  | 10367 |     $p = new partial($func, $args);
 | 
        
           |  |  | 10368 |     return array($p, 'method');
 | 
        
           |  |  | 10369 | }
 | 
        
           |  |  | 10370 |   | 
        
           |  |  | 10371 | /**
 | 
        
           |  |  | 10372 |  * helper function to load up and initialise the mnet environment
 | 
        
           |  |  | 10373 |  * this must be called before you use mnet functions.
 | 
        
           |  |  | 10374 |  *
 | 
        
           |  |  | 10375 |  * @return mnet_environment the equivalent of old $MNET global
 | 
        
           |  |  | 10376 |  */
 | 
        
           | 1326 | ariadna | 10377 | function get_mnet_environment()
 | 
        
           |  |  | 10378 | {
 | 
        
           | 1 | efrain | 10379 |     global $CFG;
 | 
        
           |  |  | 10380 |     require_once($CFG->dirroot . '/mnet/lib.php');
 | 
        
           |  |  | 10381 |     static $instance = null;
 | 
        
           |  |  | 10382 |     if (empty($instance)) {
 | 
        
           |  |  | 10383 |         $instance = new mnet_environment();
 | 
        
           |  |  | 10384 |         $instance->init();
 | 
        
           |  |  | 10385 |     }
 | 
        
           |  |  | 10386 |     return $instance;
 | 
        
           |  |  | 10387 | }
 | 
        
           |  |  | 10388 |   | 
        
           |  |  | 10389 | /**
 | 
        
           |  |  | 10390 |  * during xmlrpc server code execution, any code wishing to access
 | 
        
           |  |  | 10391 |  * information about the remote peer must use this to get it.
 | 
        
           |  |  | 10392 |  *
 | 
        
           |  |  | 10393 |  * @return mnet_remote_client|false the equivalent of old $MNETREMOTE_CLIENT global
 | 
        
           |  |  | 10394 |  */
 | 
        
           | 1326 | ariadna | 10395 | function get_mnet_remote_client()
 | 
        
           |  |  | 10396 | {
 | 
        
           | 1 | efrain | 10397 |     if (!defined('MNET_SERVER')) {
 | 
        
           |  |  | 10398 |         debugging(get_string('notinxmlrpcserver', 'mnet'));
 | 
        
           |  |  | 10399 |         return false;
 | 
        
           |  |  | 10400 |     }
 | 
        
           |  |  | 10401 |     global $MNET_REMOTE_CLIENT;
 | 
        
           |  |  | 10402 |     if (isset($MNET_REMOTE_CLIENT)) {
 | 
        
           |  |  | 10403 |         return $MNET_REMOTE_CLIENT;
 | 
        
           |  |  | 10404 |     }
 | 
        
           |  |  | 10405 |     return false;
 | 
        
           |  |  | 10406 | }
 | 
        
           |  |  | 10407 |   | 
        
           |  |  | 10408 | /**
 | 
        
           |  |  | 10409 |  * during the xmlrpc server code execution, this will be called
 | 
        
           |  |  | 10410 |  * to setup the object returned by {@link get_mnet_remote_client}
 | 
        
           |  |  | 10411 |  *
 | 
        
           |  |  | 10412 |  * @param mnet_remote_client $client the client to set up
 | 
        
           |  |  | 10413 |  * @throws moodle_exception
 | 
        
           |  |  | 10414 |  */
 | 
        
           | 1326 | ariadna | 10415 | function set_mnet_remote_client($client)
 | 
        
           |  |  | 10416 | {
 | 
        
           | 1 | efrain | 10417 |     if (!defined('MNET_SERVER')) {
 | 
        
           |  |  | 10418 |         throw new moodle_exception('notinxmlrpcserver', 'mnet');
 | 
        
           |  |  | 10419 |     }
 | 
        
           |  |  | 10420 |     global $MNET_REMOTE_CLIENT;
 | 
        
           |  |  | 10421 |     $MNET_REMOTE_CLIENT = $client;
 | 
        
           |  |  | 10422 | }
 | 
        
           |  |  | 10423 |   | 
        
           |  |  | 10424 | /**
 | 
        
           |  |  | 10425 |  * return the jump url for a given remote user
 | 
        
           |  |  | 10426 |  * this is used for rewriting forum post links in emails, etc
 | 
        
           |  |  | 10427 |  *
 | 
        
           |  |  | 10428 |  * @param stdclass $user the user to get the idp url for
 | 
        
           |  |  | 10429 |  */
 | 
        
           | 1326 | ariadna | 10430 | function mnet_get_idp_jump_url($user)
 | 
        
           |  |  | 10431 | {
 | 
        
           | 1 | efrain | 10432 |     global $CFG;
 | 
        
           |  |  | 10433 |   | 
        
           |  |  | 10434 |     static $mnetjumps = array();
 | 
        
           |  |  | 10435 |     if (!array_key_exists($user->mnethostid, $mnetjumps)) {
 | 
        
           |  |  | 10436 |         $idp = mnet_get_peer_host($user->mnethostid);
 | 
        
           |  |  | 10437 |         $idpjumppath = mnet_get_app_jumppath($idp->applicationid);
 | 
        
           |  |  | 10438 |         $mnetjumps[$user->mnethostid] = $idp->wwwroot . $idpjumppath . '?hostwwwroot=' . $CFG->wwwroot . '&wantsurl=';
 | 
        
           |  |  | 10439 |     }
 | 
        
           |  |  | 10440 |     return $mnetjumps[$user->mnethostid];
 | 
        
           |  |  | 10441 | }
 | 
        
           |  |  | 10442 |   | 
        
           |  |  | 10443 | /**
 | 
        
           |  |  | 10444 |  * Gets the homepage to use for the current user
 | 
        
           |  |  | 10445 |  *
 | 
        
           |  |  | 10446 |  * @return int One of HOMEPAGE_*
 | 
        
           |  |  | 10447 |  */
 | 
        
           | 1326 | ariadna | 10448 | function get_home_page()
 | 
        
           |  |  | 10449 | {
 | 
        
           | 1 | efrain | 10450 |     global $CFG;
 | 
        
           |  |  | 10451 |   | 
        
           |  |  | 10452 |     if (isloggedin() && !isguestuser() && !empty($CFG->defaulthomepage)) {
 | 
        
           |  |  | 10453 |         // If dashboard is disabled, home will be set to default page.
 | 
        
           |  |  | 10454 |         $defaultpage = get_default_home_page();
 | 
        
           |  |  | 10455 |         if ($CFG->defaulthomepage == HOMEPAGE_MY) {
 | 
        
           |  |  | 10456 |             if (!empty($CFG->enabledashboard)) {
 | 
        
           |  |  | 10457 |                 return HOMEPAGE_MY;
 | 
        
           |  |  | 10458 |             } else {
 | 
        
           |  |  | 10459 |                 return $defaultpage;
 | 
        
           |  |  | 10460 |             }
 | 
        
           |  |  | 10461 |         } else if ($CFG->defaulthomepage == HOMEPAGE_MYCOURSES) {
 | 
        
           |  |  | 10462 |             return HOMEPAGE_MYCOURSES;
 | 
        
           |  |  | 10463 |         } else {
 | 
        
           |  |  | 10464 |             $userhomepage = (int) get_user_preferences('user_home_page_preference', $defaultpage);
 | 
        
           |  |  | 10465 |             if (empty($CFG->enabledashboard) && $userhomepage == HOMEPAGE_MY) {
 | 
        
           |  |  | 10466 |                 // If the user was using the dashboard but it's disabled, return the default home page.
 | 
        
           |  |  | 10467 |                 $userhomepage = $defaultpage;
 | 
        
           |  |  | 10468 |             }
 | 
        
           |  |  | 10469 |             return $userhomepage;
 | 
        
           |  |  | 10470 |         }
 | 
        
           |  |  | 10471 |     }
 | 
        
           |  |  | 10472 |     return HOMEPAGE_SITE;
 | 
        
           |  |  | 10473 | }
 | 
        
           |  |  | 10474 |   | 
        
           |  |  | 10475 | /**
 | 
        
           |  |  | 10476 |  * Returns the default home page to display if current one is not defined or can't be applied.
 | 
        
           |  |  | 10477 |  * The default behaviour is to return Dashboard if it's enabled or My courses page if it isn't.
 | 
        
           |  |  | 10478 |  *
 | 
        
           |  |  | 10479 |  * @return int The default home page.
 | 
        
           |  |  | 10480 |  */
 | 
        
           | 1326 | ariadna | 10481 | function get_default_home_page(): int
 | 
        
           |  |  | 10482 | {
 | 
        
           | 1 | efrain | 10483 |     global $CFG;
 | 
        
           |  |  | 10484 |   | 
        
           |  |  | 10485 |     return (!isset($CFG->enabledashboard) || $CFG->enabledashboard) ? HOMEPAGE_MY : HOMEPAGE_MYCOURSES;
 | 
        
           |  |  | 10486 | }
 | 
        
           |  |  | 10487 |   | 
        
           |  |  | 10488 | /**
 | 
        
           |  |  | 10489 |  * Gets the name of a course to be displayed when showing a list of courses.
 | 
        
           |  |  | 10490 |  * By default this is just $course->fullname but user can configure it. The
 | 
        
           |  |  | 10491 |  * result of this function should be passed through print_string.
 | 
        
           |  |  | 10492 |  * @param stdClass|core_course_list_element $course Moodle course object
 | 
        
           |  |  | 10493 |  * @return string Display name of course (either fullname or short + fullname)
 | 
        
           |  |  | 10494 |  */
 | 
        
           | 1326 | ariadna | 10495 | function get_course_display_name_for_list($course)
 | 
        
           |  |  | 10496 | {
 | 
        
           | 1 | efrain | 10497 |     global $CFG;
 | 
        
           |  |  | 10498 |     if (!empty($CFG->courselistshortnames)) {
 | 
        
           |  |  | 10499 |         if (!($course instanceof stdClass)) {
 | 
        
           |  |  | 10500 |             $course = (object)convert_to_array($course);
 | 
        
           |  |  | 10501 |         }
 | 
        
           |  |  | 10502 |         return get_string('courseextendednamedisplay', '', $course);
 | 
        
           |  |  | 10503 |     } else {
 | 
        
           |  |  | 10504 |         return $course->fullname;
 | 
        
           |  |  | 10505 |     }
 | 
        
           |  |  | 10506 | }
 | 
        
           |  |  | 10507 |   | 
        
           |  |  | 10508 | /**
 | 
        
           |  |  | 10509 |  * Safe analogue of unserialize() that can only parse arrays
 | 
        
           |  |  | 10510 |  *
 | 
        
           |  |  | 10511 |  * Arrays may contain only integers or strings as both keys and values. Nested arrays are allowed.
 | 
        
           |  |  | 10512 |  *
 | 
        
           |  |  | 10513 |  * @param string $expression
 | 
        
           |  |  | 10514 |  * @return array|bool either parsed array or false if parsing was impossible.
 | 
        
           |  |  | 10515 |  */
 | 
        
           | 1326 | ariadna | 10516 | function unserialize_array($expression)
 | 
        
           |  |  | 10517 | {
 | 
        
           | 1 | efrain | 10518 |   | 
        
           |  |  | 10519 |     // Check the expression is an array.
 | 
        
           |  |  | 10520 |     if (!preg_match('/^a:(\d+):/', $expression)) {
 | 
        
           |  |  | 10521 |         return false;
 | 
        
           |  |  | 10522 |     }
 | 
        
           |  |  | 10523 |   | 
        
           |  |  | 10524 |     $values = (array) unserialize_object($expression);
 | 
        
           |  |  | 10525 |   | 
        
           |  |  | 10526 |     // Callback that returns true if the given value is an unserialized object, executes recursively.
 | 
        
           | 1326 | ariadna | 10527 |     $invalidvaluecallback = static function ($value) use (&$invalidvaluecallback): bool {
 | 
        
           | 1 | efrain | 10528 |         if (is_array($value)) {
 | 
        
           |  |  | 10529 |             return (bool) array_filter($value, $invalidvaluecallback);
 | 
        
           |  |  | 10530 |         }
 | 
        
           |  |  | 10531 |         return ($value instanceof stdClass) || ($value instanceof __PHP_Incomplete_Class);
 | 
        
           |  |  | 10532 |     };
 | 
        
           |  |  | 10533 |   | 
        
           |  |  | 10534 |     // Iterate over the result to ensure there are no stray objects.
 | 
        
           |  |  | 10535 |     if (array_filter($values, $invalidvaluecallback)) {
 | 
        
           |  |  | 10536 |         return false;
 | 
        
           |  |  | 10537 |     }
 | 
        
           |  |  | 10538 |   | 
        
           |  |  | 10539 |     return $values;
 | 
        
           |  |  | 10540 | }
 | 
        
           |  |  | 10541 |   | 
        
           |  |  | 10542 | /**
 | 
        
           |  |  | 10543 |  * Safe method for unserializing given input that is expected to contain only a serialized instance of an stdClass object
 | 
        
           |  |  | 10544 |  *
 | 
        
           |  |  | 10545 |  * If any class type other than stdClass is included in the input string, it will not be instantiated and will be cast to an
 | 
        
           |  |  | 10546 |  * stdClass object. The initial cast to array, then back to object is to ensure we are always returning the correct type,
 | 
        
           |  |  | 10547 |  * otherwise we would return an instances of {@see __PHP_Incomplete_class} for malformed strings
 | 
        
           |  |  | 10548 |  *
 | 
        
           |  |  | 10549 |  * @param string $input
 | 
        
           |  |  | 10550 |  * @return stdClass
 | 
        
           |  |  | 10551 |  */
 | 
        
           | 1326 | ariadna | 10552 | function unserialize_object(string $input): stdClass
 | 
        
           |  |  | 10553 | {
 | 
        
           | 1 | efrain | 10554 |     $instance = (array) unserialize($input, ['allowed_classes' => [stdClass::class]]);
 | 
        
           |  |  | 10555 |     return (object) $instance;
 | 
        
           |  |  | 10556 | }
 | 
        
           |  |  | 10557 |   | 
        
           |  |  | 10558 | /**
 | 
        
           |  |  | 10559 |  * The lang_string class
 | 
        
           |  |  | 10560 |  *
 | 
        
           |  |  | 10561 |  * This special class is used to create an object representation of a string request.
 | 
        
           |  |  | 10562 |  * It is special because processing doesn't occur until the object is first used.
 | 
        
           |  |  | 10563 |  * The class was created especially to aid performance in areas where strings were
 | 
        
           |  |  | 10564 |  * required to be generated but were not necessarily used.
 | 
        
           |  |  | 10565 |  * As an example the admin tree when generated uses over 1500 strings, of which
 | 
        
           |  |  | 10566 |  * normally only 1/3 are ever actually printed at any time.
 | 
        
           |  |  | 10567 |  * The performance advantage is achieved by not actually processing strings that
 | 
        
           |  |  | 10568 |  * arn't being used, as such reducing the processing required for the page.
 | 
        
           |  |  | 10569 |  *
 | 
        
           |  |  | 10570 |  * How to use the lang_string class?
 | 
        
           |  |  | 10571 |  *     There are two methods of using the lang_string class, first through the
 | 
        
           |  |  | 10572 |  *     forth argument of the get_string function, and secondly directly.
 | 
        
           |  |  | 10573 |  *     The following are examples of both.
 | 
        
           |  |  | 10574 |  * 1. Through get_string calls e.g.
 | 
        
           |  |  | 10575 |  *     $string = get_string($identifier, $component, $a, true);
 | 
        
           |  |  | 10576 |  *     $string = get_string('yes', 'moodle', null, true);
 | 
        
           |  |  | 10577 |  * 2. Direct instantiation
 | 
        
           |  |  | 10578 |  *     $string = new lang_string($identifier, $component, $a, $lang);
 | 
        
           |  |  | 10579 |  *     $string = new lang_string('yes');
 | 
        
           |  |  | 10580 |  *
 | 
        
           |  |  | 10581 |  * How do I use a lang_string object?
 | 
        
           |  |  | 10582 |  *     The lang_string object makes use of a magic __toString method so that you
 | 
        
           |  |  | 10583 |  *     are able to use the object exactly as you would use a string in most cases.
 | 
        
           |  |  | 10584 |  *     This means you are able to collect it into a variable and then directly
 | 
        
           |  |  | 10585 |  *     echo it, or concatenate it into another string, or similar.
 | 
        
           |  |  | 10586 |  *     The other thing you can do is manually get the string by calling the
 | 
        
           |  |  | 10587 |  *     lang_strings out method e.g.
 | 
        
           |  |  | 10588 |  *         $string = new lang_string('yes');
 | 
        
           |  |  | 10589 |  *         $string->out();
 | 
        
           |  |  | 10590 |  *     Also worth noting is that the out method can take one argument, $lang which
 | 
        
           |  |  | 10591 |  *     allows the developer to change the language on the fly.
 | 
        
           |  |  | 10592 |  *
 | 
        
           |  |  | 10593 |  * When should I use a lang_string object?
 | 
        
           |  |  | 10594 |  *     The lang_string object is designed to be used in any situation where a
 | 
        
           |  |  | 10595 |  *     string may not be needed, but needs to be generated.
 | 
        
           |  |  | 10596 |  *     The admin tree is a good example of where lang_string objects should be
 | 
        
           |  |  | 10597 |  *     used.
 | 
        
           |  |  | 10598 |  *     A more practical example would be any class that requries strings that may
 | 
        
           |  |  | 10599 |  *     not be printed (after all classes get renderer by renderers and who knows
 | 
        
           |  |  | 10600 |  *     what they will do ;))
 | 
        
           |  |  | 10601 |  *
 | 
        
           |  |  | 10602 |  * When should I not use a lang_string object?
 | 
        
           |  |  | 10603 |  *     Don't use lang_strings when you are going to use a string immediately.
 | 
        
           |  |  | 10604 |  *     There is no need as it will be processed immediately and there will be no
 | 
        
           |  |  | 10605 |  *     advantage, and in fact perhaps a negative hit as a class has to be
 | 
        
           |  |  | 10606 |  *     instantiated for a lang_string object, however get_string won't require
 | 
        
           |  |  | 10607 |  *     that.
 | 
        
           |  |  | 10608 |  *
 | 
        
           |  |  | 10609 |  * Limitations:
 | 
        
           |  |  | 10610 |  * 1. You cannot use a lang_string object as an array offset. Doing so will
 | 
        
           |  |  | 10611 |  *     result in PHP throwing an error. (You can use it as an object property!)
 | 
        
           |  |  | 10612 |  *
 | 
        
           |  |  | 10613 |  * @package    core
 | 
        
           |  |  | 10614 |  * @category   string
 | 
        
           |  |  | 10615 |  * @copyright  2011 Sam Hemelryk
 | 
        
           |  |  | 10616 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 10617 |  */
 | 
        
           | 1326 | ariadna | 10618 | class lang_string
 | 
        
           |  |  | 10619 | {
 | 
        
           | 1 | efrain | 10620 |   | 
        
           |  |  | 10621 |     /** @var string The strings identifier */
 | 
        
           |  |  | 10622 |     protected $identifier;
 | 
        
           |  |  | 10623 |     /** @var string The strings component. Default '' */
 | 
        
           |  |  | 10624 |     protected $component = '';
 | 
        
           |  |  | 10625 |     /** @var array|stdClass Any arguments required for the string. Default null */
 | 
        
           |  |  | 10626 |     protected $a = null;
 | 
        
           |  |  | 10627 |     /** @var string The language to use when processing the string. Default null */
 | 
        
           |  |  | 10628 |     protected $lang = null;
 | 
        
           |  |  | 10629 |   | 
        
           |  |  | 10630 |     /** @var string The processed string (once processed) */
 | 
        
           |  |  | 10631 |     protected $string = null;
 | 
        
           |  |  | 10632 |   | 
        
           |  |  | 10633 |     /**
 | 
        
           |  |  | 10634 |      * A special boolean. If set to true then the object has been woken up and
 | 
        
           |  |  | 10635 |      * cannot be regenerated. If this is set then $this->string MUST be used.
 | 
        
           |  |  | 10636 |      * @var bool
 | 
        
           |  |  | 10637 |      */
 | 
        
           |  |  | 10638 |     protected $forcedstring = false;
 | 
        
           |  |  | 10639 |   | 
        
           |  |  | 10640 |     /**
 | 
        
           |  |  | 10641 |      * Constructs a lang_string object
 | 
        
           |  |  | 10642 |      *
 | 
        
           |  |  | 10643 |      * This function should do as little processing as possible to ensure the best
 | 
        
           |  |  | 10644 |      * performance for strings that won't be used.
 | 
        
           |  |  | 10645 |      *
 | 
        
           |  |  | 10646 |      * @param string $identifier The strings identifier
 | 
        
           |  |  | 10647 |      * @param string $component The strings component
 | 
        
           |  |  | 10648 |      * @param stdClass|array|mixed $a Any arguments the string requires
 | 
        
           |  |  | 10649 |      * @param string $lang The language to use when processing the string.
 | 
        
           |  |  | 10650 |      * @throws coding_exception
 | 
        
           |  |  | 10651 |      */
 | 
        
           | 1326 | ariadna | 10652 |     public function __construct($identifier, $component = '', $a = null, $lang = null)
 | 
        
           |  |  | 10653 |     {
 | 
        
           | 1 | efrain | 10654 |         if (empty($component)) {
 | 
        
           |  |  | 10655 |             $component = 'moodle';
 | 
        
           |  |  | 10656 |         }
 | 
        
           |  |  | 10657 |   | 
        
           |  |  | 10658 |         $this->identifier = $identifier;
 | 
        
           |  |  | 10659 |         $this->component = $component;
 | 
        
           |  |  | 10660 |         $this->lang = $lang;
 | 
        
           |  |  | 10661 |   | 
        
           |  |  | 10662 |         // We MUST duplicate $a to ensure that it if it changes by reference those
 | 
        
           |  |  | 10663 |         // changes are not carried across.
 | 
        
           |  |  | 10664 |         // To do this we always ensure $a or its properties/values are strings
 | 
        
           |  |  | 10665 |         // and that any properties/values that arn't convertable are forgotten.
 | 
        
           |  |  | 10666 |         if ($a !== null) {
 | 
        
           |  |  | 10667 |             if (is_scalar($a)) {
 | 
        
           |  |  | 10668 |                 $this->a = $a;
 | 
        
           |  |  | 10669 |             } else if ($a instanceof lang_string) {
 | 
        
           |  |  | 10670 |                 $this->a = $a->out();
 | 
        
           |  |  | 10671 |             } else if (is_object($a) or is_array($a)) {
 | 
        
           |  |  | 10672 |                 $a = (array)$a;
 | 
        
           |  |  | 10673 |                 $this->a = array();
 | 
        
           |  |  | 10674 |                 foreach ($a as $key => $value) {
 | 
        
           |  |  | 10675 |                     // Make sure conversion errors don't get displayed (results in '').
 | 
        
           |  |  | 10676 |                     if (is_array($value)) {
 | 
        
           |  |  | 10677 |                         $this->a[$key] = '';
 | 
        
           |  |  | 10678 |                     } else if (is_object($value)) {
 | 
        
           |  |  | 10679 |                         if (method_exists($value, '__toString')) {
 | 
        
           |  |  | 10680 |                             $this->a[$key] = $value->__toString();
 | 
        
           |  |  | 10681 |                         } else {
 | 
        
           |  |  | 10682 |                             $this->a[$key] = '';
 | 
        
           |  |  | 10683 |                         }
 | 
        
           |  |  | 10684 |                     } else {
 | 
        
           |  |  | 10685 |                         $this->a[$key] = (string)$value;
 | 
        
           |  |  | 10686 |                     }
 | 
        
           |  |  | 10687 |                 }
 | 
        
           |  |  | 10688 |             }
 | 
        
           |  |  | 10689 |         }
 | 
        
           |  |  | 10690 |   | 
        
           |  |  | 10691 |         if (debugging(false, DEBUG_DEVELOPER)) {
 | 
        
           |  |  | 10692 |             if (clean_param($this->identifier, PARAM_STRINGID) == '') {
 | 
        
           |  |  | 10693 |                 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition');
 | 
        
           |  |  | 10694 |             }
 | 
        
           |  |  | 10695 |             if (!empty($this->component) && clean_param($this->component, PARAM_COMPONENT) == '') {
 | 
        
           |  |  | 10696 |                 throw new coding_exception('Invalid string compontent. Please check your string definition');
 | 
        
           |  |  | 10697 |             }
 | 
        
           |  |  | 10698 |             if (!get_string_manager()->string_exists($this->identifier, $this->component)) {
 | 
        
           | 1326 | ariadna | 10699 |                 debugging('String does not exist. Please check your string definition for ' . $this->identifier . '/' . $this->component, DEBUG_DEVELOPER);
 | 
        
           | 1 | efrain | 10700 |             }
 | 
        
           |  |  | 10701 |         }
 | 
        
           |  |  | 10702 |     }
 | 
        
           |  |  | 10703 |   | 
        
           |  |  | 10704 |     /**
 | 
        
           |  |  | 10705 |      * Processes the string.
 | 
        
           |  |  | 10706 |      *
 | 
        
           |  |  | 10707 |      * This function actually processes the string, stores it in the string property
 | 
        
           |  |  | 10708 |      * and then returns it.
 | 
        
           |  |  | 10709 |      * You will notice that this function is VERY similar to the get_string method.
 | 
        
           |  |  | 10710 |      * That is because it is pretty much doing the same thing.
 | 
        
           |  |  | 10711 |      * However as this function is an upgrade it isn't as tolerant to backwards
 | 
        
           |  |  | 10712 |      * compatibility.
 | 
        
           |  |  | 10713 |      *
 | 
        
           |  |  | 10714 |      * @return string
 | 
        
           |  |  | 10715 |      * @throws coding_exception
 | 
        
           |  |  | 10716 |      */
 | 
        
           | 1326 | ariadna | 10717 |     protected function get_string()
 | 
        
           |  |  | 10718 |     {
 | 
        
           | 1 | efrain | 10719 |         global $CFG;
 | 
        
           |  |  | 10720 |   | 
        
           |  |  | 10721 |         // Check if we need to process the string.
 | 
        
           |  |  | 10722 |         if ($this->string === null) {
 | 
        
           |  |  | 10723 |             // Check the quality of the identifier.
 | 
        
           |  |  | 10724 |             if ($CFG->debugdeveloper && clean_param($this->identifier, PARAM_STRINGID) === '') {
 | 
        
           |  |  | 10725 |                 throw new coding_exception('Invalid string identifier. Most probably some illegal character is part of the string identifier. Please check your string definition', DEBUG_DEVELOPER);
 | 
        
           |  |  | 10726 |             }
 | 
        
           |  |  | 10727 |   | 
        
           |  |  | 10728 |             // Process the string.
 | 
        
           |  |  | 10729 |             $this->string = get_string_manager()->get_string($this->identifier, $this->component, $this->a, $this->lang);
 | 
        
           |  |  | 10730 |             // Debugging feature lets you display string identifier and component.
 | 
        
           |  |  | 10731 |             if (isset($CFG->debugstringids) && $CFG->debugstringids && optional_param('strings', 0, PARAM_INT)) {
 | 
        
           |  |  | 10732 |                 $this->string .= ' {' . $this->identifier . '/' . $this->component . '}';
 | 
        
           |  |  | 10733 |             }
 | 
        
           |  |  | 10734 |         }
 | 
        
           |  |  | 10735 |         // Return the string.
 | 
        
           |  |  | 10736 |         return $this->string;
 | 
        
           |  |  | 10737 |     }
 | 
        
           |  |  | 10738 |   | 
        
           |  |  | 10739 |     /**
 | 
        
           |  |  | 10740 |      * Returns the string
 | 
        
           |  |  | 10741 |      *
 | 
        
           |  |  | 10742 |      * @param string $lang The langauge to use when processing the string
 | 
        
           |  |  | 10743 |      * @return string
 | 
        
           |  |  | 10744 |      */
 | 
        
           | 1326 | ariadna | 10745 |     public function out($lang = null)
 | 
        
           |  |  | 10746 |     {
 | 
        
           | 1 | efrain | 10747 |         if ($lang !== null && $lang != $this->lang && ($this->lang == null && $lang != current_language())) {
 | 
        
           |  |  | 10748 |             if ($this->forcedstring) {
 | 
        
           | 1326 | ariadna | 10749 |                 debugging('lang_string objects that have been used cannot be printed in another language. (' . $this->lang . ' used)', DEBUG_DEVELOPER);
 | 
        
           | 1 | efrain | 10750 |                 return $this->get_string();
 | 
        
           |  |  | 10751 |             }
 | 
        
           |  |  | 10752 |             $translatedstring = new lang_string($this->identifier, $this->component, $this->a, $lang);
 | 
        
           |  |  | 10753 |             return $translatedstring->out();
 | 
        
           |  |  | 10754 |         }
 | 
        
           |  |  | 10755 |         return $this->get_string();
 | 
        
           |  |  | 10756 |     }
 | 
        
           |  |  | 10757 |   | 
        
           |  |  | 10758 |     /**
 | 
        
           |  |  | 10759 |      * Magic __toString method for printing a string
 | 
        
           |  |  | 10760 |      *
 | 
        
           |  |  | 10761 |      * @return string
 | 
        
           |  |  | 10762 |      */
 | 
        
           | 1326 | ariadna | 10763 |     public function __toString()
 | 
        
           |  |  | 10764 |     {
 | 
        
           | 1 | efrain | 10765 |         return $this->get_string();
 | 
        
           |  |  | 10766 |     }
 | 
        
           |  |  | 10767 |   | 
        
           |  |  | 10768 |     /**
 | 
        
           |  |  | 10769 |      * Magic __set_state method used for var_export
 | 
        
           |  |  | 10770 |      *
 | 
        
           |  |  | 10771 |      * @param array $array
 | 
        
           |  |  | 10772 |      * @return self
 | 
        
           |  |  | 10773 |      */
 | 
        
           | 1326 | ariadna | 10774 |     public static function __set_state(array $array): self
 | 
        
           |  |  | 10775 |     {
 | 
        
           | 1 | efrain | 10776 |         $tmp = new lang_string($array['identifier'], $array['component'], $array['a'], $array['lang']);
 | 
        
           |  |  | 10777 |         $tmp->string = $array['string'];
 | 
        
           |  |  | 10778 |         $tmp->forcedstring = $array['forcedstring'];
 | 
        
           |  |  | 10779 |         return $tmp;
 | 
        
           |  |  | 10780 |     }
 | 
        
           |  |  | 10781 |   | 
        
           |  |  | 10782 |     /**
 | 
        
           |  |  | 10783 |      * Prepares the lang_string for sleep and stores only the forcedstring and
 | 
        
           |  |  | 10784 |      * string properties... the string cannot be regenerated so we need to ensure
 | 
        
           |  |  | 10785 |      * it is generated for this.
 | 
        
           |  |  | 10786 |      *
 | 
        
           |  |  | 10787 |      * @return array
 | 
        
           |  |  | 10788 |      */
 | 
        
           | 1326 | ariadna | 10789 |     public function __sleep()
 | 
        
           |  |  | 10790 |     {
 | 
        
           | 1 | efrain | 10791 |         $this->get_string();
 | 
        
           |  |  | 10792 |         $this->forcedstring = true;
 | 
        
           |  |  | 10793 |         return array('forcedstring', 'string', 'lang');
 | 
        
           |  |  | 10794 |     }
 | 
        
           |  |  | 10795 |   | 
        
           |  |  | 10796 |     /**
 | 
        
           |  |  | 10797 |      * Returns the identifier.
 | 
        
           |  |  | 10798 |      *
 | 
        
           |  |  | 10799 |      * @return string
 | 
        
           |  |  | 10800 |      */
 | 
        
           | 1326 | ariadna | 10801 |     public function get_identifier()
 | 
        
           |  |  | 10802 |     {
 | 
        
           | 1 | efrain | 10803 |         return $this->identifier;
 | 
        
           |  |  | 10804 |     }
 | 
        
           |  |  | 10805 |   | 
        
           |  |  | 10806 |     /**
 | 
        
           |  |  | 10807 |      * Returns the component.
 | 
        
           |  |  | 10808 |      *
 | 
        
           |  |  | 10809 |      * @return string
 | 
        
           |  |  | 10810 |      */
 | 
        
           | 1326 | ariadna | 10811 |     public function get_component()
 | 
        
           |  |  | 10812 |     {
 | 
        
           | 1 | efrain | 10813 |         return $this->component;
 | 
        
           |  |  | 10814 |     }
 | 
        
           |  |  | 10815 | }
 | 
        
           |  |  | 10816 |   | 
        
           |  |  | 10817 | /**
 | 
        
           |  |  | 10818 |  * Get human readable name describing the given callable.
 | 
        
           |  |  | 10819 |  *
 | 
        
           |  |  | 10820 |  * This performs syntax check only to see if the given param looks like a valid function, method or closure.
 | 
        
           |  |  | 10821 |  * It does not check if the callable actually exists.
 | 
        
           |  |  | 10822 |  *
 | 
        
           |  |  | 10823 |  * @param callable|string|array $callable
 | 
        
           |  |  | 10824 |  * @return string|bool Human readable name of callable, or false if not a valid callable.
 | 
        
           |  |  | 10825 |  */
 | 
        
           | 1326 | ariadna | 10826 | function get_callable_name($callable)
 | 
        
           |  |  | 10827 | {
 | 
        
           | 1 | efrain | 10828 |   | 
        
           |  |  | 10829 |     if (!is_callable($callable, true, $name)) {
 | 
        
           |  |  | 10830 |         return false;
 | 
        
           |  |  | 10831 |     } else {
 | 
        
           |  |  | 10832 |         return $name;
 | 
        
           |  |  | 10833 |     }
 | 
        
           |  |  | 10834 | }
 | 
        
           |  |  | 10835 |   | 
        
           |  |  | 10836 | /**
 | 
        
           |  |  | 10837 |  * Tries to guess if $CFG->wwwroot is publicly accessible or not.
 | 
        
           |  |  | 10838 |  * Never put your faith on this function and rely on its accuracy as there might be false positives.
 | 
        
           |  |  | 10839 |  * It just performs some simple checks, and mainly is used for places where we want to hide some options
 | 
        
           |  |  | 10840 |  * such as site registration when $CFG->wwwroot is not publicly accessible.
 | 
        
           |  |  | 10841 |  * Good thing is there is no false negative.
 | 
        
           |  |  | 10842 |  * Note that it's possible to force the result of this check by specifying $CFG->site_is_public in config.php
 | 
        
           |  |  | 10843 |  *
 | 
        
           |  |  | 10844 |  * @return bool
 | 
        
           |  |  | 10845 |  */
 | 
        
           | 1326 | ariadna | 10846 | function site_is_public()
 | 
        
           |  |  | 10847 | {
 | 
        
           | 1 | efrain | 10848 |     global $CFG;
 | 
        
           |  |  | 10849 |   | 
        
           |  |  | 10850 |     // Return early if site admin has forced this setting.
 | 
        
           |  |  | 10851 |     if (isset($CFG->site_is_public)) {
 | 
        
           |  |  | 10852 |         return (bool)$CFG->site_is_public;
 | 
        
           |  |  | 10853 |     }
 | 
        
           |  |  | 10854 |   | 
        
           |  |  | 10855 |     $host = parse_url($CFG->wwwroot, PHP_URL_HOST);
 | 
        
           |  |  | 10856 |   | 
        
           |  |  | 10857 |     if ($host === 'localhost' || preg_match('|^127\.\d+\.\d+\.\d+$|', $host)) {
 | 
        
           |  |  | 10858 |         $ispublic = false;
 | 
        
           |  |  | 10859 |     } else if (\core\ip_utils::is_ip_address($host) && !ip_is_public($host)) {
 | 
        
           |  |  | 10860 |         $ispublic = false;
 | 
        
           |  |  | 10861 |     } else if (($address = \core\ip_utils::get_ip_address($host)) && !ip_is_public($address)) {
 | 
        
           |  |  | 10862 |         $ispublic = false;
 | 
        
           |  |  | 10863 |     } else {
 | 
        
           |  |  | 10864 |         $ispublic = true;
 | 
        
           |  |  | 10865 |     }
 | 
        
           |  |  | 10866 |   | 
        
           |  |  | 10867 |     return $ispublic;
 | 
        
           |  |  | 10868 | }
 | 
        
           |  |  | 10869 |   | 
        
           |  |  | 10870 | /**
 | 
        
           |  |  | 10871 |  * Validates user's password length.
 | 
        
           |  |  | 10872 |  *
 | 
        
           |  |  | 10873 |  * @param string $password
 | 
        
           |  |  | 10874 |  * @param int $pepperlength The length of the used peppers
 | 
        
           |  |  | 10875 |  * @return bool
 | 
        
           |  |  | 10876 |  */
 | 
        
           | 1326 | ariadna | 10877 | function exceeds_password_length(string $password, int $pepperlength = 0): bool
 | 
        
           |  |  | 10878 | {
 | 
        
           | 1 | efrain | 10879 |     return (strlen($password) > (MAX_PASSWORD_CHARACTERS + $pepperlength));
 | 
        
           |  |  | 10880 | }
 | 
        
           |  |  | 10881 |   | 
        
           |  |  | 10882 | /**
 | 
        
           |  |  | 10883 |  * A helper to replace PHP 8.3 usage of array_keys with two args.
 | 
        
           |  |  | 10884 |  *
 | 
        
           |  |  | 10885 |  * There is an indication that this will become a new method in PHP 8.4, but that has not happened yet.
 | 
        
           |  |  | 10886 |  * Therefore this non-polyfill has been created with a different naming convention.
 | 
        
           |  |  | 10887 |  * In the future it can be deprecated if a core PHP method is created.
 | 
        
           |  |  | 10888 |  *
 | 
        
           |  |  | 10889 |  * https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#array_keys
 | 
        
           |  |  | 10890 |  *
 | 
        
           |  |  | 10891 |  * @param array $array
 | 
        
           |  |  | 10892 |  * @param mixed $filter The value to filter on
 | 
        
           |  |  | 10893 |  * @param bool $strict Whether to apply a strit test with the filter
 | 
        
           |  |  | 10894 |  * @return array
 | 
        
           |  |  | 10895 |  */
 | 
        
           | 1326 | ariadna | 10896 | function moodle_array_keys_filter(array $array, mixed $filter, bool $strict = false): array
 | 
        
           |  |  | 10897 | {
 | 
        
           | 1 | efrain | 10898 |     return array_keys(array_filter(
 | 
        
           |  |  | 10899 |         $array,
 | 
        
           | 1326 | ariadna | 10900 |         function ($value, $key) use ($filter, $strict): bool {
 | 
        
           | 1 | efrain | 10901 |             if ($strict) {
 | 
        
           |  |  | 10902 |                 return $value === $filter;
 | 
        
           |  |  | 10903 |             }
 | 
        
           |  |  | 10904 |             return $value == $filter;
 | 
        
           |  |  | 10905 |         },
 | 
        
           |  |  | 10906 |         ARRAY_FILTER_USE_BOTH,
 | 
        
           |  |  | 10907 |     ));
 | 
        
           |  |  | 10908 | }
 |