PageRenderTime 64ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/system/src/Grav/Common/GPM/Installer.php

https://gitlab.com/asun89/socianovation-web
PHP | 370 lines | 219 code | 60 blank | 91 comment | 34 complexity | c8c8dd3a4fa93e9cda4994ed24b3be06 MD5 | raw file
  1. <?php
  2. namespace Grav\Common\GPM;
  3. use Grav\Common\Filesystem\Folder;
  4. use Symfony\Component\Yaml\Yaml;
  5. class Installer
  6. {
  7. /** @const No error */
  8. const OK = 0;
  9. /** @const Target already exists */
  10. const EXISTS = 1;
  11. /** @const Target is a symbolic link */
  12. const IS_LINK = 2;
  13. /** @const Target doesn't exist */
  14. const NOT_FOUND = 4;
  15. /** @const Target is not a directory */
  16. const NOT_DIRECTORY = 8;
  17. /** @const Target is not a Grav instance */
  18. const NOT_GRAV_ROOT = 16;
  19. /** @const Error while trying to open the ZIP package */
  20. const ZIP_OPEN_ERROR = 32;
  21. /** @const Error while trying to extract the ZIP package */
  22. const ZIP_EXTRACT_ERROR = 64;
  23. /**
  24. * Destination folder on which validation checks are applied
  25. * @var string
  26. */
  27. protected static $target;
  28. /**
  29. * Error Code
  30. * @var integer
  31. */
  32. protected static $error = 0;
  33. /**
  34. * Default options for the install
  35. * @var array
  36. */
  37. protected static $options = [
  38. 'overwrite' => true,
  39. 'ignore_symlinks' => true,
  40. 'sophisticated' => false,
  41. 'theme' => false,
  42. 'install_path' => '',
  43. 'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
  44. ];
  45. /**
  46. * Installs a given package to a given destination.
  47. *
  48. * @param string $package The local path to the ZIP package
  49. * @param string $destination The local path to the Grav Instance
  50. * @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
  51. *
  52. * @return boolean True if everything went fine, False otherwise.
  53. */
  54. public static function install($package, $destination, $options = [])
  55. {
  56. $destination = rtrim($destination, DS);
  57. $options = array_merge(self::$options, $options);
  58. $install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
  59. if (!self::isGravInstance($destination) || !self::isValidDestination($install_path, $options['exclude_checks'])) {
  60. return false;
  61. }
  62. if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
  63. self::lastErrorCode() == self::EXISTS && !$options['overwrite']) {
  64. return false;
  65. }
  66. // Pre install checks
  67. static::flightProcessing('pre_install', $install_path);
  68. $zip = new \ZipArchive();
  69. $archive = $zip->open($package);
  70. $tmp = CACHE_DIR . 'tmp/Grav-' . uniqid();
  71. if ($archive !== true) {
  72. self::$error = self::ZIP_OPEN_ERROR;
  73. return false;
  74. }
  75. Folder::mkdir($tmp);
  76. $unzip = $zip->extractTo($tmp);
  77. if (!$unzip) {
  78. self::$error = self::ZIP_EXTRACT_ERROR;
  79. $zip->close();
  80. Folder::delete($tmp);
  81. return false;
  82. }
  83. if (!$options['sophisticated']) {
  84. if ($options['theme']) {
  85. self::copyInstall($zip, $install_path, $tmp);
  86. } else {
  87. self::moveInstall($zip, $install_path, $tmp);
  88. }
  89. } else {
  90. self::sophisticatedInstall($zip, $install_path, $tmp);
  91. }
  92. Folder::delete($tmp);
  93. $zip->close();
  94. // Post install checks
  95. static::flightProcessing('post_install', $install_path);
  96. self::$error = self::OK;
  97. return true;
  98. }
  99. /**
  100. * @param $state
  101. * @param $install_path
  102. */
  103. protected static function flightProcessing($state, $install_path)
  104. {
  105. $blueprints_path = $install_path . DS . 'blueprints.yaml';
  106. if (file_exists($blueprints_path)) {
  107. $package_yaml = Yaml::parse(file_get_contents($blueprints_path));
  108. if (isset($package_yaml['install'][$state]['create'])) {
  109. foreach ((array) $package_yaml['install']['pre_install']['create'] as $file) {
  110. Folder::mkdir($install_path . '/' . ltrim($file, '/'));
  111. }
  112. }
  113. if (isset($package_yaml['install'][$state]['remove'])) {
  114. foreach ((array) $package_yaml['install']['pre_install']['remove'] as $file) {
  115. Folder::delete($install_path . '/' . ltrim($file, '/'));
  116. }
  117. }
  118. }
  119. }
  120. /**
  121. * @param \ZipArchive $zip
  122. * @param $install_path
  123. * @param $tmp
  124. *
  125. * @return bool
  126. */
  127. public static function moveInstall(\ZipArchive $zip, $install_path, $tmp)
  128. {
  129. $container = $zip->getNameIndex(0);
  130. if (file_exists($install_path)) {
  131. Folder::delete($install_path);
  132. }
  133. Folder::move($tmp . DS . $container, $install_path);
  134. return true;
  135. }
  136. /**
  137. * @param \ZipArchive $zip
  138. * @param $install_path
  139. * @param $tmp
  140. *
  141. * @return bool
  142. */
  143. public static function copyInstall(\ZipArchive $zip, $install_path, $tmp)
  144. {
  145. $firstDir = $zip->getNameIndex(0);
  146. if (empty($firstDir)) {
  147. throw new \RuntimeException("Directory $firstDir is missing");
  148. } else {
  149. $tmp = realpath($tmp . DS . $firstDir);
  150. Folder::rcopy($tmp, $install_path);
  151. }
  152. return true;
  153. }
  154. /**
  155. * @param \ZipArchive $zip
  156. * @param $install_path
  157. * @param $tmp
  158. *
  159. * @return bool
  160. */
  161. public static function sophisticatedInstall(\ZipArchive $zip, $install_path, $tmp)
  162. {
  163. for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
  164. $filename = $zip->getNameIndex($i);
  165. $fileinfo = pathinfo($filename);
  166. $depth = count(explode(DS, rtrim($filename, '/')));
  167. if ($depth > 2) {
  168. continue;
  169. }
  170. $path = $install_path . DS . $fileinfo['basename'];
  171. if (is_link($path)) {
  172. continue;
  173. } else {
  174. if (is_dir($path)) {
  175. Folder::delete($path);
  176. Folder::move($tmp . DS . $filename, $path);
  177. if ($fileinfo['basename'] == 'bin') {
  178. foreach (glob($path . DS . '*') as $file) {
  179. @chmod($file, 0755);
  180. }
  181. }
  182. } else {
  183. @unlink($path);
  184. @copy($tmp . DS . $filename, $path);
  185. }
  186. }
  187. }
  188. return true;
  189. }
  190. /**
  191. * Uninstalls one or more given package
  192. *
  193. * @param string $path The slug of the package(s)
  194. * @param array $options Options to use for uninstalling
  195. *
  196. * @return boolean True if everything went fine, False otherwise.
  197. */
  198. public static function uninstall($path, $options = [])
  199. {
  200. $options = array_merge(self::$options, $options);
  201. if (!self::isValidDestination($path, $options['exclude_checks'])
  202. ) {
  203. return false;
  204. }
  205. return Folder::delete($path);
  206. }
  207. /**
  208. * Runs a set of checks on the destination and sets the Error if any
  209. *
  210. * @param string $destination The directory to run validations at
  211. * @param array $exclude An array of constants to exclude from the validation
  212. *
  213. * @return boolean True if validation passed. False otherwise
  214. */
  215. public static function isValidDestination($destination, $exclude = [])
  216. {
  217. self::$error = 0;
  218. self::$target = $destination;
  219. if (is_link($destination)) {
  220. self::$error = self::IS_LINK;
  221. } elseif (file_exists($destination)) {
  222. self::$error = self::EXISTS;
  223. } elseif (!file_exists($destination)) {
  224. self::$error = self::NOT_FOUND;
  225. } elseif (!is_dir($destination)) {
  226. self::$error = self::NOT_DIRECTORY;
  227. }
  228. if (count($exclude) && in_array(self::$error, $exclude)) {
  229. return true;
  230. }
  231. return !(self::$error);
  232. }
  233. /**
  234. * Validates if the given path is a Grav Instance
  235. *
  236. * @param string $target The local path to the Grav Instance
  237. *
  238. * @return boolean True if is a Grav Instance. False otherwise
  239. */
  240. public static function isGravInstance($target)
  241. {
  242. self::$error = 0;
  243. self::$target = $target;
  244. if (
  245. !file_exists($target . DS . 'index.php') ||
  246. !file_exists($target . DS . 'bin') ||
  247. !file_exists($target . DS . 'user') ||
  248. !file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
  249. ) {
  250. self::$error = self::NOT_GRAV_ROOT;
  251. }
  252. return !self::$error;
  253. }
  254. /**
  255. * Returns the last error occurred in a string message format
  256. * @return string The message of the last error
  257. */
  258. public static function lastErrorMsg()
  259. {
  260. if (is_string(self::$error)) {
  261. return self::$error;
  262. }
  263. switch (self::$error) {
  264. case 0:
  265. $msg = 'No Error';
  266. break;
  267. case self::EXISTS:
  268. $msg = 'The target path "' . self::$target . '" already exists';
  269. break;
  270. case self::IS_LINK:
  271. $msg = 'The target path "' . self::$target . '" is a symbolic link';
  272. break;
  273. case self::NOT_FOUND:
  274. $msg = 'The target path "' . self::$target . '" does not appear to exist';
  275. break;
  276. case self::NOT_DIRECTORY:
  277. $msg = 'The target path "' . self::$target . '" does not appear to be a folder';
  278. break;
  279. case self::NOT_GRAV_ROOT:
  280. $msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
  281. break;
  282. case self::ZIP_OPEN_ERROR:
  283. $msg = 'Unable to open the package file';
  284. break;
  285. case self::ZIP_EXTRACT_ERROR:
  286. $msg = 'An error occurred while extracting the package';
  287. break;
  288. default:
  289. $msg = 'Unknown Error';
  290. break;
  291. }
  292. return $msg;
  293. }
  294. /**
  295. * Returns the last error code of the occurred error
  296. * @return integer The code of the last error
  297. */
  298. public static function lastErrorCode()
  299. {
  300. return self::$error;
  301. }
  302. /**
  303. * Allows to manually set an error
  304. * @param int|string $error the Error code
  305. */
  306. public static function setError($error)
  307. {
  308. self::$error = $error;
  309. }
  310. }