PageRenderTime 26ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Updater/Updater.php

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