"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/lib/Drupal/Core/Updater/Updater.php" (26 Nov 2020, 13285 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Updater.php" see the Fossies "Dox" file reference documentation.

    1 <?php
    2 
    3 namespace Drupal\Core\Updater;
    4 
    5 use Drupal\Core\FileTransfer\FileTransferException;
    6 use Drupal\Core\FileTransfer\FileTransfer;
    7 
    8 /**
    9  * Defines the base class for Updaters used in Drupal.
   10  */
   11 class Updater {
   12 
   13   /**
   14    * Directory to install from.
   15    *
   16    * @var string
   17    */
   18   public $source;
   19 
   20   /**
   21    * The root directory under which new projects will be copied.
   22    *
   23    * @var string
   24    */
   25   protected $root;
   26 
   27   /**
   28    * Constructs a new updater.
   29    *
   30    * @param string $source
   31    *   Directory to install from.
   32    * @param string $root
   33    *   The root directory under which the project will be copied to if it's a
   34    *   new project. Usually this is the app root (the directory in which the
   35    *   Drupal site is installed).
   36    */
   37   public function __construct($source, $root) {
   38     $this->source = $source;
   39     $this->root = $root;
   40     $this->name = self::getProjectName($source);
   41     $this->title = self::getProjectTitle($source);
   42   }
   43 
   44   /**
   45    * Returns an Updater of the appropriate type depending on the source.
   46    *
   47    * If a directory is provided which contains a module, will return a
   48    * ModuleUpdater.
   49    *
   50    * @param string $source
   51    *   Directory of a Drupal project.
   52    * @param string $root
   53    *   The root directory under which the project will be copied to if it's a
   54    *   new project. Usually this is the app root (the directory in which the
   55    *   Drupal site is installed).
   56    *
   57    * @return \Drupal\Core\Updater\Updater
   58    *   A new Drupal\Core\Updater\Updater object.
   59    *
   60    * @throws \Drupal\Core\Updater\UpdaterException
   61    */
   62   public static function factory($source, $root) {
   63     if (is_dir($source)) {
   64       $updater = self::getUpdaterFromDirectory($source);
   65     }
   66     else {
   67       throw new UpdaterException('Unable to determine the type of the source directory.');
   68     }
   69     return new $updater($source, $root);
   70   }
   71 
   72   /**
   73    * Determines which Updater class can operate on the given directory.
   74    *
   75    * @param string $directory
   76    *   Extracted Drupal project.
   77    *
   78    * @return string
   79    *   The class name which can work with this project type.
   80    *
   81    * @throws \Drupal\Core\Updater\UpdaterException
   82    */
   83   public static function getUpdaterFromDirectory($directory) {
   84     // Gets a list of possible implementing classes.
   85     $updaters = drupal_get_updaters();
   86     foreach ($updaters as $updater) {
   87       $class = $updater['class'];
   88       if (call_user_func([$class, 'canUpdateDirectory'], $directory)) {
   89         return $class;
   90       }
   91     }
   92     throw new UpdaterException('Cannot determine the type of project.');
   93   }
   94 
   95   /**
   96    * Determines what the most important (or only) info file is in a directory.
   97    *
   98    * Since there is no enforcement of which info file is the project's "main"
   99    * info file, this will get one with the same name as the directory, or the
  100    * first one it finds.  Not ideal, but needs a larger solution.
  101    *
  102    * @param string $directory
  103    *   Directory to search in.
  104    *
  105    * @return string
  106    *   Path to the info file.
  107    */
  108   public static function findInfoFile($directory) {
  109     /** @var \Drupal\Core\File\FileSystemInterface $file_system */
  110     $file_system = \Drupal::service('file_system');
  111     $info_files = [];
  112     if (is_dir($directory)) {
  113       $info_files = $file_system->scanDirectory($directory, '/.*\.info.yml$/');
  114     }
  115     if (!$info_files) {
  116       return FALSE;
  117     }
  118     foreach ($info_files as $info_file) {
  119       if (mb_substr($info_file->filename, 0, -9) == $file_system->basename($directory)) {
  120         // Info file Has the same name as the directory, return it.
  121         return $info_file->uri;
  122       }
  123     }
  124     // Otherwise, return the first one.
  125     $info_file = array_shift($info_files);
  126     return $info_file->uri;
  127   }
  128 
  129   /**
  130    * Get Extension information from directory.
  131    *
  132    * @param string $directory
  133    *   Directory to search in.
  134    *
  135    * @return array
  136    *   Extension info.
  137    *
  138    * @throws \Drupal\Core\Updater\UpdaterException
  139    *   If the info parser does not provide any info.
  140    */
  141   protected static function getExtensionInfo($directory) {
  142     $info_file = static::findInfoFile($directory);
  143     $info = \Drupal::service('info_parser')->parse($info_file);
  144     if (empty($info)) {
  145       throw new UpdaterException("Unable to parse info file: '$info_file'.");
  146     }
  147 
  148     return $info;
  149   }
  150 
  151   /**
  152    * Gets the name of the project directory (basename).
  153    *
  154    * @todo It would be nice, if projects contained an info file which could
  155    *   provide their canonical name.
  156    *
  157    * @param string $directory
  158    *
  159    * @return string
  160    *   The name of the project.
  161    */
  162   public static function getProjectName($directory) {
  163     return \Drupal::service('file_system')->basename($directory);
  164   }
  165 
  166   /**
  167    * Returns the project name from a Drupal info file.
  168    *
  169    * @param string $directory
  170    *   Directory to search for the info file.
  171    *
  172    * @return string
  173    *   The title of the project.
  174    *
  175    * @throws \Drupal\Core\Updater\UpdaterException
  176    */
  177   public static function getProjectTitle($directory) {
  178     $info_file = self::findInfoFile($directory);
  179     $info = \Drupal::service('info_parser')->parse($info_file);
  180     if (empty($info)) {
  181       throw new UpdaterException("Unable to parse info file: '$info_file'.");
  182     }
  183     return $info['name'];
  184   }
  185 
  186   /**
  187    * Stores the default parameters for the Updater.
  188    *
  189    * @param array $overrides
  190    *   An array of overrides for the default parameters.
  191    *
  192    * @return array
  193    *   An array of configuration parameters for an update or install operation.
  194    */
  195   protected function getInstallArgs($overrides = []) {
  196     $args = [
  197       'make_backup' => FALSE,
  198       'install_dir' => $this->getInstallDirectory(),
  199       'backup_dir'  => $this->getBackupDir(),
  200     ];
  201     return array_merge($args, $overrides);
  202   }
  203 
  204   /**
  205    * Updates a Drupal project and returns a list of next actions.
  206    *
  207    * @param \Drupal\Core\FileTransfer\FileTransfer $filetransfer
  208    *   Object that is a child of FileTransfer. Used for moving files
  209    *   to the server.
  210    * @param array $overrides
  211    *   An array of settings to override defaults; see self::getInstallArgs().
  212    *
  213    * @return array
  214    *   An array of links which the user may need to complete the update
  215    *
  216    * @throws \Drupal\Core\Updater\UpdaterException
  217    * @throws \Drupal\Core\Updater\UpdaterFileTransferException
  218    */
  219   public function update(&$filetransfer, $overrides = []) {
  220     try {
  221       // Establish arguments with possible overrides.
  222       $args = $this->getInstallArgs($overrides);
  223 
  224       // Take a Backup.
  225       if ($args['make_backup']) {
  226         $this->makeBackup($filetransfer, $args['install_dir'], $args['backup_dir']);
  227       }
  228 
  229       if (!$this->name) {
  230         // This is bad, don't want to delete the install directory.
  231         throw new UpdaterException('Fatal error in update, cowardly refusing to wipe out the install directory.');
  232       }
  233 
  234       // Make sure the installation parent directory exists and is writable.
  235       $this->prepareInstallDirectory($filetransfer, $args['install_dir']);
  236 
  237       if (is_dir($args['install_dir'] . '/' . $this->name)) {
  238         // Remove the existing installed file.
  239         $filetransfer->removeDirectory($args['install_dir'] . '/' . $this->name);
  240       }
  241 
  242       // Copy the directory in place.
  243       $filetransfer->copyDirectory($this->source, $args['install_dir']);
  244 
  245       // Make sure what we just installed is readable by the web server.
  246       $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
  247 
  248       // Run the updates.
  249       // @todo Decide if we want to implement this.
  250       $this->postUpdate();
  251 
  252       // For now, just return a list of links of things to do.
  253       return $this->postUpdateTasks();
  254     }
  255     catch (FileTransferException $e) {
  256       throw new UpdaterFileTransferException("File Transfer failed, reason: '" . strtr($e->getMessage(), $e->arguments) . "'");
  257     }
  258   }
  259 
  260   /**
  261    * Installs a Drupal project, returns a list of next actions.
  262    *
  263    * @param \Drupal\Core\FileTransfer\FileTransfer $filetransfer
  264    *   Object that is a child of FileTransfer.
  265    * @param array $overrides
  266    *   An array of settings to override defaults; see self::getInstallArgs().
  267    *
  268    * @return array
  269    *   An array of links which the user may need to complete the install.
  270    *
  271    * @throws \Drupal\Core\Updater\UpdaterFileTransferException
  272    */
  273   public function install(&$filetransfer, $overrides = []) {
  274     try {
  275       // Establish arguments with possible overrides.
  276       $args = $this->getInstallArgs($overrides);
  277 
  278       // Make sure the installation parent directory exists and is writable.
  279       $this->prepareInstallDirectory($filetransfer, $args['install_dir']);
  280 
  281       // Copy the directory in place.
  282       $filetransfer->copyDirectory($this->source, $args['install_dir']);
  283 
  284       // Make sure what we just installed is readable by the web server.
  285       $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name);
  286 
  287       // Potentially enable something?
  288       // @todo Decide if we want to implement this.
  289       $this->postInstall();
  290       // For now, just return a list of links of things to do.
  291       return $this->postInstallTasks();
  292     }
  293     catch (FileTransferException $e) {
  294       throw new UpdaterFileTransferException("File Transfer failed, reason: '" . strtr($e->getMessage(), $e->arguments) . "'");
  295     }
  296   }
  297 
  298   /**
  299    * Makes sure the installation parent directory exists and is writable.
  300    *
  301    * @param \Drupal\Core\FileTransfer\FileTransfer $filetransfer
  302    *   Object which is a child of FileTransfer.
  303    * @param string $directory
  304    *   The installation directory to prepare.
  305    *
  306    * @throws \Drupal\Core\Updater\UpdaterException
  307    */
  308   public function prepareInstallDirectory(&$filetransfer, $directory) {
  309     // Make the parent dir writable if need be and create the dir.
  310     if (!is_dir($directory)) {
  311       $parent_dir = dirname($directory);
  312       if (!is_writable($parent_dir)) {
  313         @chmod($parent_dir, 0755);
  314         // It is expected that this will fail if the directory is owned by the
  315         // FTP user. If the FTP user == web server, it will succeed.
  316         try {
  317           $filetransfer->createDirectory($directory);
  318           $this->makeWorldReadable($filetransfer, $directory);
  319         }
  320         catch (FileTransferException $e) {
  321           // Probably still not writable. Try to chmod and do it again.
  322           // @todo Make a new exception class so we can catch it differently.
  323           try {
  324             $old_perms = substr(sprintf('%o', fileperms($parent_dir)), -4);
  325             $filetransfer->chmod($parent_dir, 0755);
  326             $filetransfer->createDirectory($directory);
  327             $this->makeWorldReadable($filetransfer, $directory);
  328             // Put the permissions back.
  329             $filetransfer->chmod($parent_dir, intval($old_perms, 8));
  330           }
  331           catch (FileTransferException $e) {
  332             $message = t($e->getMessage(), $e->arguments);
  333             $throw_message = t('Unable to create %directory due to the following: %reason', ['%directory' => $directory, '%reason' => $message]);
  334             throw new UpdaterException($throw_message);
  335           }
  336         }
  337         // Put the parent directory back.
  338         @chmod($parent_dir, 0555);
  339       }
  340     }
  341   }
  342 
  343   /**
  344    * Ensures that a given directory is world readable.
  345    *
  346    * @param \Drupal\Core\FileTransfer\FileTransfer $filetransfer
  347    *   Object which is a child of FileTransfer.
  348    * @param string $path
  349    *   The file path to make world readable.
  350    * @param bool $recursive
  351    *   If the chmod should be applied recursively.
  352    */
  353   public function makeWorldReadable(&$filetransfer, $path, $recursive = TRUE) {
  354     if (!is_executable($path)) {
  355       // Set it to read + execute.
  356       $new_perms = substr(sprintf('%o', fileperms($path)), -4, -1) . "5";
  357       $filetransfer->chmod($path, intval($new_perms, 8), $recursive);
  358     }
  359   }
  360 
  361   /**
  362    * Performs a backup.
  363    *
  364    * @param \Drupal\Core\FileTransfer\FileTransfer $filetransfer
  365    *   Object which is a child of FileTransfer.
  366    * @param string $from
  367    *   The file path to copy from.
  368    * @param string $to
  369    *   The file path to copy to.
  370    *
  371    * @todo Not implemented: https://www.drupal.org/node/2474355
  372    */
  373   public function makeBackup(FileTransfer $filetransfer, $from, $to) {
  374   }
  375 
  376   /**
  377    * Returns the full path to a directory where backups should be written.
  378    */
  379   public function getBackupDir() {
  380     return \Drupal::service('stream_wrapper_manager')->getViaScheme('temporary')->getDirectoryPath();
  381   }
  382 
  383   /**
  384    * Performs actions after new code is updated.
  385    */
  386   public function postUpdate() {
  387   }
  388 
  389   /**
  390    * Performs actions after installation.
  391    */
  392   public function postInstall() {
  393   }
  394 
  395   /**
  396    * Returns an array of links to pages that should be visited post operation.
  397    *
  398    * @return array
  399    *   Links which provide actions to take after the install is finished.
  400    */
  401   public function postInstallTasks() {
  402     return [];
  403   }
  404 
  405   /**
  406    * Returns an array of links to pages that should be visited post operation.
  407    *
  408    * @return array
  409    *   Links which provide actions to take after the update is finished.
  410    */
  411   public function postUpdateTasks() {
  412     return [];
  413   }
  414 
  415 }