[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> componentlib.class.php (source)

   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   *                     print_error($cd->get_error(), 'error', '', $a);
  80   *                 } else {
  81   *                     print_error($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      function component_installer ($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       * This function will check if everything is properly set to begin
 211       * one installation. Also, it will check for required settings
 212       * and will fill everything as needed.
 213       *
 214       * @global object
 215       * @return boolean true/false (plus detailed error in errorstring)
 216       */
 217      function check_requisites() {
 218          global $CFG;
 219  
 220          $this->requisitesok = false;
 221  
 222      /// Check that everything we need is present
 223          if (empty($this->sourcebase) || empty($this->zipfilename)) {
 224              $this->errorstring='missingrequiredfield';
 225              return false;
 226          }
 227      /// Check for correct sourcebase (this will be out in the future)
 228          if (!PHPUNIT_TEST and $this->sourcebase != 'https://download.moodle.org') {
 229              $this->errorstring='wrongsourcebase';
 230              return false;
 231          }
 232      /// Check the zip file is a correct one (by extension)
 233          if (stripos($this->zipfilename, '.zip') === false) {
 234              $this->errorstring='wrongzipfilename';
 235              return false;
 236          }
 237      /// Check that exists under dataroot
 238          if (!empty($this->destpath)) {
 239              if (!file_exists($CFG->dataroot.'/'.$this->destpath)) {
 240                  $this->errorstring='wrongdestpath';
 241                  return false;
 242              }
 243          }
 244      /// Calculate the componentname
 245          $pos = stripos($this->zipfilename, '.zip');
 246          $this->componentname = substr($this->zipfilename, 0, $pos);
 247      /// Calculate md5filename if it's empty
 248          if (empty($this->md5filename)) {
 249              $this->md5filename = $this->componentname.'.md5';
 250          }
 251      /// Set the requisites passed flag
 252          $this->requisitesok = true;
 253          return true;
 254      }
 255  
 256      /**
 257       * This function will perform the full installation if needed, i.e.
 258       * compare md5 values, download, unzip, install and regenerate
 259       * local md5 file
 260       *
 261       * @global object
 262       * @uses COMPONENT_ERROR
 263       * @uses COMPONENT_UPTODATE
 264       * @uses COMPONENT_ERROR
 265       * @uses COMPONENT_INSTALLED
 266       * @return int COMPONENT_(ERROR | UPTODATE | INSTALLED)
 267       */
 268      function install() {
 269  
 270          global $CFG;
 271  
 272      /// Check requisites are passed
 273          if (!$this->requisitesok) {
 274              return COMPONENT_ERROR;
 275          }
 276      /// Confirm we need upgrade
 277          if ($this->need_upgrade() === COMPONENT_ERROR) {
 278              return COMPONENT_ERROR;
 279          } else if ($this->need_upgrade() === COMPONENT_UPTODATE) {
 280              $this->errorstring='componentisuptodate';
 281              return COMPONENT_UPTODATE;
 282          }
 283      /// Create temp directory if necesary
 284          if (!make_temp_directory('', false)) {
 285               $this->errorstring='cannotcreatetempdir';
 286               return COMPONENT_ERROR;
 287          }
 288      /// Download zip file and save it to temp
 289          if ($this->zippath) {
 290              $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->zipfilename;
 291          } else {
 292              $source = $this->sourcebase.'/'.$this->zipfilename;
 293          }
 294  
 295          $zipfile= $CFG->tempdir.'/'.$this->zipfilename;
 296  
 297          if($contents = download_file_content($source)) {
 298              if ($file = fopen($zipfile, 'w')) {
 299                  if (!fwrite($file, $contents)) {
 300                      fclose($file);
 301                      $this->errorstring='cannotsavezipfile';
 302                      return COMPONENT_ERROR;
 303                  }
 304              } else {
 305                  $this->errorstring='cannotsavezipfile';
 306                  return COMPONENT_ERROR;
 307              }
 308              fclose($file);
 309          } else {
 310              $this->errorstring='cannotdownloadzipfile';
 311              return COMPONENT_ERROR;
 312          }
 313      /// Calculate its md5
 314          $new_md5 = md5($contents);
 315      /// Compare it with the remote md5 to check if we have the correct zip file
 316          if (!$remote_md5 = $this->get_component_md5()) {
 317              return COMPONENT_ERROR;
 318          }
 319          if ($new_md5 != $remote_md5) {
 320              $this->errorstring='downloadedfilecheckfailed';
 321              return COMPONENT_ERROR;
 322          }
 323      /// Move current revision to a safe place
 324          $destinationdir = $CFG->dataroot.'/'.$this->destpath;
 325          $destinationcomponent = $destinationdir.'/'.$this->componentname;
 326          @remove_dir($destinationcomponent.'_old');     //Deleting possible old components before
 327          @rename ($destinationcomponent, $destinationcomponent.'_old');  //Moving to a safe place
 328      /// Unzip new version
 329          if (!unzip_file($zipfile, $destinationdir, false)) {
 330          /// Error so, go back to the older
 331              @remove_dir($destinationcomponent);
 332              @rename ($destinationcomponent.'_old', $destinationcomponent);
 333              $this->errorstring='cannotunzipfile';
 334              return COMPONENT_ERROR;
 335          }
 336      /// Delete old component version
 337          @remove_dir($destinationcomponent.'_old');
 338      /// Create local md5
 339          if ($file = fopen($destinationcomponent.'/'.$this->componentname.'.md5', 'w')) {
 340              if (!fwrite($file, $new_md5)) {
 341                  fclose($file);
 342                  $this->errorstring='cannotsavemd5file';
 343                  return COMPONENT_ERROR;
 344              }
 345          } else  {
 346              $this->errorstring='cannotsavemd5file';
 347              return COMPONENT_ERROR;
 348          }
 349          fclose($file);
 350      /// Delete temp zip file
 351          @unlink($zipfile);
 352  
 353          return COMPONENT_INSTALLED;
 354      }
 355  
 356      /**
 357       * This function will detect if remote component needs to be installed
 358       * because it's different from the local one
 359       *
 360       * @uses COMPONENT_ERROR
 361       * @uses COMPONENT_UPTODATE
 362       * @uses COMPONENT_NEEDUPDATE
 363       * @return int COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
 364       */
 365      function need_upgrade() {
 366  
 367      /// Check requisites are passed
 368          if (!$this->requisitesok) {
 369              return COMPONENT_ERROR;
 370          }
 371      /// Get local md5
 372          $local_md5 = $this->get_local_md5();
 373      /// Get remote md5
 374          if (!$remote_md5 = $this->get_component_md5()) {
 375              return COMPONENT_ERROR;
 376          }
 377      /// Return result
 378         if ($local_md5 == $remote_md5) {
 379             return COMPONENT_UPTODATE;
 380         } else {
 381             return COMPONENT_NEEDUPDATE;
 382         }
 383      }
 384  
 385      /**
 386       * This function will change the zip file to install on the fly
 387       * to allow the class to process different components of the
 388       * same md5 file without intantiating more objects.
 389       *
 390       * @param string $newzipfilename New zip filename to process
 391       * @return boolean true/false
 392       */
 393      function change_zip_file($newzipfilename) {
 394  
 395          $this->zipfilename = $newzipfilename;
 396          return $this->check_requisites();
 397      }
 398  
 399      /**
 400       * This function will get the local md5 value of the installed
 401       * component.
 402       *
 403       * @global object
 404       * @return bool|string md5 of the local component (false on error)
 405       */
 406      function get_local_md5() {
 407          global $CFG;
 408  
 409      /// Check requisites are passed
 410          if (!$this->requisitesok) {
 411              return false;
 412          }
 413  
 414          $return_value = 'needtobeinstalled';   /// Fake value to force new installation
 415  
 416      /// Calculate source to read
 417         $source = $CFG->dataroot.'/'.$this->destpath.'/'.$this->componentname.'/'.$this->componentname.'.md5';
 418      /// Read md5 value stored (if exists)
 419         if (file_exists($source)) {
 420             if ($temp = file_get_contents($source)) {
 421                 $return_value = $temp;
 422             }
 423          }
 424          return $return_value;
 425      }
 426  
 427      /**
 428       * This function will download the specified md5 file, looking for the
 429       * current componentname, returning its md5 field and storing extramd5info
 430       * if present. Also it caches results to cachedmd5components for better
 431       * performance in the same request.
 432       *
 433       * @return mixed md5 present in server (or false if error)
 434       */
 435      function get_component_md5() {
 436  
 437      /// Check requisites are passed
 438          if (!$this->requisitesok) {
 439              return false;
 440          }
 441      /// Get all components of md5 file
 442          if (!$comp_arr = $this->get_all_components_md5()) {
 443              if (empty($this->errorstring)) {
 444                  $this->errorstring='cannotdownloadcomponents';
 445              }
 446              return false;
 447          }
 448      /// Search for the componentname component
 449          if (empty($comp_arr[$this->componentname]) || !$component = $comp_arr[$this->componentname]) {
 450               $this->errorstring='cannotfindcomponent';
 451               return false;
 452          }
 453      /// Check we have a valid md5
 454          if (empty($component[1]) || strlen($component[1]) != 32) {
 455              $this->errorstring='invalidmd5';
 456              return false;
 457          }
 458      /// Set the extramd5info field
 459          if (!empty($component[2])) {
 460              $this->extramd5info = $component[2];
 461          }
 462          return $component[1];
 463      }
 464  
 465      /**
 466       * This function allows you to retrieve the complete array of components found in
 467       * the md5filename
 468       *
 469       * @return bool|array array of components in md5 file or false if error
 470       */
 471      function get_all_components_md5() {
 472  
 473      /// Check requisites are passed
 474          if (!$this->requisitesok) {
 475              return false;
 476          }
 477  
 478      /// Initialize components array
 479          $comp_arr = array();
 480  
 481      /// Define and retrieve the full md5 file
 482          if ($this->zippath) {
 483              $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->md5filename;
 484          } else {
 485              $source = $this->sourcebase.'/'.$this->md5filename;
 486          }
 487  
 488      /// Check if we have downloaded the md5 file before (per request cache)
 489          if (!empty($this->cachedmd5components[$source])) {
 490              $comp_arr = $this->cachedmd5components[$source];
 491          } else {
 492          /// Not downloaded, let's do it now
 493              $availablecomponents = array();
 494  
 495              if ($contents = download_file_content($source)) {
 496              /// Split text into lines
 497                  $lines=preg_split('/\r?\n/',$contents);
 498              /// Each line will be one component
 499                  foreach($lines as $line) {
 500                      $availablecomponents[] = explode(',', $line);
 501                  }
 502              /// If no components have been found, return error
 503                  if (empty($availablecomponents)) {
 504                      $this->errorstring='cannotdownloadcomponents';
 505                      return false;
 506                  }
 507              /// Build an associative array of components for easily search
 508              /// applying trim to avoid linefeeds and other...
 509                  $comp_arr = array();
 510                  foreach ($availablecomponents as $component) {
 511                  /// Avoid sometimes empty lines
 512                      if (empty($component[0])) {
 513                          continue;
 514                      }
 515                      $component[0]=trim($component[0]);
 516                      if (!empty($component[1])) {
 517                          $component[1]=trim($component[1]);
 518                      }
 519                      if (!empty($component[2])) {
 520                          $component[2]=trim($component[2]);
 521                      }
 522                      $comp_arr[$component[0]] = $component;
 523                  }
 524              /// Cache components
 525                  $this->cachedmd5components[$source] = $comp_arr;
 526              } else {
 527              /// Return error
 528                  $this->errorstring='remotedownloaderror';
 529                  return false;
 530              }
 531          }
 532      /// If there is no commponents or erros found, error
 533          if (!empty($this->errorstring)) {
 534               return false;
 535  
 536          } else if (empty($comp_arr)) {
 537               $this->errorstring='cannotdownloadcomponents';
 538               return false;
 539          }
 540          return $comp_arr;
 541      }
 542  
 543      /**
 544       * This function returns the errorstring
 545       *
 546       * @return string the error string
 547       */
 548      function get_error() {
 549          return $this->errorstring;
 550      }
 551  
 552      /** This function returns the extramd5 field (optional in md5 file)
 553       *
 554       * @return string the extramd5 field
 555       */
 556      function get_extra_md5_field() {
 557          return $this->extramd5info;
 558      }
 559  
 560  } /// End of component_installer class
 561  
 562  
 563  /**
 564   * Language packs installer
 565   *
 566   * This class wraps the functionality provided by {@link component_installer}
 567   * and adds support for installing a set of language packs.
 568   *
 569   * Given an array of required language packs, this class fetches them all
 570   * and installs them. It detects eventual dependencies and installs
 571   * all parent languages, too.
 572   *
 573   * @copyright 2011 David Mudrak <[email protected]>
 574   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 575   */
 576  class lang_installer {
 577  
 578      /** lang pack was successfully downloaded and deployed */
 579      const RESULT_INSTALLED      = 'installed';
 580      /** lang pack was up-to-date so no download was needed */
 581      const RESULT_UPTODATE       = 'uptodate';
 582      /** there was a problem with downloading the lang pack */
 583      const RESULT_DOWNLOADERROR  = 'downloaderror';
 584  
 585      /** @var array of languages to install */
 586      protected $queue = array();
 587      /** @var string the code of language being currently installed */
 588      protected $current;
 589      /** @var array of languages already installed by this instance */
 590      protected $done = array();
 591      /** @var string this Moodle major version */
 592      protected $version;
 593  
 594      /**
 595       * Prepare the installer
 596       *
 597       * @param string|array $langcode a code of the language to install
 598       */
 599      public function __construct($langcode = '') {
 600          global $CFG;
 601  
 602          $this->set_queue($langcode);
 603          $this->version = moodle_major_version(true);
 604  
 605          if (!empty($CFG->langotherroot) and $CFG->langotherroot !== $CFG->dataroot . '/lang') {
 606              debugging('The in-built language pack installer does not support alternative location ' .
 607                  'of languages root directory. You are supposed to install and update your language '.
 608                  'packs on your own.');
 609          }
 610      }
 611  
 612      /**
 613       * Sets the queue of language packs to be installed
 614       *
 615       * @param string|array $langcodes language code like 'cs' or a list of them
 616       */
 617      public function set_queue($langcodes) {
 618          if (is_array($langcodes)) {
 619              $this->queue = $langcodes;
 620          } else if (!empty($langcodes)) {
 621              $this->queue = array($langcodes);
 622          }
 623      }
 624  
 625      /**
 626       * Runs the installer
 627       *
 628       * This method calls {@link self::install_language_pack} for every language in the
 629       * queue. If a dependency is detected, the parent language is added to the queue.
 630       *
 631       * @return array results, array of self::RESULT_xxx constants indexed by language code
 632       */
 633      public function run() {
 634  
 635          $results = array();
 636  
 637          while ($this->current = array_shift($this->queue)) {
 638  
 639              if ($this->was_processed($this->current)) {
 640                  // do not repeat yourself
 641                  continue;
 642              }
 643  
 644              if ($this->current === 'en') {
 645                  $this->mark_processed($this->current);
 646                  continue;
 647              }
 648  
 649              $results[$this->current] = $this->install_language_pack($this->current);
 650  
 651              if (in_array($results[$this->current], array(self::RESULT_INSTALLED, self::RESULT_UPTODATE))) {
 652                  if ($parentlang = $this->get_parent_language($this->current)) {
 653                      if (!$this->is_queued($parentlang) and !$this->was_processed($parentlang)) {
 654                          $this->add_to_queue($parentlang);
 655                      }
 656                  }
 657              }
 658  
 659              $this->mark_processed($this->current);
 660          }
 661  
 662          return $results;
 663      }
 664  
 665      /**
 666       * Returns the URL where a given language pack can be downloaded
 667       *
 668       * Alternatively, if the parameter is empty, returns URL of the page with the
 669       * list of all available language packs.
 670       *
 671       * @param string $langcode language code like 'cs' or empty for unknown
 672       * @return string URL
 673       */
 674      public function lang_pack_url($langcode = '') {
 675  
 676          if (empty($langcode)) {
 677              return 'https://download.moodle.org/langpack/'.$this->version.'/';
 678          } else {
 679              return 'https://download.moodle.org/download.php/langpack/'.$this->version.'/'.$langcode.'.zip';
 680          }
 681      }
 682  
 683      /**
 684       * Returns the list of available language packs from download.moodle.org
 685       *
 686       * @return array|bool false if can not download
 687       */
 688      public function get_remote_list_of_languages() {
 689          $source = 'https://download.moodle.org/langpack/' . $this->version . '/languages.md5';
 690          $availablelangs = array();
 691  
 692          if ($content = download_file_content($source)) {
 693              $alllines = explode("\n", $content);
 694              foreach($alllines as $line) {
 695                  if (!empty($line)){
 696                      $availablelangs[] = explode(',', $line);
 697                  }
 698              }
 699              return $availablelangs;
 700  
 701          } else {
 702              return false;
 703          }
 704      }
 705  
 706      // Internal implementation /////////////////////////////////////////////////
 707  
 708      /**
 709       * Adds a language pack (or a list of them) to the queue
 710       *
 711       * @param string|array $langcodes code of the language to install or a list of them
 712       */
 713      protected function add_to_queue($langcodes) {
 714          if (is_array($langcodes)) {
 715              $this->queue = array_merge($this->queue, $langcodes);
 716          } else if (!empty($langcodes)) {
 717              $this->queue[] = $langcodes;
 718          }
 719      }
 720  
 721      /**
 722       * Checks if the given language is queued or if the queue is empty
 723       *
 724       * @example $installer->is_queued('es');    // is Spanish going to be installed?
 725       * @example $installer->is_queued();        // is there a language queued?
 726       *
 727       * @param string $langcode language code or empty string for "any"
 728       * @return boolean
 729       */
 730      protected function is_queued($langcode = '') {
 731  
 732          if (empty($langcode)) {
 733              return !empty($this->queue);
 734  
 735          } else {
 736              return in_array($langcode, $this->queue);
 737          }
 738      }
 739  
 740      /**
 741       * Checks if the given language has already been processed by this instance
 742       *
 743       * @see self::mark_processed()
 744       * @param string $langcode
 745       * @return boolean
 746       */
 747      protected function was_processed($langcode) {
 748          return isset($this->done[$langcode]);
 749      }
 750  
 751      /**
 752       * Mark the given language pack as processed
 753       *
 754       * @see self::was_processed()
 755       * @param string $langcode
 756       */
 757      protected function mark_processed($langcode) {
 758          $this->done[$langcode] = 1;
 759      }
 760  
 761      /**
 762       * Returns a parent language of the given installed language
 763       *
 764       * @param string $langcode
 765       * @return string parent language's code
 766       */
 767      protected function get_parent_language($langcode) {
 768          return get_parent_language($langcode);
 769      }
 770  
 771      /**
 772       * Perform the actual language pack installation
 773       *
 774       * @uses component_installer
 775       * @param string $langcode
 776       * @return int return status
 777       */
 778      protected function install_language_pack($langcode) {
 779  
 780          // initialise new component installer to process this language
 781          $installer = new component_installer('https://download.moodle.org', 'download.php/direct/langpack/' . $this->version,
 782              $langcode . '.zip', 'languages.md5', 'lang');
 783  
 784          if (!$installer->requisitesok) {
 785              throw new lang_installer_exception('installer_requisites_check_failed');
 786          }
 787  
 788          $status = $installer->install();
 789  
 790          if ($status == COMPONENT_ERROR) {
 791              if ($installer->get_error() === 'remotedownloaderror') {
 792                  return self::RESULT_DOWNLOADERROR;
 793              } else {
 794                  throw new lang_installer_exception($installer->get_error(), $langcode);
 795              }
 796  
 797          } else if ($status == COMPONENT_UPTODATE) {
 798              return self::RESULT_UPTODATE;
 799  
 800          } else if ($status == COMPONENT_INSTALLED) {
 801              return self::RESULT_INSTALLED;
 802  
 803          } else {
 804              throw new lang_installer_exception('unexpected_installer_result', $status);
 805          }
 806      }
 807  }
 808  
 809  
 810  /**
 811   * Exception thrown by {@link lang_installer}
 812   *
 813   * @copyright 2011 David Mudrak <[email protected]>
 814   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 815   */
 816  class lang_installer_exception extends moodle_exception {
 817  
 818      public function __construct($errorcode, $debuginfo = null) {
 819          parent::__construct($errorcode, 'error', '', null, $debuginfo);
 820      }
 821  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1