xpdotransport.class.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. <?php
  2. /*
  3. * Copyright 2010-2015 by MODX, LLC.
  4. *
  5. * This file is part of xPDO.
  6. *
  7. * xPDO is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * xPDO is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * xPDO; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  18. * Suite 330, Boston, MA 02111-1307 USA
  19. */
  20. /**
  21. * Represents a transportable package of related data and other resources.
  22. *
  23. * @package xpdo
  24. * @subpackage transport
  25. */
  26. /**
  27. * Represents xPDOObject and related data in a serialized format for exchange.
  28. *
  29. * @package xpdo
  30. * @subpackage transport
  31. */
  32. class xPDOTransport {
  33. /**#@+
  34. * Attributes of the package that can be used to control behavior.
  35. * @var string
  36. */
  37. const PRESERVE_KEYS = 'preserve_keys';
  38. const NATIVE_KEY = 'native_key';
  39. const UNIQUE_KEY = 'unique_key';
  40. const UPDATE_OBJECT = 'update_object';
  41. const RESOLVE_FILES = 'resolve_files';
  42. const RESOLVE_FILES_REMOVE = 'resolve_files_remove';
  43. const RESOLVE_PHP = 'resolve_php';
  44. const PACKAGE_ACTION = 'package_action';
  45. const PACKAGE_STATE = 'package_state';
  46. const RELATED_OBJECTS = 'related_objects';
  47. const RELATED_OBJECT_ATTRIBUTES = 'related_object_attributes';
  48. const MANIFEST_ATTRIBUTES = 'manifest-attributes';
  49. const MANIFEST_VEHICLES = 'manifest-vehicles';
  50. const MANIFEST_VERSION = 'manifest-version';
  51. const PREEXISTING_MODE = 'preexisting_mode';
  52. const INSTALL_FILES = 'install_files';
  53. const UNINSTALL_FILES = 'uninstall_files';
  54. const UNINSTALL_OBJECT = 'uninstall_object';
  55. const ARCHIVE_WITH = 'archive_with';
  56. const ABORT_INSTALL_ON_VEHICLE_FAIL = 'abort_install_on_vehicle_fail';
  57. const PACKAGE_NAME = 'package_name';
  58. const PACKAGE_VERSION = 'package_version';
  59. /**
  60. * Indicates how pre-existing objects are treated on install/uninstall.
  61. * @var integer
  62. */
  63. const PRESERVE_PREEXISTING = 0;
  64. const REMOVE_PREEXISTING = 1;
  65. const RESTORE_PREEXISTING = 2;
  66. /**
  67. * Indicates the physical state of the package.
  68. * @var integer
  69. */
  70. const STATE_UNPACKED = 0;
  71. const STATE_PACKED = 1;
  72. const STATE_INSTALLED = 2;
  73. /**
  74. * Indicates an action that can be performed on the package.
  75. * @var integer
  76. */
  77. const ACTION_INSTALL = 0;
  78. const ACTION_UPGRADE = 1;
  79. const ACTION_UNINSTALL = 2;
  80. /**#@-*/
  81. /**
  82. * Indicates which archiving tool to use for pack()'ing and unpack()'ing the transport.
  83. * @var integer
  84. */
  85. const ARCHIVE_WITH_DEFAULT = 0;
  86. const ARCHIVE_WITH_PCLZIP = 1;
  87. const ARCHIVE_WITH_ZIPARCHIVE = 2;
  88. /**
  89. * An {@link xPDO} reference controlling this transport instance.
  90. * @var xPDO
  91. * @access public
  92. */
  93. public $xpdo= null;
  94. /**
  95. * A unique signature to identify the package.
  96. * @var string
  97. * @access public
  98. */
  99. public $signature= null;
  100. /**
  101. * A unique name used to identify the package without the version.
  102. * @var string
  103. */
  104. public $name= null;
  105. /**
  106. * The package version, as a PHP-standardized version number string.
  107. * @var string
  108. */
  109. public $version= null;
  110. /**
  111. * Indicates the state of the xPDOTransport instance.
  112. * @var integer
  113. */
  114. public $state= null;
  115. /**
  116. * Stores various attributes about the transport package.
  117. * @var array
  118. */
  119. public $attributes= array ();
  120. /**
  121. * A map of object vehicles containing payloads of data for transport.
  122. * @var array
  123. */
  124. public $vehicles= array ();
  125. /**
  126. * The physical location of the transport package.
  127. * @var string
  128. */
  129. public $path= null;
  130. /**
  131. * The current manifest version for this transport.
  132. * @var string
  133. */
  134. public $manifestVersion = '1.1';
  135. /**
  136. * An map of preserved objects from an install used by uninstall.
  137. * @var array
  138. */
  139. public $_preserved = array();
  140. /**
  141. * Parse the name and version from a package signature.
  142. *
  143. * @static
  144. * @param string $signature The package signature to parse.
  145. * @return array An array with two elements containing the name and version respectively.
  146. */
  147. public static function parseSignature($signature) {
  148. $exploded = explode('-', $signature);
  149. $name = current($exploded);
  150. $version = '';
  151. $part = next($exploded);
  152. while ($part !== false) {
  153. $dotPos = strpos($part, '.');
  154. if ($dotPos > 0 && is_numeric(substr($part, 0, $dotPos))) {
  155. $version = $part;
  156. while (($part = next($exploded)) !== false) {
  157. $version .= '-' . $part;
  158. }
  159. break;
  160. } else {
  161. $name .= '-' . $part;
  162. $part = next($exploded);
  163. }
  164. }
  165. return array(
  166. $name,
  167. $version
  168. );
  169. }
  170. /**
  171. * Compares two package versions by signature.
  172. *
  173. * @static
  174. * @param string $signature1 A package signature.
  175. * @param string $signature2 Another package signature to compare.
  176. * @return bool|int Returns -1 if the first version is lower than the second, 0 if they
  177. * are equal, and 1 if the second is lower if the package names in the provided
  178. * signatures are equal; otherwise returns false.
  179. */
  180. public static function compareSignature($signature1, $signature2) {
  181. $value = false;
  182. $parsed1 = self::parseSignature($signature1);
  183. $parsed2 = self::parseSignature($signature2);
  184. if ($parsed1[0] === $parsed2[0]) {
  185. $value = version_compare($parsed1[1], $parsed2[1]);
  186. }
  187. return $value;
  188. }
  189. /**
  190. * Prepares and returns a new xPDOTransport instance.
  191. *
  192. * @param xPDO &$xpdo The xPDO instance accessing this package.
  193. * @param string $signature The unique signature of the package.
  194. * @param string $path Valid path to the physical transport package.
  195. * @param array $options An optional array of attributes for constructing the instance.
  196. */
  197. public function __construct(& $xpdo, $signature, $path, array $options = array()) {
  198. $this->xpdo= & $xpdo;
  199. $this->signature= $signature;
  200. $this->path= $path;
  201. if (!empty($options) && array_key_exists(self::PACKAGE_NAME, $options) && array_key_exists(self::PACKAGE_VERSION, $options)) {
  202. $this->name= $options[self::PACKAGE_NAME];
  203. $this->version= $options[self::PACKAGE_VERSION];
  204. } else {
  205. $nameAndVersion= self::parseSignature($this->signature);
  206. if (count($nameAndVersion) == 2) {
  207. $this->name= $nameAndVersion[0];
  208. $this->version= $nameAndVersion[1];
  209. }
  210. }
  211. $xpdo->loadClass('transport.xPDOVehicle', XPDO_CORE_PATH, true, true);
  212. }
  213. /**
  214. * Get an {@link xPDOVehicle} instance from an unpacked transport package.
  215. *
  216. * @param string $objFile Full path to a payload file to import. The
  217. * payload file, when included must return a valid {@link xPDOVehicle::$payload}.
  218. * @param array $options An array of options to be applied when getting the
  219. * object.
  220. * @return xPDOVehicle The vehicle represented in the file.
  221. */
  222. public function get($objFile, $options= array ()) {
  223. $vehicle = null;
  224. $objFile = $this->path . $this->signature . '/' . $objFile;
  225. $vehiclePackage = isset($options['vehicle_package']) ? $options['vehicle_package'] : '';
  226. $vehiclePackagePath = isset($options['vehicle_package_path']) ? $options['vehicle_package_path'] : '';
  227. $vehicleClass = isset($options['vehicle_class']) ? $options['vehicle_class'] : '';
  228. if (empty($vehiclePackage)) $vehiclePackage = $options['vehicle_package'] = 'transport';
  229. if (empty($vehicleClass)) $vehicleClass = $options['vehicle_class'] = 'xPDOObjectVehicle';
  230. if ($className = $this->xpdo->loadClass("{$vehiclePackage}.{$vehicleClass}", $vehiclePackagePath, true, true)) {
  231. $vehicle = new $className();
  232. if (file_exists($objFile)) {
  233. $payload = include ($objFile);
  234. if ($payload) {
  235. $vehicle->payload = $payload;
  236. }
  237. }
  238. } else {
  239. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehiclePackage}.{$vehicleClass}) could not be loaded.");
  240. }
  241. return $vehicle;
  242. }
  243. /**
  244. * Install vehicles in the package into the sponsor {@link xPDO} instance.
  245. *
  246. * @param array $options Install options to be applied to the process.
  247. * @return boolean true if the vehicles were successfully installed.
  248. */
  249. public function install($options= array ()) {
  250. $installed= false;
  251. $saved = array();
  252. $this->_preserved = array();
  253. if (!is_array($options)) {
  254. $options= array(xPDOTransport::PACKAGE_ACTION => xPDOTransport::ACTION_INSTALL);
  255. } elseif (!isset($options[xPDOTransport::PACKAGE_ACTION])) {
  256. $options[xPDOTransport::PACKAGE_ACTION]= xPDOTransport::ACTION_INSTALL;
  257. }
  258. if (!empty ($this->vehicles)) {
  259. foreach ($this->vehicles as $vIndex => $vehicleMeta) {
  260. $vOptions = array_merge($options, $vehicleMeta);
  261. if ($vehicle = $this->get($vehicleMeta['filename'], $vOptions)) {
  262. $vehicleInstalled = $vehicle->install($this, $vOptions);
  263. if (!$vehicleInstalled && isset($vehicle->payload[xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL]) && !empty($vehicle->payload[xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL])) {
  264. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Vehicle {$vehicle->payload['guid']} in transport {$this->signature} failed to install and indicated the process should be aborted.");
  265. return false;
  266. } else {
  267. $saved[$vehicle->payload['guid']] = $vehicleInstalled;
  268. }
  269. }
  270. }
  271. $this->writePreserved();
  272. if (!empty($saved)) {
  273. $installed = true;
  274. }
  275. } else {
  276. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, 'No vehicles are defined in the transport package (' . $this->signature . ') manifest for installation');
  277. }
  278. return $installed;
  279. }
  280. /**
  281. * Uninstall vehicles in the package from the sponsor {@link xPDO} instance.
  282. *
  283. * @param array $options Uninstall options to be applied to the process.
  284. * @return boolean true if the vehicles were successfully uninstalled.
  285. */
  286. public function uninstall($options = array ()) {
  287. $processed = array();
  288. if (!is_array($options)) {
  289. $options= array(xPDOTransport::PACKAGE_ACTION => xPDOTransport::ACTION_UNINSTALL);
  290. } elseif (!isset($options[xPDOTransport::PACKAGE_ACTION])) {
  291. $options[xPDOTransport::PACKAGE_ACTION]= xPDOTransport::ACTION_UNINSTALL;
  292. }
  293. if (!empty ($this->vehicles)) {
  294. $this->_preserved = $this->loadPreserved();
  295. $vehicleArray = array_reverse($this->vehicles, true);
  296. foreach ($vehicleArray as $vIndex => $vehicleMeta) {
  297. $vOptions = array_merge($options, $vehicleMeta);
  298. if ($this->xpdo->getDebug() === true) {
  299. $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Removing Vehicle: " . print_r($vOptions, true));
  300. }
  301. if ($vehicle = $this->get($vehicleMeta['filename'], $vOptions)) {
  302. $processed[$vehicleMeta['guid']] = $vehicle->uninstall($this, $vOptions);
  303. } else {
  304. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not load vehicle: ' . print_r($vOptions, true));
  305. }
  306. }
  307. } else {
  308. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, 'No vehicles are defined in the transport package (' . $this->signature . ') for removal');
  309. }
  310. $uninstalled = (array_search(false, $processed, true) === false);
  311. return $uninstalled;
  312. }
  313. /**
  314. * Wrap artifact with an {@link xPDOVehicle} and register in the transport.
  315. *
  316. * @param mixed $artifact An artifact to load into the transport.
  317. * @param array $attributes A set of attributes related to the artifact; these
  318. * can be anything from rules describing how to pack or unpack the artifact,
  319. * or any other data that might be useful when dealing with a transportable
  320. * artifact.
  321. * @return bool TRUE if the artifact is successfully registered in the transport.
  322. */
  323. public function put($artifact, $attributes = array ()) {
  324. $added= false;
  325. if (!empty($artifact)) {
  326. $vehiclePackage = isset($attributes['vehicle_package']) ? $attributes['vehicle_package'] : '';
  327. $vehiclePackagePath = isset($attributes['vehicle_package_path']) ? $attributes['vehicle_package_path'] : '';
  328. $vehicleClass = isset($attributes['vehicle_class']) ? $attributes['vehicle_class'] : '';
  329. if (empty($vehiclePackage)) $vehiclePackage = $attributes['vehicle_package'] = 'transport';
  330. if (empty($vehicleClass)) $vehicleClass = $attributes['vehicle_class'] = 'xPDOObjectVehicle';
  331. if ($className = $this->xpdo->loadClass("{$vehiclePackage}.{$vehicleClass}", $vehiclePackagePath, true, true)) {
  332. /** @var xPDOVehicle $vehicle */
  333. $vehicle = new $className();
  334. $vehicle->put($this, $artifact, $attributes);
  335. if ($added= $vehicle->store($this)) {
  336. $this->registerVehicle($vehicle);
  337. }
  338. } else {
  339. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehiclePackage}.{$vehicleClass}) could not be loaded.");
  340. }
  341. }
  342. return $added;
  343. }
  344. /**
  345. * Pack the {@link xPDOTransport} instance in preparation for distribution.
  346. *
  347. * @return boolean Indicates if the transport was packed successfully.
  348. */
  349. public function pack() {
  350. if (empty($this->vehicles)) {
  351. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Attempt to pack a transport package with no vehicles.');
  352. return false;
  353. }
  354. $this->writeManifest();
  355. $fileName = $this->path . $this->signature . '.transport.zip';
  356. return xPDOTransport::_pack($this->xpdo, $fileName, $this->path, $this->signature);
  357. }
  358. /**
  359. * Pack the resources from path relative to source into an archive with filename.
  360. *
  361. * @uses compression.xPDOZip OR compression.PclZip
  362. * @todo Refactor this to be implemented in a service class external to xPDOTransport.
  363. *
  364. * @param xPDO &$xpdo A reference to an xPDO instance.
  365. * @param string $filename A valid zip archive filename.
  366. * @param string $path An absolute file system path location of the resources to pack.
  367. * @param string $source A relative portion of path to include in the archive.
  368. * @return boolean True if packed successfully.
  369. */
  370. public static function _pack(& $xpdo, $filename, $path, $source) {
  371. $packed = false;
  372. $packResults = false;
  373. $errors = array();
  374. if ($xpdo->getOption(xPDOTransport::ARCHIVE_WITH, null, 0) != xPDOTransport::ARCHIVE_WITH_PCLZIP && class_exists('ZipArchive', true) && $xpdo->loadClass('compression.xPDOZip', XPDO_CORE_PATH, true, true)) {
  375. if ($xpdo->getDebug() === true) {
  376. $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Using xPDOZip / native ZipArchive", null, __METHOD__, __FILE__, __LINE__);
  377. }
  378. $archive = new xPDOZip($xpdo, $filename, array(xPDOZip::CREATE => true, xPDOZip::OVERWRITE => true));
  379. if ($archive) {
  380. $packResults = $archive->pack("{$path}{$source}", array(xPDOZip::ZIP_TARGET => "{$source}/"));
  381. $archive->close();
  382. if (!$archive->hasError() && !empty($packResults)) {
  383. $packed = true;
  384. } else {
  385. $errors = $archive->getErrors();
  386. }
  387. }
  388. } elseif (class_exists('PclZip') || include(XPDO_CORE_PATH . 'compression/pclzip.lib.php')) {
  389. $archive = new PclZip($filename);
  390. if ($archive) {
  391. $packResults = $archive->create("{$path}{$source}", PCLZIP_OPT_REMOVE_PATH, "{$path}");
  392. if ($packResults) {
  393. $packed = true;
  394. } else {
  395. $errors = $archive->errorInfo($xpdo->getDebug() === true);
  396. }
  397. }
  398. }
  399. if (!$packed) {
  400. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error packing {$path}{$source} to {$filename}: " . print_r($errors, true));
  401. }
  402. if ($xpdo->getDebug() === true) {
  403. $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Results of packing {$path}{$source} to {$filename}: " . print_r($packResults, true), null, __METHOD__, __FILE__, __LINE__);
  404. }
  405. return $packed;
  406. }
  407. /**
  408. * Write the package manifest file.
  409. *
  410. * @return boolean Indicates if the manifest was successfully written.
  411. */
  412. public function writeManifest() {
  413. $written = false;
  414. if (!empty ($this->vehicles)) {
  415. if (!empty($this->attributes['setup-options']) && is_array($this->attributes['setup-options'])) {
  416. $cacheManager = $this->xpdo->getCacheManager();
  417. $cacheManager->copyFile($this->attributes['setup-options']['source'],$this->path . $this->signature . '/setup-options.php');
  418. $this->attributes['setup-options'] = $this->signature . '/setup-options.php';
  419. }
  420. $manifest = array(
  421. xPDOTransport::MANIFEST_VERSION => $this->manifestVersion,
  422. xPDOTransport::MANIFEST_ATTRIBUTES => $this->attributes,
  423. xPDOTransport::MANIFEST_VEHICLES => $this->vehicles
  424. );
  425. $content = var_export($manifest, true);
  426. $cacheManager = $this->xpdo->getCacheManager();
  427. if ($content && $cacheManager) {
  428. $fileName = $this->path . $this->signature . '/manifest.php';
  429. $content = "<?php return {$content};";
  430. if (!($written = $cacheManager->writeFile($fileName, $content))) {
  431. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error writing manifest to ' . $fileName);
  432. }
  433. }
  434. }
  435. return $written;
  436. }
  437. /**
  438. * Write objects preserved during install() to file for use by uninstall().
  439. *
  440. * @return boolean Indicates if the preserved file was successfully written.
  441. */
  442. public function writePreserved() {
  443. $written = false;
  444. if (!empty($this->_preserved)) {
  445. $content = var_export($this->_preserved, true);
  446. $cacheManager = $this->xpdo->getCacheManager();
  447. if ($content && $cacheManager) {
  448. $fileName = $this->path . $this->signature . '/preserved.php';
  449. $content = "<?php return {$content};";
  450. if (!($written = $cacheManager->writeFile($fileName, $content))) {
  451. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error writing preserved objects to ' . $fileName);
  452. }
  453. }
  454. }
  455. return $written;
  456. }
  457. /**
  458. * Load preserved objects from the previous install().
  459. *
  460. * @return array An array of preserved objects, or an empty array.
  461. */
  462. public function loadPreserved() {
  463. $preserved = array();
  464. $fileName = $this->path . $this->signature . '/preserved.php';
  465. if (file_exists($fileName)) {
  466. $content = include($fileName);
  467. if (is_array($content)) {
  468. $preserved = $content;
  469. } else {
  470. $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error loading preserved objects from ' . $fileName);
  471. }
  472. }
  473. return $preserved;
  474. }
  475. /**
  476. * Register an xPDOVehicle with this transport instance.
  477. *
  478. * @param xPDOVehicle &$vehicle A reference to the vehicle being registered.
  479. */
  480. public function registerVehicle(& $vehicle) {
  481. $this->vehicles[] = $vehicle->register($this);
  482. }
  483. /**
  484. * Get an attribute of the package manifest.
  485. *
  486. * @param string $key The key of the attribute to retrieve.
  487. * @return mixed The value of the attribute or null if it is not set.
  488. */
  489. public function getAttribute($key) {
  490. $value = null;
  491. if (array_key_exists($key, $this->attributes)) $value = $this->attributes[$key];
  492. return $value;
  493. }
  494. /**
  495. * Set an attribute of the package manifest.
  496. *
  497. * @param string $key The key identifying the attribute to set.
  498. * @param mixed $value The value to set the attribute to.
  499. */
  500. public function setAttribute($key, $value) {
  501. $this->attributes[$key]= $value;
  502. }
  503. /**
  504. * Get dependency requirements for this xPDOTransport.
  505. *
  506. * @param array $requires An optional array of dependent package constraints
  507. * to override/supplement those specified in the package metadata.
  508. *
  509. * @return array An array of dependency requirements for the package.
  510. */
  511. public function getDependencies(array $requires = array()) {
  512. $requiresAttribute = $this->getAttribute('requires');
  513. if (is_array($requiresAttribute)) {
  514. $requires = array_merge($requiresAttribute, $requires);
  515. }
  516. return $requires;
  517. }
  518. /**
  519. * Check if dependencies are satisfied for the package.
  520. *
  521. * Override this method to check implementation specific package
  522. * dependencies. This implementation only checks platform dependencies.
  523. *
  524. * @param array $options An array of options for the checks.
  525. *
  526. * @return array An array containing any unsatisfied dependencies.
  527. */
  528. public function checkDependencies(array $options = array()) {
  529. $unsatisfied = $this->getDependencies();
  530. return self::checkPlatformDependencies($unsatisfied);
  531. }
  532. /**
  533. * Check if any specified platform dependencies are satisfied.
  534. *
  535. * @param array $dependencies An array of dependencies to test.
  536. *
  537. * @return array An array containing any unsatisfied dependencies.
  538. */
  539. public static function checkPlatformDependencies($dependencies) {
  540. if (is_array($dependencies)) {
  541. foreach ($dependencies as $depName => $depRequire) {
  542. switch ($depName) {
  543. case 'php':
  544. if (self::satisfies(XPDO_PHP_VERSION, $depRequire)) {
  545. unset($dependencies[$depName]);
  546. }
  547. break;
  548. default:
  549. break;
  550. }
  551. }
  552. }
  553. return $dependencies;
  554. }
  555. /**
  556. * Test if a version satisfies a version constraint.
  557. *
  558. * @param string $version The version to test.
  559. * @param string $constraint The constraint to satisfy.
  560. *
  561. * @return bool TRUE if the version satisfies the constraint; FALSE otherwise.
  562. */
  563. public static function satisfies($version, $constraint) {
  564. $satisfied = false;
  565. $constraint = trim($constraint);
  566. if (substr($constraint, 0, 1) === '~') {
  567. $requirement = substr($constraint, 1);
  568. $constraint = ">={$requirement},<" . self::nextSignificantRelease($requirement);
  569. }
  570. if (strpos($constraint, ',') !== false) {
  571. $exploded = explode(',', $constraint);
  572. array_walk($exploded, 'trim');
  573. $satisfies = array();
  574. foreach ($exploded as $requirement) {
  575. $satisfies[] = self::satisfies($version, $requirement);
  576. }
  577. $satisfied = (false === array_search(false, $satisfies, true));
  578. } elseif (($wildcardPos = strpos($constraint, '.*')) > 0) {
  579. $requirement = substr($constraint, 0, $wildcardPos + 1);
  580. $requirements = array(
  581. ">=" . $requirement,
  582. "<" . self::nextSignificantRelease($requirement)
  583. );
  584. $satisfies = array();
  585. foreach ($requirements as $requires) {
  586. $satisfies[] = self::satisfies($version, $requires);
  587. }
  588. $satisfied = (false === array_search(false, $satisfies, true));
  589. } elseif (in_array(substr($constraint, 0, 1), array('<', '>', '!'))) {
  590. $operator = substr($constraint, 0, 1);
  591. $versionPos = 1;
  592. if (substr($constraint, 1, 1) === '=') {
  593. $operator .= substr($constraint, 1, 1);
  594. $versionPos++;
  595. }
  596. $requirement = substr($constraint, $versionPos);
  597. $satisfied = version_compare($version, $requirement, $operator);
  598. } elseif ($constraint === '*') {
  599. $satisfied = true;
  600. } elseif (version_compare($version, $constraint) === 0) {
  601. $satisfied = true;
  602. }
  603. return $satisfied;
  604. }
  605. /**
  606. * Get the next significant release version for a given version string.
  607. *
  608. * @param string $version A valid SemVer version string.
  609. *
  610. * @return string The next significant version for the specified version.
  611. */
  612. public static function nextSignificantRelease($version) {
  613. $parsed = explode('.', $version, 3);
  614. if (count($parsed) > 1) array_pop($parsed);
  615. $parsed[count($parsed) - 1]++;
  616. if (count($parsed) === 1) $parsed[] = '0';
  617. return implode('.', $parsed);
  618. }
  619. /**
  620. * Get an xPDOTransport instance from an existing package.
  621. *
  622. * @param xPDO &$xpdo A reference to an xPDO instance.
  623. * @param string $source Path to the packed transport.
  624. * @param string $target Path to unpack the transport to.
  625. * @param int $state The packed state of the transport.
  626. *
  627. * @return null|xPDOTransport An xPDOTransport instance or null.
  628. */
  629. public static function retrieve(& $xpdo, $source, $target, $state= xPDOTransport::STATE_PACKED) {
  630. $instance= null;
  631. $signature = basename($source, '.transport.zip');
  632. if (file_exists($source)) {
  633. if (is_writable($target)) {
  634. $manifest = xPDOTransport :: unpack($xpdo, $source, $target, $state);
  635. if ($manifest) {
  636. $instance = new xPDOTransport($xpdo, $signature, $target);
  637. if (!$instance) {
  638. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not instantiate a valid xPDOTransport object from the package {$source} to {$target}. SIG: {$signature} MANIFEST: " . print_r($manifest, 1));
  639. }
  640. $manifestVersion = xPDOTransport :: manifestVersion($manifest);
  641. switch ($manifestVersion) {
  642. case '0.1':
  643. $instance->vehicles = xPDOTransport :: _convertManifestVer1_1(xPDOTransport :: _convertManifestVer1_0($manifest));
  644. case '0.2':
  645. $instance->vehicles = xPDOTransport :: _convertManifestVer1_1(xPDOTransport :: _convertManifestVer1_0($manifest[xPDOTransport::MANIFEST_VEHICLES]));
  646. $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES];
  647. break;
  648. case '1.0':
  649. $instance->vehicles = xPDOTransport :: _convertManifestVer1_1($manifest[xPDOTransport::MANIFEST_VEHICLES]);
  650. $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES];
  651. break;
  652. default:
  653. $instance->vehicles = $manifest[xPDOTransport::MANIFEST_VEHICLES];
  654. $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES];
  655. break;
  656. }
  657. } else {
  658. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not unpack package {$source} to {$target}. SIG: {$signature}");
  659. }
  660. } else {
  661. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not unpack package: {$target} is not writable. SIG: {$signature}");
  662. }
  663. } else {
  664. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Package {$source} not found. SIG: {$signature}");
  665. }
  666. return $instance;
  667. }
  668. /**
  669. * Store the package to a specified resource location.
  670. *
  671. * @todo Implement ability to store a package to a specified location, supporting various
  672. * transport methods.
  673. * @param mixed $location The location to store the package.
  674. * @return bool TRUE if the package is stored successfully.
  675. */
  676. public function store($location) {
  677. $stored= false;
  678. if ($this->state === xPDOTransport::STATE_PACKED) {}
  679. return $stored;
  680. }
  681. /**
  682. * Unpack the package to prepare for installation and return a manifest.
  683. *
  684. * @param xPDO &$xpdo A reference to an xPDO instance.
  685. * @param string $from Filename of the archive containing the transport package.
  686. * @param string $to The root path where the contents of the archive should be extracted. This
  687. * path must be writable by the user executing the PHP process on the server.
  688. * @param integer $state The current state of the package, i.e. packed or unpacked.
  689. * @return array The manifest which is included after successful extraction.
  690. */
  691. public static function unpack(& $xpdo, $from, $to, $state = xPDOTransport::STATE_PACKED) {
  692. $manifest= null;
  693. if ($state !== xPDOTransport::STATE_UNPACKED) {
  694. $resources = xPDOTransport::_unpack($xpdo, $from, $to);
  695. } else {
  696. $resources = true;
  697. }
  698. if ($resources) {
  699. $manifestFilename = $to . basename($from, '.transport.zip') . '/manifest.php';
  700. if (file_exists($manifestFilename)) {
  701. $manifest= @include ($manifestFilename);
  702. } else {
  703. $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not find package manifest at {$manifestFilename}");
  704. }
  705. }
  706. return $manifest;
  707. }
  708. /**
  709. * Unpack a zip archive to a specified location.
  710. *
  711. * @uses compression.xPDOZip OR compression.PclZip
  712. * @todo Refactor this to be implemented in a service class external to xPDOTransport.
  713. *
  714. * @param xPDO &$xpdo A reference to an xPDO instance.
  715. * @param string $from An absolute file system location to a valid zip archive.
  716. * @param string $to A file system location to extract the contents of the archive to.
  717. * @return array|boolean An array of unpacked resources or false on failure.
  718. */
  719. public static function _unpack(& $xpdo, $from, $to) {
  720. $resources = false;
  721. if ($xpdo->getOption(xPDOTransport::ARCHIVE_WITH, null, 0) != xPDOTransport::ARCHIVE_WITH_PCLZIP && class_exists('ZipArchive', true) && $xpdo->loadClass('compression.xPDOZip', XPDO_CORE_PATH, true, true)) {
  722. $archive = new xPDOZip($xpdo, $from);
  723. if ($archive) {
  724. $resources = $archive->unpack($to);
  725. $archive->close();
  726. }
  727. } elseif (class_exists('PclZip') || include(XPDO_CORE_PATH . 'compression/pclzip.lib.php')) {
  728. $archive = new PclZip($from);
  729. if ($archive) {
  730. $resources = $archive->extract(PCLZIP_OPT_PATH, $to);
  731. }
  732. }
  733. return $resources;
  734. }
  735. /**
  736. * Returns the structure version of the given manifest array.
  737. *
  738. * @static
  739. * @param array $manifest A valid xPDOTransport manifest array.
  740. * @return string Version string of the manifest structure.
  741. */
  742. public static function manifestVersion($manifest) {
  743. $version = false;
  744. if (is_array($manifest)) {
  745. if (isset($manifest[xPDOTransport::MANIFEST_VERSION])) {
  746. $version = $manifest[xPDOTransport::MANIFEST_VERSION];
  747. }
  748. elseif (isset($manifest[xPDOTransport::MANIFEST_VEHICLES])) {
  749. $version = '0.2';
  750. }
  751. else {
  752. $version = '0.1';
  753. }
  754. }
  755. return $version;
  756. }
  757. /**
  758. * Converts older manifest vehicles to 1.0 format.
  759. *
  760. * @static
  761. * @access private
  762. * @param array $manifestVehicles A structure representing vehicles from a pre-1.0 manifest
  763. * format.
  764. * @return array Vehicle definition structures converted to 1.0 format.
  765. */
  766. protected static function _convertManifestVer1_0($manifestVehicles) {
  767. $manifest = array();
  768. foreach ($manifestVehicles as $vClass => $vehicles) {
  769. foreach ($vehicles as $vKey => $vehicle) {
  770. $entry = array(
  771. 'class' => $vClass,
  772. 'native_key' => $vehicle['native_key'],
  773. 'filename' => $vehicle['filename'],
  774. );
  775. if (isset($vehicle['namespace'])) {
  776. $entry['namespace'] = $vehicle['namespace'];
  777. }
  778. $manifest[] = $entry;
  779. }
  780. }
  781. return $manifest;
  782. }
  783. /**
  784. * Converts 1.0 manifest vehicles to 1.1 format.
  785. *
  786. * @static
  787. * @access private
  788. * @param array $vehicles A structure representing vehicles from a pre-1.1 manifest format.
  789. * @return array Vehicle definition structures converted to 1.1 format.
  790. */
  791. protected static function _convertManifestVer1_1($vehicles) {
  792. $manifest = array();
  793. foreach ($vehicles as $vKey => $vehicle) {
  794. $entry = $vehicle;
  795. if (!isset($vehicle['vehicle_class'])) {
  796. $entry['vehicle_class'] = 'xPDOObjectVehicle';
  797. }
  798. if (!isset($vehicle['vehicle_package'])) {
  799. $entry['vehicle_package'] = 'transport';
  800. }
  801. $manifest[] = $entry;
  802. }
  803. return $manifest;
  804. }
  805. }