See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * This library includes all the necessary stuff to use the one-click 20 * download and install feature of Moodle, used to keep updated some 21 * items like languages, pear, enviroment... i.e, components. 22 * 23 * It has been developed harcoding some important limits that are 24 * explained below: 25 * - It only can check, download and install items under moodledata. 26 * - Every downloadeable item must be one zip file. 27 * - The zip file root content must be 1 directory, i.e, everything 28 * is stored under 1 directory. 29 * - Zip file name and root directory must have the same name (but 30 * the .zip extension, of course). 31 * - Every .zip file must be defined in one .md5 file that will be 32 * stored in the same remote directory than the .zip file. 33 * - The name of such .md5 file is free, although it's recommended 34 * to use the same name than the .zip (that's the default 35 * assumption if no specified). 36 * - Every remote .md5 file will be a comma separated (CVS) file where each 37 * line will follow this format: 38 * - Field 1: name of the zip file (without extension). Mandatory. 39 * - Field 2: md5 of the zip file. Mandatory. 40 * - Field 3: whatever you want (or need). Optional. 41 * -Every local .md5 file will: 42 * - Have the zip file name (without the extension) plus -md5 43 * - Will reside inside the expanded zip file dir 44 * - Will contain the md5 od the latest installed component 45 * With all these details present, the process will perform this tasks: 46 * - Perform security checks. Only admins are allowed to use this for now. 47 * - Read the .md5 file from source (1). 48 * - Extract the correct line for the .zip being requested. 49 * - Compare it with the local .md5 file (2). 50 * - If different: 51 * - Download the newer .zip file from source. 52 * - Calculate its md5 (3). 53 * - Compare (1) and (3). 54 * - If equal: 55 * - Delete old directory. 56 * - Uunzip the newer .zip file. 57 * - Create the new local .md5 file. 58 * - Delete the .zip file. 59 * - If different: 60 * - ERROR. Old package won't be modified. We shouldn't 61 * reach here ever. 62 * - If component download is not possible, a message text about how to do 63 * the process manually (remotedownloaderror) must be displayed to explain it. 64 * 65 * General Usage: 66 * 67 * To install one component: 68 * <code> 69 * require_once($CFG->libdir.'/componentlib.class.php'); 70 * if ($cd = new component_installer('https://download.moodle.org', 'langpack/2.0', 71 * 'es.zip', 'languages.md5', 'lang')) { 72 * $status = $cd->install(); //returns COMPONENT_(ERROR | UPTODATE | INSTALLED) 73 * switch ($status) { 74 * case COMPONENT_ERROR: 75 * if ($cd->get_error() == 'remotedownloaderror') { 76 * $a = new stdClass(); 77 * $a->url = 'https://download.moodle.org/langpack/2.0/es.zip'; 78 * $a->dest= $CFG->dataroot.'/lang'; 79 * throw new \moodle_exception($cd->get_error(), 'error', '', $a); 80 * } else { 81 * throw new \moodle_exception($cd->get_error(), 'error'); 82 * } 83 * break; 84 * case COMPONENT_UPTODATE: 85 * //Print error string or whatever you want to do 86 * break; 87 * case COMPONENT_INSTALLED: 88 * //Print/do whatever you want 89 * break; 90 * default: 91 * //We shouldn't reach this point 92 * } 93 * } else { 94 * //We shouldn't reach this point 95 * } 96 * </code> 97 * 98 * To switch of component (maintaining the rest of settings): 99 * <code> 100 * $status = $cd->change_zip_file('en.zip'); //returns boolean false on error 101 * </code> 102 * 103 * To retrieve all the components in one remote md5 file 104 * <code> 105 * $components = $cd->get_all_components_md5(); //returns boolean false on error, array instead 106 * </code> 107 * 108 * To check if current component needs to be updated 109 * <code> 110 * $status = $cd->need_upgrade(); //returns COMPONENT_(ERROR | UPTODATE | NEEDUPDATE) 111 * </code> 112 * 113 * To get the 3rd field of the md5 file (optional) 114 * <code> 115 * $field = $cd->get_extra_md5_field(); //returns string (empty if not exists) 116 * </code> 117 * 118 * For all the error situations the $cd->get_error() method should return always the key of the 119 * error to be retrieved by one standard get_string() call against the error.php lang file. 120 * 121 * That's all! 122 * 123 * @package core 124 * @copyright (C) 2001-3001 Eloy Lafuente (stronk7) {@link http://contiento.com} 125 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 126 */ 127 128 defined('MOODLE_INTERNAL') || die(); 129 130 /** 131 * @global object $CFG 132 * @name $CFG 133 */ 134 global $CFG; 135 require_once($CFG->libdir.'/filelib.php'); 136 137 // Some needed constants 138 define('COMPONENT_ERROR', 0); 139 define('COMPONENT_UPTODATE', 1); 140 define('COMPONENT_NEEDUPDATE', 2); 141 define('COMPONENT_INSTALLED', 3); 142 143 /** 144 * This class is used to check, download and install items from 145 * download.moodle.org to the moodledata directory. 146 * 147 * It always return true/false in all their public methods to say if 148 * execution has ended succesfuly or not. If there is any problem 149 * its getError() method can be called, returning one error string 150 * to be used with the standard get/print_string() functions. 151 * 152 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 153 * @package moodlecore 154 */ 155 class component_installer { 156 /** 157 * @var string 158 */ 159 var $sourcebase; /// Full http URL, base for downloadable items 160 var $zippath; /// Relative path (from sourcebase) where the 161 /// downloadeable item resides. 162 var $zipfilename; /// Name of the .zip file to be downloaded 163 var $md5filename; /// Name of the .md5 file to be read 164 var $componentname;/// Name of the component. Must be the zip name without 165 /// the extension. And it defines a lot of things: 166 /// the md5 line to search for, the default m5 file name 167 /// and the name of the root dir stored inside the zip file 168 var $destpath; /// Relative path (from moodledata) where the .zip 169 /// file will be expanded. 170 var $errorstring; /// Latest error produced. It will contain one lang string key. 171 var $extramd5info; /// Contents of the optional third field in the .md5 file. 172 var $requisitesok; /// Flag to see if requisites check has been passed ok. 173 /** 174 * @var array 175 */ 176 var $cachedmd5components; /// Array of cached components to avoid to 177 /// download the same md5 file more than once per request. 178 179 /** 180 * Standard constructor of the class. It will initialize all attributes. 181 * without performing any check at all. 182 * 183 * @param string $sourcebase Full http URL, base for downloadeable items 184 * @param string $zippath Relative path (from sourcebase) where the 185 * downloadeable item resides 186 * @param string $zipfilename Name of the .zip file to be downloaded 187 * @param string $md5filename Name of the .md5 file to be read (default '' = same 188 * than zipfilename) 189 * @param string $destpath Relative path (from moodledata) where the .zip file will 190 * be expanded (default='' = moodledataitself) 191 * @return object 192 */ 193 public function __construct($sourcebase, $zippath, $zipfilename, $md5filename='', $destpath='') { 194 195 $this->sourcebase = $sourcebase; 196 $this->zippath = $zippath; 197 $this->zipfilename = $zipfilename; 198 $this->md5filename = $md5filename; 199 $this->componentname= ''; 200 $this->destpath = $destpath; 201 $this->errorstring = ''; 202 $this->extramd5info = ''; 203 $this->requisitesok = false; 204 $this->cachedmd5components = array(); 205 206 $this->check_requisites(); 207 } 208 209 /** 210 * Old syntax of class constructor. Deprecated in PHP7. 211 * 212 * @deprecated since Moodle 3.1 213 */ 214 public function component_installer($sourcebase, $zippath, $zipfilename, $md5filename='', $destpath='') { 215 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 216 self::__construct($sourcebase, $zippath, $zipfilename, $md5filename, $destpath); 217 } 218 219 /** 220 * This function will check if everything is properly set to begin 221 * one installation. Also, it will check for required settings 222 * and will fill everything as needed. 223 * 224 * @global object 225 * @return boolean true/false (plus detailed error in errorstring) 226 */ 227 function check_requisites() { 228 global $CFG; 229 230 $this->requisitesok = false; 231 232 /// Check that everything we need is present 233 if (empty($this->sourcebase) || empty($this->zipfilename)) { 234 $this->errorstring='missingrequiredfield'; 235 return false; 236 } 237 /// Check for correct sourcebase (this will be out in the future) 238 if (!PHPUNIT_TEST and $this->sourcebase != 'https://download.moodle.org') { 239 $this->errorstring='wrongsourcebase'; 240 return false; 241 } 242 /// Check the zip file is a correct one (by extension) 243 if (stripos($this->zipfilename, '.zip') === false) { 244 $this->errorstring='wrongzipfilename'; 245 return false; 246 } 247 /// Check that exists under dataroot 248 if (!empty($this->destpath)) { 249 if (!file_exists($CFG->dataroot.'/'.$this->destpath)) { 250 $this->errorstring='wrongdestpath'; 251 return false; 252 } 253 } 254 /// Calculate the componentname 255 $pos = stripos($this->zipfilename, '.zip'); 256 $this->componentname = substr($this->zipfilename, 0, $pos); 257 /// Calculate md5filename if it's empty 258 if (empty($this->md5filename)) { 259 $this->md5filename = $this->componentname.'.md5'; 260 } 261 /// Set the requisites passed flag 262 $this->requisitesok = true; 263 return true; 264 } 265 266 /** 267 * This function will perform the full installation if needed, i.e. 268 * compare md5 values, download, unzip, install and regenerate 269 * local md5 file 270 * 271 * @uses COMPONENT_ERROR 272 * @uses COMPONENT_UPTODATE 273 * @uses COMPONENT_ERROR 274 * @uses COMPONENT_INSTALLED 275 * @return int COMPONENT_(ERROR | UPTODATE | INSTALLED) 276 */ 277 public function install() { 278 global $CFG; 279 280 /// Check requisites are passed 281 if (!$this->requisitesok) { 282 return COMPONENT_ERROR; 283 } 284 /// Confirm we need upgrade 285 if ($this->need_upgrade() === COMPONENT_ERROR) { 286 return COMPONENT_ERROR; 287 } else if ($this->need_upgrade() === COMPONENT_UPTODATE) { 288 $this->errorstring='componentisuptodate'; 289 return COMPONENT_UPTODATE; 290 } 291 /// Create temp directory if necesary 292 if (!make_temp_directory('', false)) { 293 $this->errorstring='cannotcreatetempdir'; 294 return COMPONENT_ERROR; 295 } 296 /// Download zip file and save it to temp 297 if ($this->zippath) { 298 $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->zipfilename; 299 } else { 300 $source = $this->sourcebase.'/'.$this->zipfilename; 301 } 302 303 $zipfile= $CFG->tempdir.'/'.$this->zipfilename; 304 305 $contents = download_file_content($source, null, null, true); 306 if ($contents->results && (int) $contents->status === 200) { 307 if ($file = fopen($zipfile, 'w')) { 308 if (!fwrite($file, $contents->results)) { 309 fclose($file); 310 $this->errorstring='cannotsavezipfile'; 311 return COMPONENT_ERROR; 312 } 313 } else { 314 $this->errorstring='cannotsavezipfile'; 315 return COMPONENT_ERROR; 316 } 317 fclose($file); 318 } else { 319 $this->errorstring='cannotdownloadzipfile'; 320 return COMPONENT_ERROR; 321 } 322 /// Calculate its md5 323 $new_md5 = md5($contents->results); 324 /// Compare it with the remote md5 to check if we have the correct zip file 325 if (!$remote_md5 = $this->get_component_md5()) { 326 return COMPONENT_ERROR; 327 } 328 if ($new_md5 != $remote_md5) { 329 $this->errorstring='downloadedfilecheckfailed'; 330 return COMPONENT_ERROR; 331 } 332 333 // Move current revision to a safe place. 334 $destinationdir = $CFG->dataroot . '/' . $this->destpath; 335 $destinationcomponent = $destinationdir . '/' . $this->componentname; 336 $destinationcomponentold = $destinationcomponent . '_old'; 337 @remove_dir($destinationcomponentold); // Deleting a possible old version. 338 339 // Moving to a safe place. 340 @rename($destinationcomponent, $destinationcomponentold); 341 342 // Unzip new version. 343 $packer = get_file_packer('application/zip'); 344 $unzipsuccess = $packer->extract_to_pathname($zipfile, $destinationdir, null, null, true); 345 if (!$unzipsuccess) { 346 @remove_dir($destinationcomponent); 347 @rename($destinationcomponentold, $destinationcomponent); 348 $this->errorstring = 'cannotunzipfile'; 349 return COMPONENT_ERROR; 350 } 351 352 // Delete old component version. 353 @remove_dir($destinationcomponentold); 354 355 // Create local md5. 356 if ($file = fopen($destinationcomponent.'/'.$this->componentname.'.md5', 'w')) { 357 if (!fwrite($file, $new_md5)) { 358 fclose($file); 359 $this->errorstring='cannotsavemd5file'; 360 return COMPONENT_ERROR; 361 } 362 } else { 363 $this->errorstring='cannotsavemd5file'; 364 return COMPONENT_ERROR; 365 } 366 fclose($file); 367 /// Delete temp zip file 368 @unlink($zipfile); 369 370 return COMPONENT_INSTALLED; 371 } 372 373 /** 374 * This function will detect if remote component needs to be installed 375 * because it's different from the local one 376 * 377 * @uses COMPONENT_ERROR 378 * @uses COMPONENT_UPTODATE 379 * @uses COMPONENT_NEEDUPDATE 380 * @return int COMPONENT_(ERROR | UPTODATE | NEEDUPDATE) 381 */ 382 function need_upgrade() { 383 384 /// Check requisites are passed 385 if (!$this->requisitesok) { 386 return COMPONENT_ERROR; 387 } 388 /// Get local md5 389 $local_md5 = $this->get_local_md5(); 390 /// Get remote md5 391 if (!$remote_md5 = $this->get_component_md5()) { 392 return COMPONENT_ERROR; 393 } 394 /// Return result 395 if ($local_md5 == $remote_md5) { 396 return COMPONENT_UPTODATE; 397 } else { 398 return COMPONENT_NEEDUPDATE; 399 } 400 } 401 402 /** 403 * This function will change the zip file to install on the fly 404 * to allow the class to process different components of the 405 * same md5 file without intantiating more objects. 406 * 407 * @param string $newzipfilename New zip filename to process 408 * @return boolean true/false 409 */ 410 function change_zip_file($newzipfilename) { 411 412 $this->zipfilename = $newzipfilename; 413 return $this->check_requisites(); 414 } 415 416 /** 417 * This function will get the local md5 value of the installed 418 * component. 419 * 420 * @global object 421 * @return bool|string md5 of the local component (false on error) 422 */ 423 function get_local_md5() { 424 global $CFG; 425 426 /// Check requisites are passed 427 if (!$this->requisitesok) { 428 return false; 429 } 430 431 $return_value = 'needtobeinstalled'; /// Fake value to force new installation 432 433 /// Calculate source to read 434 $source = $CFG->dataroot.'/'.$this->destpath.'/'.$this->componentname.'/'.$this->componentname.'.md5'; 435 /// Read md5 value stored (if exists) 436 if (file_exists($source)) { 437 if ($temp = file_get_contents($source)) { 438 $return_value = $temp; 439 } 440 } 441 return $return_value; 442 } 443 444 /** 445 * This function will download the specified md5 file, looking for the 446 * current componentname, returning its md5 field and storing extramd5info 447 * if present. Also it caches results to cachedmd5components for better 448 * performance in the same request. 449 * 450 * @return mixed md5 present in server (or false if error) 451 */ 452 function get_component_md5() { 453 454 /// Check requisites are passed 455 if (!$this->requisitesok) { 456 return false; 457 } 458 /// Get all components of md5 file 459 if (!$comp_arr = $this->get_all_components_md5()) { 460 if (empty($this->errorstring)) { 461 $this->errorstring='cannotdownloadcomponents'; 462 } 463 return false; 464 } 465 /// Search for the componentname component 466 if (empty($comp_arr[$this->componentname]) || !$component = $comp_arr[$this->componentname]) { 467 $this->errorstring='cannotfindcomponent'; 468 return false; 469 } 470 /// Check we have a valid md5 471 if (empty($component[1]) || strlen($component[1]) != 32) { 472 $this->errorstring='invalidmd5'; 473 return false; 474 } 475 /// Set the extramd5info field 476 if (!empty($component[2])) { 477 $this->extramd5info = $component[2]; 478 } 479 return $component[1]; 480 } 481 482 /** 483 * This function allows you to retrieve the complete array of components found in 484 * the md5filename 485 * 486 * @return bool|array array of components in md5 file or false if error 487 */ 488 function get_all_components_md5() { 489 490 /// Check requisites are passed 491 if (!$this->requisitesok) { 492 return false; 493 } 494 495 /// Initialize components array 496 $comp_arr = array(); 497 498 /// Define and retrieve the full md5 file 499 if ($this->zippath) { 500 $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->md5filename; 501 } else { 502 $source = $this->sourcebase.'/'.$this->md5filename; 503 } 504 505 /// Check if we have downloaded the md5 file before (per request cache) 506 if (!empty($this->cachedmd5components[$source])) { 507 $comp_arr = $this->cachedmd5components[$source]; 508 } else { 509 /// Not downloaded, let's do it now 510 $availablecomponents = array(); 511 512 $contents = download_file_content($source, null, null, true); 513 if ($contents->results && (int) $contents->status === 200) { 514 /// Split text into lines 515 $lines = preg_split('/\r?\n/', $contents->results); 516 /// Each line will be one component 517 foreach($lines as $line) { 518 $availablecomponents[] = explode(',', $line); 519 } 520 /// If no components have been found, return error 521 if (empty($availablecomponents)) { 522 $this->errorstring='cannotdownloadcomponents'; 523 return false; 524 } 525 /// Build an associative array of components for easily search 526 /// applying trim to avoid linefeeds and other... 527 $comp_arr = array(); 528 foreach ($availablecomponents as $component) { 529 /// Avoid sometimes empty lines 530 if (empty($component[0])) { 531 continue; 532 } 533 $component[0]=trim($component[0]); 534 if (!empty($component[1])) { 535 $component[1]=trim($component[1]); 536 } 537 if (!empty($component[2])) { 538 $component[2]=trim($component[2]); 539 } 540 $comp_arr[$component[0]] = $component; 541 } 542 /// Cache components 543 $this->cachedmd5components[$source] = $comp_arr; 544 } else { 545 /// Return error 546 $this->errorstring='remotedownloaderror'; 547 return false; 548 } 549 } 550 /// If there is no commponents or erros found, error 551 if (!empty($this->errorstring)) { 552 return false; 553 554 } else if (empty($comp_arr)) { 555 $this->errorstring='cannotdownloadcomponents'; 556 return false; 557 } 558 return $comp_arr; 559 } 560 561 /** 562 * This function returns the errorstring 563 * 564 * @return string the error string 565 */ 566 function get_error() { 567 return $this->errorstring; 568 } 569 570 /** This function returns the extramd5 field (optional in md5 file) 571 * 572 * @return string the extramd5 field 573 */ 574 function get_extra_md5_field() { 575 return $this->extramd5info; 576 } 577 578 } /// End of component_installer class 579 580 581 /** 582 * Language packs installer 583 * 584 * This class wraps the functionality provided by {@link component_installer} 585 * and adds support for installing a set of language packs. 586 * 587 * Given an array of required language packs, this class fetches them all 588 * and installs them. It detects eventual dependencies and installs 589 * all parent languages, too. 590 * 591 * @copyright 2011 David Mudrak <david@moodle.com> 592 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 593 */ 594 class lang_installer { 595 596 /** lang pack was successfully downloaded and deployed */ 597 const RESULT_INSTALLED = 'installed'; 598 /** lang pack was up-to-date so no download was needed */ 599 const RESULT_UPTODATE = 'uptodate'; 600 /** there was a problem with downloading the lang pack */ 601 const RESULT_DOWNLOADERROR = 'downloaderror'; 602 603 /** @var array of languages to install */ 604 protected $queue = array(); 605 /** @var string the code of language being currently installed */ 606 protected $current; 607 /** @var array of languages already installed by this instance */ 608 protected $done = array(); 609 /** @var string this Moodle major version */ 610 protected $version; 611 612 /** 613 * Prepare the installer 614 * 615 * @param string|array $langcode a code of the language to install 616 */ 617 public function __construct($langcode = '') { 618 global $CFG; 619 620 $this->set_queue($langcode); 621 $this->version = moodle_major_version(true); 622 623 if (!empty($CFG->langotherroot) and $CFG->langotherroot !== $CFG->dataroot . '/lang') { 624 debugging('The in-built language pack installer does not support alternative location ' . 625 'of languages root directory. You are supposed to install and update your language '. 626 'packs on your own.'); 627 } 628 } 629 630 /** 631 * Sets the queue of language packs to be installed 632 * 633 * @param string|array $langcodes language code like 'cs' or a list of them 634 */ 635 public function set_queue($langcodes) { 636 if (is_array($langcodes)) { 637 $this->queue = $langcodes; 638 } else if (!empty($langcodes)) { 639 $this->queue = array($langcodes); 640 } 641 } 642 643 /** 644 * Runs the installer 645 * 646 * This method calls {@link self::install_language_pack} for every language in the 647 * queue. If a dependency is detected, the parent language is added to the queue. 648 * 649 * @return array results, array of self::RESULT_xxx constants indexed by language code 650 */ 651 public function run() { 652 653 $results = array(); 654 655 while ($this->current = array_shift($this->queue)) { 656 657 if ($this->was_processed($this->current)) { 658 // do not repeat yourself 659 continue; 660 } 661 662 if ($this->current === 'en') { 663 $this->mark_processed($this->current); 664 continue; 665 } 666 667 $results[$this->current] = $this->install_language_pack($this->current); 668 669 if (in_array($results[$this->current], array(self::RESULT_INSTALLED, self::RESULT_UPTODATE))) { 670 if ($parentlang = $this->get_parent_language($this->current)) { 671 if (!$this->is_queued($parentlang) and !$this->was_processed($parentlang)) { 672 $this->add_to_queue($parentlang); 673 } 674 } 675 } 676 677 $this->mark_processed($this->current); 678 } 679 680 return $results; 681 } 682 683 /** 684 * Returns the URL where a given language pack can be downloaded 685 * 686 * Alternatively, if the parameter is empty, returns URL of the page with the 687 * list of all available language packs. 688 * 689 * @param string $langcode language code like 'cs' or empty for unknown 690 * @return string URL 691 */ 692 public function lang_pack_url($langcode = '') { 693 694 if (empty($langcode)) { 695 return 'https://download.moodle.org/langpack/'.$this->version.'/'; 696 } else { 697 return 'https://download.moodle.org/download.php/langpack/'.$this->version.'/'.$langcode.'.zip'; 698 } 699 } 700 701 /** 702 * Returns the list of available language packs from download.moodle.org 703 * 704 * @return array|bool false if can not download 705 */ 706 public function get_remote_list_of_languages() { 707 $source = 'https://download.moodle.org/langpack/' . $this->version . '/languages.md5'; 708 $availablelangs = array(); 709 710 $contents = download_file_content($source, null, null, true); 711 if ($contents->results && (int) $contents->status === 200) { 712 $alllines = explode("\n", $contents->results); 713 foreach($alllines as $line) { 714 if (!empty($line)){ 715 $availablelangs[] = explode(',', $line); 716 } 717 } 718 return $availablelangs; 719 720 } else { 721 return false; 722 } 723 } 724 725 // Internal implementation ///////////////////////////////////////////////// 726 727 /** 728 * Adds a language pack (or a list of them) to the queue 729 * 730 * @param string|array $langcodes code of the language to install or a list of them 731 */ 732 protected function add_to_queue($langcodes) { 733 if (is_array($langcodes)) { 734 $this->queue = array_merge($this->queue, $langcodes); 735 } else if (!empty($langcodes)) { 736 $this->queue[] = $langcodes; 737 } 738 } 739 740 /** 741 * Checks if the given language is queued or if the queue is empty 742 * 743 * @example $installer->is_queued('es'); // is Spanish going to be installed? 744 * @example $installer->is_queued(); // is there a language queued? 745 * 746 * @param string $langcode language code or empty string for "any" 747 * @return boolean 748 */ 749 protected function is_queued($langcode = '') { 750 751 if (empty($langcode)) { 752 return !empty($this->queue); 753 754 } else { 755 return in_array($langcode, $this->queue); 756 } 757 } 758 759 /** 760 * Checks if the given language has already been processed by this instance 761 * 762 * @see self::mark_processed() 763 * @param string $langcode 764 * @return boolean 765 */ 766 protected function was_processed($langcode) { 767 return isset($this->done[$langcode]); 768 } 769 770 /** 771 * Mark the given language pack as processed 772 * 773 * @see self::was_processed() 774 * @param string $langcode 775 */ 776 protected function mark_processed($langcode) { 777 $this->done[$langcode] = 1; 778 } 779 780 /** 781 * Returns a parent language of the given installed language 782 * 783 * @param string $langcode 784 * @return string parent language's code 785 */ 786 protected function get_parent_language($langcode) { 787 return get_parent_language($langcode); 788 } 789 790 /** 791 * Perform the actual language pack installation 792 * 793 * @uses component_installer 794 * @param string $langcode 795 * @return int return status 796 */ 797 protected function install_language_pack($langcode) { 798 799 // initialise new component installer to process this language 800 $installer = new component_installer('https://download.moodle.org', 'download.php/direct/langpack/' . $this->version, 801 $langcode . '.zip', 'languages.md5', 'lang'); 802 803 if (!$installer->requisitesok) { 804 throw new lang_installer_exception('installer_requisites_check_failed'); 805 } 806 807 $status = $installer->install(); 808 809 if ($status == COMPONENT_ERROR) { 810 if ($installer->get_error() === 'remotedownloaderror') { 811 return self::RESULT_DOWNLOADERROR; 812 } else { 813 throw new lang_installer_exception($installer->get_error(), $langcode); 814 } 815 816 } else if ($status == COMPONENT_UPTODATE) { 817 return self::RESULT_UPTODATE; 818 819 } else if ($status == COMPONENT_INSTALLED) { 820 return self::RESULT_INSTALLED; 821 822 } else { 823 throw new lang_installer_exception('unexpected_installer_result', $status); 824 } 825 } 826 } 827 828 829 /** 830 * Exception thrown by {@link lang_installer} 831 * 832 * @copyright 2011 David Mudrak <david@moodle.com> 833 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 834 */ 835 class lang_installer_exception extends moodle_exception { 836 837 public function __construct($errorcode, $debuginfo = null) { 838 parent::__construct($errorcode, 'error', '', null, $debuginfo); 839 } 840 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body