[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

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

   1  <?php  //$Id: componentlib.class.php,v 1.7.2.6 2008/04/02 06:10:01 dongsheng Exp $
   2  
   3  ///////////////////////////////////////////////////////////////////////////
   4  //                                                                       //
   5  // NOTICE OF COPYRIGHT                                                   //
   6  //                                                                       //
   7  // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
   8  //          http://moodle.com                                            //
   9  //                                                                       //
  10  // Copyright (C) 1999 onwards Martin Dougiamas     http://dougiamas.com  //
  11  //           (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com  //
  12  //                                                                       //
  13  // This program is free software; you can redistribute it and/or modify  //
  14  // it under the terms of the GNU General Public License as published by  //
  15  // the Free Software Foundation; either version 2 of the License, or     //
  16  // (at your option) any later version.                                   //
  17  //                                                                       //
  18  // This program is distributed in the hope that it will be useful,       //
  19  // but WITHOUT ANY WARRANTY; without even the implied warranty of        //
  20  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
  21  // GNU General Public License for more details:                          //
  22  //                                                                       //
  23  //          http://www.gnu.org/copyleft/gpl.html                         //
  24  //                                                                       //
  25  ///////////////////////////////////////////////////////////////////////////
  26  
  27  // This library includes all the necessary stuff to use the one-click
  28  // download and install feature of Moodle, used to keep updated some
  29  // items like languages, pear, enviroment... i.e, components.
  30  //
  31  // It has been developed harcoding some important limits that are
  32  // explained below:
  33  //    - It only can check, download and install items under moodledata.
  34  //    - Every downloadeable item must be one zip file.
  35  //    - The zip file root content must be 1 directory, i.e, everything
  36  //      is stored under 1 directory.
  37  //    - Zip file name and root directory must have the same name (but
  38  //      the .zip extension, of course).
  39  //    - Every .zip file must be defined in one .md5 file that will be
  40  //      stored in the same remote directory than the .zip file.
  41  //    - The name of such .md5 file is free, although it's recommended
  42  //      to use the same name than the .zip (that's the default
  43  //      assumption if no specified).
  44  //    - Every remote .md5 file will be a comma separated (CVS) file where each
  45  //      line will follow this format:
  46  //        - Field 1: name of the zip file (without extension). Mandatory.
  47  //        - Field 2: md5 of the zip file. Mandatory.
  48  //        - Field 3: whatever you want (or need). Optional.
  49  //    -Every local .md5 file will:
  50  //        - Have the zip file name (without the extension) plus -md5
  51  //        - Will reside inside the expanded zip file dir
  52  //        - Will contain the md5 od the latest installed component
  53  // With all these details present, the process will perform this tasks:
  54  //    - Perform security checks. Only admins are allowed to use this for now.
  55  //    - Read the .md5 file from source (1).
  56  //    - Extract the correct line for the .zip being requested.
  57  //    - Compare it with the local .md5 file (2).
  58  //    - If different:
  59  //        - Download the newer .zip file from source.
  60  //        - Calculate its md5 (3).
  61  //        - Compare (1) and (3).
  62  //        - If equal:
  63  //            - Delete old directory.
  64  //            - Uunzip the newer .zip file.
  65  //            - Create the new local .md5 file.
  66  //            - Delete the .zip file.
  67  //        - If different:
  68  //            - ERROR. Old package won't be modified. We shouldn't
  69  //              reach here ever.
  70  //    - If component download is not possible, a message text about how to do
  71  //      the process manually (remotedownloaderror) must be displayed to explain it.
  72  //
  73  // General Usage:
  74  //
  75  // To install one component:
  76  //
  77  //     require_once($CFG->libdir.'/componentlib.class.php');
  78  //     if ($cd = new component_installer('http://download.moodle.org', 'lang16',
  79  //                                       'es_utf8.zip', 'languages.md5', 'lang')) {
  80  //         $status = $cd->install(); //returns COMPONENT_(ERROR | UPTODATE | INSTALLED)
  81  //         switch ($status) {
  82  //             case COMPONENT_ERROR:
  83  //                 if ($cd->get_error() == 'remotedownloaderror') {
  84  //                     $a = new stdClass();
  85  //                     $a->url = 'http://download.moodle.org/lang16/es_utf8.zip';
  86  //                     $a->dest= $CFG->dataroot.'/lang';
  87  //                     print_error($cd->get_error(), 'error', '', $a);
  88  //                 } else {
  89  //                     print_error($cd->get_error(), 'error');
  90  //                 }
  91  //                 break;
  92  //             case COMPONENT_UPTODATE:
  93  //                 //Print error string or whatever you want to do
  94  //                 break;
  95  //             case COMPONENT_INSTALLED:
  96  //                 //Print/do whatever you want
  97  //                 break;
  98  //             default:
  99  //                 //We shouldn't reach this point
 100  //         }
 101  //     } else {
 102  //         //We shouldn't reach this point
 103  //     }
 104  //
 105  // To switch of component (maintaining the rest of settings):
 106  //
 107  //     $status = $cd->change_zip_file('en_utf8.zip'); //returns boolean false on error
 108  //
 109  // To retrieve all the components in one remote md5 file
 110  //
 111  //     $components = $cd->get_all_components_md5();  //returns boolean false on error, array instead
 112  //
 113  // To check if current component needs to be updated
 114  //
 115  //     $status = $cd->need_upgrade();  //returns COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
 116  //
 117  // To get the 3rd field of the md5 file (optional)
 118  //
 119  //     $field = $cd->get_extra_md5_field();  //returns string (empty if not exists)
 120  //
 121  // For all the error situations the $cd->get_error() method should return always the key of the
 122  // error to be retrieved by one standard get_string() call against the error.php lang file.
 123  //
 124  // That's all!
 125  global $CFG;
 126  require_once($CFG->libdir.'/filelib.php');
 127  
 128  // Some needed constants
 129  define('COMPONENT_ERROR',           0);
 130  define('COMPONENT_UPTODATE',        1);
 131  define('COMPONENT_NEEDUPDATE',      2);
 132  define('COMPONENT_INSTALLED',       3);
 133  
 134  /**
 135   * This class is used to check, download and install items from
 136   * download.moodle.org to the moodledata directory. It always
 137   * return true/false in all their public methods to say if
 138   * execution has ended succesfuly or not. If there is any problem
 139   * its getError() method can be called, returning one error string
 140   * to be used with the standard get/print_string() functions.
 141   */
 142  class component_installer {
 143  
 144      var $sourcebase;   /// Full http URL, base for downloadable items
 145      var $zippath;      /// Relative path (from sourcebase) where the
 146                         /// downloadeable item resides.
 147      var $zipfilename;  /// Name of the .zip file to be downloaded
 148      var $md5filename;  /// Name of the .md5 file to be read
 149      var $componentname;/// Name of the component. Must be the zip name without
 150                         /// the extension. And it defines a lot of things:
 151                         /// the md5 line to search for, the default m5 file name
 152                         /// and the name of the root dir stored inside the zip file
 153      var $destpath;     /// Relative path (from moodledata) where the .zip
 154                         /// file will be expanded.
 155      var $errorstring;  /// Latest error produced. It will contain one lang string key.
 156      var $extramd5info; /// Contents of the optional third field in the .md5 file.
 157      var $requisitesok; /// Flag to see if requisites check has been passed ok.
 158  
 159      var $cachedmd5components; /// Array of cached components to avoid to
 160                                /// download the same md5 file more than once per request.
 161  
 162      /**
 163       * Standard constructor of the class. It will initialize all attributes.
 164       * without performing any check at all.
 165       *
 166       * @param string Full http URL, base for downloadeable items
 167       * @param string Relative path (from sourcebase) where the
 168       *               downloadeable item resides
 169       * @param string Name of the .zip file to be downloaded
 170       * @param string Name of the .md5 file to be read (default '' = same
 171       *               than zipfilename)
 172       * @param string Relative path (from moodledata) where the .zip file will
 173       *               be expanded (default='' = moodledataitself)
 174       * @return object
 175       */
 176      function component_installer ($sourcebase, $zippath, $zipfilename, $md5filename='', $destpath='') {
 177  
 178          $this->sourcebase   = $sourcebase;
 179          $this->zippath      = $zippath;
 180          $this->zipfilename  = $zipfilename;
 181          $this->md5filename  = $md5filename;
 182          $this->componentname= '';
 183          $this->destpath     = $destpath;
 184          $this->errorstring  = '';
 185          $this->extramd5info = '';
 186          $this->requisitesok = false;
 187          $this->cachedmd5components = array();
 188  
 189          $this->check_requisites();
 190      }
 191  
 192      /**
 193       * This function will check if everything is properly set to begin
 194       * one installation. Also, it will check for required settings
 195       * and will fill everything as needed.
 196       *
 197       * @return boolean true/false (plus detailed error in errorstring)
 198       */
 199      function check_requisites() {
 200          global $CFG;
 201  
 202          $this->requisitesok = false;
 203  
 204      /// Check that everything we need is present
 205          if (empty($this->sourcebase) || empty($this->zippath) || empty($this->zipfilename)) {
 206              $this->errorstring='missingrequiredfield';
 207              return false;
 208          }
 209      /// Check for correct sourcebase (this will be out in the future)
 210          if ($this->sourcebase != 'http://download.moodle.org') {
 211              $this->errorstring='wrongsourcebase';
 212              return false;
 213          }
 214      /// Check the zip file is a correct one (by extension)
 215          if (stripos($this->zipfilename, '.zip') === false) {
 216              $this->errorstring='wrongzipfilename';
 217              return false;
 218          }
 219      /// Check that exists under dataroot
 220          if (!empty($this->destpath)) {
 221              if (!file_exists($CFG->dataroot.'/'.$this->destpath)) {
 222                  $this->errorstring='wrongdestpath';
 223                  return false;
 224              }
 225          }
 226      /// Calculate the componentname
 227          $pos = stripos($this->zipfilename, '.zip');
 228          $this->componentname = substr($this->zipfilename, 0, $pos);
 229      /// Calculate md5filename if it's empty
 230          if (empty($this->md5filename)) {
 231              $this->md5filename = $this->componentname.'.md5';
 232          }
 233      /// Set the requisites passed flag
 234          $this->requisitesok = true;
 235          return true;
 236      }
 237  
 238      /**
 239       * This function will perform the full installation if needed, i.e.
 240       * compare md5 values, download, unzip, install and regenerate
 241       * local md5 file
 242       *
 243       * @return int COMPONENT_(ERROR | UPTODATE | INSTALLED)
 244       */
 245      function install() {
 246  
 247          global $CFG;
 248  
 249      /// Check requisites are passed
 250          if (!$this->requisitesok) {
 251              return COMPONENT_ERROR;
 252          }
 253      /// Confirm we need upgrade
 254          if ($this->need_upgrade() === COMPONENT_ERROR) {
 255              return COMPONENT_ERROR;
 256          } else if ($this->need_upgrade() === COMPONENT_UPTODATE) {
 257              $this->errorstring='componentisuptodate';
 258              return COMPONENT_UPTODATE;
 259          }
 260      /// Create temp directory if necesary
 261          if (!make_upload_directory('temp', false)) {
 262               $this->errorstring='cannotcreatetempdir';
 263               return COMPONENT_ERROR;
 264          }
 265      /// Download zip file and save it to temp
 266          $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->zipfilename;
 267          $zipfile= $CFG->dataroot.'/temp/'.$this->zipfilename;
 268  
 269          if($contents = download_file_content($source)) {
 270              if ($file = fopen($zipfile, 'w')) {
 271                  if (!fwrite($file, $contents)) {
 272                      fclose($file);
 273                      $this->errorstring='cannotsavezipfile';
 274                      return COMPONENT_ERROR;
 275                  }
 276              } else {
 277                  $this->errorstring='cannotsavezipfile';
 278                  return COMPONENT_ERROR;
 279              }
 280              fclose($file);
 281          } else {
 282              $this->errorstring='cannotdownloadzipfile';
 283              return COMPONENT_ERROR;
 284          }
 285      /// Calculate its md5
 286          $new_md5 = md5($contents);
 287      /// Compare it with the remote md5 to check if we have the correct zip file
 288          if (!$remote_md5 = $this->get_component_md5()) {
 289              return COMPONENT_ERROR;
 290          }
 291          if ($new_md5 != $remote_md5) {
 292              $this->errorstring='downloadedfilecheckfailed';
 293              return COMPONENT_ERROR;
 294          }
 295      /// Move current revision to a safe place
 296          $destinationdir = $CFG->dataroot.'/'.$this->destpath;
 297          $destinationcomponent = $destinationdir.'/'.$this->componentname;
 298          @remove_dir($destinationcomponent.'_old');     //Deleting possible old components before
 299          @rename ($destinationcomponent, $destinationcomponent.'_old');  //Moving to a safe place
 300      /// Unzip new version
 301          if (!unzip_file($zipfile, $destinationdir, false)) {
 302          /// Error so, go back to the older
 303              @remove_dir($destinationcomponent);
 304              @rename ($destinationcomponent.'_old', $destinationcomponent);
 305              $this->errorstring='cannotunzipfile';
 306              return COMPONENT_ERROR;
 307          }
 308      /// Delete old component version
 309          @remove_dir($destinationcomponent.'_old');
 310      /// Create local md5
 311          if ($file = fopen($destinationcomponent.'/'.$this->componentname.'.md5', 'w')) {
 312              if (!fwrite($file, $new_md5)) {
 313                  fclose($file);
 314                  $this->errorstring='cannotsavemd5file';
 315                  return COMPONENT_ERROR;
 316              }
 317          } else  {
 318              $this->errorstring='cannotsavemd5file';
 319              return COMPONENT_ERROR;
 320          }
 321          fclose($file);
 322      /// Delete temp zip file
 323          @unlink($zipfile);
 324  
 325          return COMPONENT_INSTALLED;
 326      }
 327  
 328      /**
 329       * This function will detect if remote component needs to be installed
 330       * because it's different from the local one
 331       *
 332       * @return int COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
 333       */
 334      function need_upgrade() {
 335  
 336      /// Check requisites are passed
 337          if (!$this->requisitesok) {
 338              return COMPONENT_ERROR;
 339          }
 340      /// Get local md5
 341          $local_md5 = $this->get_local_md5();
 342      /// Get remote md5
 343          if (!$remote_md5 = $this->get_component_md5()) {
 344              return COMPONENT_ERROR;
 345          }
 346      /// Return result
 347         if ($local_md5 == $remote_md5) {
 348             return COMPONENT_UPTODATE;
 349         } else {
 350             return COMPONENT_NEEDUPDATE;
 351         }
 352      }
 353  
 354      /**
 355       * This function will change the zip file to install on the fly
 356       * to allow the class to process different components of the
 357       * same md5 file without intantiating more objects.
 358       *
 359       * @param string New zip filename to process
 360       * @return boolean true/false
 361       */
 362      function change_zip_file($newzipfilename) {
 363  
 364          $this->zipfilename = $newzipfilename;
 365          return $this->check_requisites();
 366      }
 367  
 368      /**
 369       * This function will get the local md5 value of the installed
 370       * component.
 371       *
 372       * @return string md5 of the local component (false on error)
 373       */
 374      function get_local_md5() {
 375          global $CFG;
 376  
 377      /// Check requisites are passed
 378          if (!$this->requisitesok) {
 379              return false;
 380          }
 381  
 382          $return_value = 'needtobeinstalled';   /// Fake value to force new installation
 383  
 384      /// Calculate source to read
 385         $source = $CFG->dataroot.'/'.$this->destpath.'/'.$this->componentname.'/'.$this->componentname.'.md5';
 386      /// Read md5 value stored (if exists)
 387         if (file_exists($source)) {
 388             if ($temp = file_get_contents($source)) {
 389                 $return_value = $temp;
 390             }
 391          }
 392          return $return_value;
 393      }
 394  
 395      /**
 396       * This function will download the specified md5 file, looking for the
 397       * current componentname, returning its md5 field and storing extramd5info
 398       * if present. Also it caches results to cachedmd5components for better
 399       * performance in the same request.
 400       *
 401       * @return mixed md5 present in server (or false if error)
 402       */
 403      function get_component_md5() {
 404  
 405      /// Check requisites are passed
 406          if (!$this->requisitesok) {
 407              return false;
 408          }
 409      /// Get all components of md5 file
 410          if (!$comp_arr = $this->get_all_components_md5()) {
 411              if (empty($this->errorstring)) {
 412                  $this->errorstring='cannotdownloadcomponents';
 413              }
 414              return false;
 415          }
 416      /// Search for the componentname component
 417          if (empty($comp_arr[$this->componentname]) || !$component = $comp_arr[$this->componentname]) {
 418               $this->errorstring='cannotfindcomponent';
 419               return false;
 420          }
 421      /// Check we have a valid md5
 422          if (empty($component[1]) || strlen($component[1]) != 32) {
 423              $this->errorstring='invalidmd5';
 424              return false;
 425          }
 426      /// Set the extramd5info field
 427          if (!empty($component[2])) {
 428              $this->extramd5info = $component[2];
 429          }
 430          return $component[1];
 431      }
 432  
 433      /**
 434       * This function allows you to retrieve the complete array of components found in
 435       * the md5filename
 436       *
 437       * @return array array of components in md5 file or false if error
 438       */
 439      function get_all_components_md5() {
 440  
 441      /// Check requisites are passed
 442          if (!$this->requisitesok) {
 443              return false;
 444          }
 445  
 446      /// Initialize components array
 447          $comp_arr = array();
 448  
 449      /// Define and retrieve the full md5 file
 450          $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->md5filename;
 451  
 452      /// Check if we have downloaded the md5 file before (per request cache)
 453          if (!empty($this->cachedmd5components[$source])) {
 454              $comp_arr = $this->cachedmd5components[$source];
 455          } else {
 456          /// Not downloaded, let's do it now
 457              $availablecomponents = array();
 458  
 459              if ($contents = download_file_content($source)) {
 460              /// Split text into lines
 461                  $lines=preg_split('/\r?\n/',$contents);
 462              /// Each line will be one component
 463                  foreach($lines as $line) {
 464                      $availablecomponents[] = split(',', $line);
 465                  }
 466              /// If no components have been found, return error
 467                  if (empty($availablecomponents)) {
 468                      $this->errorstring='cannotdownloadcomponents';
 469                      return false;
 470                  }
 471              /// Build an associative array of components for easily search
 472              /// applying trim to avoid linefeeds and other...
 473                  $comp_arr = array();
 474                  foreach ($availablecomponents as $component) {
 475                  /// Avoid sometimes empty lines
 476                      if (empty($component[0])) {
 477                          continue;
 478                      }
 479                      $component[0]=trim($component[0]);
 480                      $component[1]=trim($component[1]);
 481                      if (!empty($component[2])) {
 482                          $component[2]=trim($component[2]);
 483                      }
 484                      $comp_arr[$component[0]] = $component;
 485                  }
 486              /// Cache components
 487                  $this->cachedmd5components[$source] = $comp_arr;
 488              } else {
 489              /// Return error
 490                  $this->errorstring='remotedownloaderror';
 491                  return false;
 492              }
 493          }
 494      /// If there is no commponents or erros found, error
 495          if (!empty($this->errorstring)) {
 496               return false;
 497  
 498          } else if (empty($comp_arr)) {
 499               $this->errorstring='cannotdownloadcomponents';
 500               return false;
 501          }
 502          return $comp_arr;
 503      }
 504  
 505      /**
 506       * This function returns the errorstring
 507       *
 508       * @return string the error string
 509       */
 510      function get_error() {
 511          return $this->errorstring;
 512      }
 513  
 514      /** This function returns the extramd5 field (optional in md5 file)
 515       *
 516       * @return string the extramd5 field
 517       */
 518      function get_extra_md5_field() {
 519          return $this->extramd5info;
 520      }
 521  
 522  } /// End of component_installer class
 523  
 524  ?>


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7