xpdovehicle.class.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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. * Abstract class that represents an artifact within a transportable package.
  22. *
  23. * @package xpdo
  24. * @subpackage transport
  25. */
  26. /**
  27. * Represents an individual artifact within an {@link xPDOTransport} package.
  28. *
  29. * Extend this abstract class to provide custom xPDOVehicle behavior for various kinds of artifacts
  30. * (e.g. objects, xPDOObjects, files, database schemas, etc.).
  31. *
  32. * @package xpdo
  33. * @subpackage transport
  34. *
  35. * @abstract
  36. */
  37. abstract class xPDOVehicle {
  38. /**
  39. * Represents the artifact and related attributes stored in the vehicle.
  40. * @var array
  41. */
  42. public $payload = array ();
  43. public $class = 'xPDOVehicle';
  44. /**
  45. * Build a manifest entry to be registered in a transport for this vehicle.
  46. *
  47. * @param xPDOTransport &$transport The xPDOTransport instance to register
  48. * the vehicle into.
  49. * @return array An array of vehicle attributes that will be registered into
  50. * an xPDOTransport manifest.
  51. */
  52. public function register(& $transport) {
  53. $vPackage = isset($this->payload['vehicle_package']) ? $this->payload['vehicle_package'] : 'transport';
  54. $vClass = isset($this->payload['vehicle_class']) ? $this->payload['vehicle_class'] : $this->class;
  55. $class = isset($this->payload['class']) ? $this->payload['class'] : $vClass;
  56. $entry = array(
  57. 'vehicle_package' => $vPackage,
  58. 'vehicle_class' => $vClass,
  59. 'class' => $class,
  60. 'guid' => $this->payload['guid'],
  61. 'native_key' => array_key_exists('native_key', $this->payload) ? $this->payload['native_key'] : null,
  62. 'filename' => $class . '/' . $this->payload['filename'],
  63. );
  64. if (isset($this->payload['namespace'])) {
  65. $entry['namespace'] = $this->payload['namespace'];
  66. }
  67. return $entry;
  68. }
  69. /**
  70. * Retrieve an artifact represented in this vehicle.
  71. *
  72. * By default, this method simply returns the raw payload merged with the
  73. * provided options, but you can optionally provide a payload element
  74. * specifically on which to operate as well as override the method in
  75. * derivatives to further transform the returned artifact.
  76. *
  77. * @param xPDOTransport $transport The transport package containing this
  78. * vehicle.
  79. * @param array $options Options that apply to the artifact or retrieval
  80. * process.
  81. * @param array $element An optional payload element representing a specific
  82. * part of the artifact to operate on. If not specified, the root element
  83. * of the payload is used.
  84. */
  85. public function get(& $transport, $options = array (), $element = null) {
  86. $artifact = null;
  87. if ($element === null) $element = $this->payload;
  88. $artifact = array_merge($options, $element);
  89. return $artifact;
  90. }
  91. /**
  92. * Install the vehicle artifact into a transport host.
  93. *
  94. * @abstract Implement this in a derivative to make an installable vehicle.
  95. * @param xPDOTransport &$transport A reference to the transport.
  96. * @param array $options An array of options for altering the installation
  97. * of the artifact.
  98. * @return boolean True if the installation of the vehicle artifact was
  99. * successful.
  100. */
  101. abstract public function install(& $transport, $options);
  102. /**
  103. * Uninstalls the vehicle artifact from a transport host.
  104. *
  105. * @abstract Implement this in a derivative to make an uninstallable
  106. * vehicle.
  107. * @param xPDOTransport &$transport A reference to the transport.
  108. * @param array $options An array of options for altering the uninstallation
  109. * of the artifact.
  110. */
  111. abstract public function uninstall(& $transport, $options);
  112. /**
  113. * Resolve any dependencies of the artifact represented in this vehicle.
  114. *
  115. * @param xPDOTransport &$transport A reference to the xPDOTransport in
  116. * which this vehicle is stored.
  117. * @param mixed &$object An object reference to resolve dependencies for.
  118. * Use this to make the artifact or other important data available to the
  119. * resolver scripts.
  120. * @param array $options Additional options for the resolution process.
  121. * @return boolean Indicates if the resolution was successful.
  122. */
  123. public function resolve(& $transport, & $object, $options = array ()) {
  124. $resolved = false;
  125. if (isset ($this->payload['resolve'])) {
  126. foreach ($this->payload['resolve'] as $rKey => $r) {
  127. $type = $r['type'];
  128. $body = $r['body'];
  129. $preExistingMode = xPDOTransport::PRESERVE_PREEXISTING;
  130. if (!empty ($options[xPDOTransport::PREEXISTING_MODE])) {
  131. $preExistingMode = intval($options[xPDOTransport::PREEXISTING_MODE]);
  132. }
  133. switch ($type) {
  134. case 'file' :
  135. if (isset ($options[xPDOTransport::RESOLVE_FILES]) && !$options[xPDOTransport::RESOLVE_FILES]) {
  136. $resolved = true;
  137. break;
  138. }
  139. if ($transport->xpdo->getDebug() === true) {
  140. $transport->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Resolving transport files: " . print_r($this, true));
  141. }
  142. $fileMeta = $transport->xpdo->fromJSON($body, true);
  143. $fileName = $fileMeta['name'];
  144. $fileSource = $transport->path . $fileMeta['source'];
  145. $fileTarget = eval ($fileMeta['target']);
  146. $fileTargetPath = $fileTarget . $fileName;
  147. $preservedArchive = $transport->path . $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'] . '.' . $rKey . '.preserved.zip';
  148. $cacheManager = $transport->xpdo->getCacheManager();
  149. switch ($options[xPDOTransport::PACKAGE_ACTION]) {
  150. case xPDOTransport::ACTION_UPGRADE:
  151. case xPDOTransport::ACTION_INSTALL: // if package is installing
  152. if ($cacheManager && file_exists($fileSource) && !empty ($fileTarget)) {
  153. $copied = array();
  154. if ($preExistingMode === xPDOTransport::PRESERVE_PREEXISTING && file_exists($fileTargetPath)) {
  155. $transport->xpdo->log(xPDO::LOG_LEVEL_INFO, "Attempting to preserve files at {$fileTargetPath} into archive {$preservedArchive}");
  156. $preserved = xPDOTransport::_pack($transport->xpdo, $preservedArchive, $fileTarget, $fileName);
  157. }
  158. if (is_dir($fileSource)) {
  159. $copied = $cacheManager->copyTree($fileSource, $fileTarget, array_merge($options, array('copy_return_file_stat' => true)));
  160. } elseif (is_file($fileSource)) {
  161. $copied = $cacheManager->copyFile($fileSource, $fileTarget, array_merge($options, array('copy_return_file_stat' => true)));
  162. }
  163. if (empty($copied)) {
  164. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy {$fileSource} to {$fileTargetPath}");
  165. } else {
  166. if ($preExistingMode === xPDOTransport::PRESERVE_PREEXISTING && is_array($copied)) {
  167. foreach ($copied as $copiedFile => $stat) {
  168. if (isset($stat['overwritten'])) $transport->_preserved[$options['guid']]['files'][substr($copiedFile, strlen($fileTarget))]= $stat;
  169. }
  170. }
  171. $resolved = true;
  172. }
  173. } else {
  174. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy {$fileSource} to {$fileTargetPath}");
  175. }
  176. break;
  177. case xPDOTransport::ACTION_UNINSTALL: /* if package is uninstalling
  178. user can override whether or not files from resolver are removed
  179. however default action is to remove */
  180. if (!isset($options[xPDOTransport::RESOLVE_FILES_REMOVE]) || $options[xPDOTransport::RESOLVE_FILES_REMOVE] !== false) {
  181. $path = $fileTarget.$fileName;
  182. $transport->xpdo->log(xPDO::LOG_LEVEL_INFO,'Removing files in file resolver: '.$path);
  183. if ($cacheManager && file_exists($path)) {
  184. if (is_dir($path) && $cacheManager->deleteTree($path, array_merge(array('deleteTop' => true, 'skipDirs' => false, 'extensions' => array()), $options))) {
  185. $resolved = true;
  186. } elseif (is_file($path) && unlink($path)) {
  187. $resolved = true;
  188. } else {
  189. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR,'Could not remove files from path: '.$path);
  190. }
  191. } else {
  192. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR,'Could not find files to remove.');
  193. }
  194. } else {
  195. /* action was chosen not to remove, send log message and continue */
  196. $transport->xpdo->log(xPDO::LOG_LEVEL_INFO,'Skipping removing of files according to vehicle attributes.');
  197. $resolved = true;
  198. }
  199. if ($preExistingMode === xPDOTransport::RESTORE_PREEXISTING && file_exists($preservedArchive)) {
  200. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Attempting to restore files to {$fileTarget} from archive {$preservedArchive}");
  201. $unpackedResult = xPDOTransport::_unpack($transport->xpdo, $preservedArchive, $fileTarget);
  202. if ($unpackedResult > 0) {
  203. $resolved = true;
  204. } else {
  205. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error unpacking preserved files from archive {$preservedArchive}");
  206. }
  207. }
  208. break;
  209. }
  210. break;
  211. case 'php' :
  212. if (isset ($options[xPDOTransport::RESOLVE_PHP]) && !$options[xPDOTransport::RESOLVE_PHP]) {
  213. break;
  214. }
  215. $fileMeta = $transport->xpdo->fromJSON($body, true);
  216. $fileName = $fileMeta['name'];
  217. $fileSource = $transport->path . $fileMeta['source'];
  218. if (!$resolved = include ($fileSource)) {
  219. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOVehicle resolver failed: type php ({$fileSource})");
  220. }
  221. break;
  222. default :
  223. $transport->xpdo->log(xPDO::LOG_LEVEL_WARN, "xPDOVehicle does not support resolvers of type {$type}.");
  224. break;
  225. }
  226. }
  227. } else {
  228. $resolved = true;
  229. }
  230. return $resolved;
  231. }
  232. /**
  233. * Validate any dependencies for the object represented in this vehicle.
  234. *
  235. * @param xPDOTransport &$transport A reference to the xPDOTransport in
  236. * which this vehicle is stored.
  237. * @param xPDOObject &$object An object reference to access during
  238. * validation.
  239. * @param array $options Additional options for the validation process.
  240. * @return boolean Indicating if the validation was successful.
  241. */
  242. public function validate(& $transport, & $object, $options = array ()) {
  243. $validated = true;
  244. if (isset ($this->payload['validate'])) {
  245. foreach ($this->payload['validate'] as $rKey => $r) {
  246. $type = $r['type'];
  247. $body = $r['body'];
  248. switch ($type) {
  249. case 'php' :
  250. // if (isset ($options[xPDOTransport::VALIDATE_PHP]) && !$options[xPDOTransport::VALIDATE_PHP]) {
  251. // continue;
  252. // }
  253. $fileMeta = $transport->xpdo->fromJSON($body, true);
  254. $fileName = $fileMeta['name'];
  255. $fileSource = $transport->path . $fileMeta['source'];
  256. if (!$validated = include ($fileSource)) {
  257. if (!isset($fileMeta['silent_fail']) || !$fileMeta['silent_fail']) {
  258. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "xPDOVehicle validator failed: type php ({$fileSource})");
  259. }
  260. }
  261. break;
  262. default :
  263. $transport->xpdo->log(xPDO::LOG_LEVEL_WARN, "xPDOVehicle does not support validators of type {$type}.");
  264. break;
  265. }
  266. }
  267. } else {
  268. $validated = true;
  269. }
  270. return $validated;
  271. }
  272. /**
  273. * Put an artifact representation into this vehicle.
  274. *
  275. * @param xPDOTransport $transport The transport package hosting the
  276. * vehicle.
  277. * @param mixed &$object A reference to the artifact this vehicle will
  278. * represent.
  279. * @param array $attributes Additional attributes represented in the
  280. * vehicle.
  281. */
  282. public function put(& $transport, & $object, $attributes = array ()) {
  283. $this->payload = array_merge($this->payload, $attributes);
  284. if (!isset($this->payload['guid'])) {
  285. $this->payload['guid'] = md5(uniqid(rand(), true));
  286. }
  287. if (!isset ($this->payload['package'])) {
  288. if ($object instanceof xPDOObject) {
  289. $packageName = $object->_package;
  290. } else {
  291. $packageName = '';
  292. }
  293. $this->payload['package'] = $packageName;
  294. }
  295. if (!isset($this->payload['class'])) {
  296. $className = 'xPDOVehicle';
  297. if (is_object($object)) {
  298. if ($object instanceof xPDOObject) {
  299. $className = $object->_class;
  300. } else {
  301. $className = get_class($object);
  302. }
  303. }
  304. $this->payload['class'] = $className;
  305. }
  306. if (!isset($this->payload['signature'])) {
  307. $this->payload['signature'] = md5($this->payload['class'] . '_' . $this->payload['guid']);
  308. }
  309. if (!isset($this->payload['native_key'])) {
  310. $nativeKey = null;
  311. $nativeKeyAttr = isset($this->payload['native_key_attribute']) ? $this->payload['native_key_attribute'] : null;
  312. if (is_object($object)) {
  313. if ($object instanceof xPDOObject) {
  314. $nativeKey = $object->getPrimaryKey();
  315. } elseif (!empty($nativeKeyAttr) && isset($object->$nativeKeyAttr)) {
  316. $nativeKey = $object->$nativeKeyAttr;
  317. }
  318. } elseif (!empty($nativeKeyAttr) && isset($this->payload[$nativeKeyAttr])) {
  319. $nativeKey = $this->payload[$nativeKeyAttr];
  320. } else {
  321. $nativeKey = $this->payload['guid'];
  322. }
  323. $this->payload['native_key'] = $nativeKey;
  324. }
  325. if (isset ($attributes['validate'])) {
  326. $this->payload['validate'] = (array) $attributes['validate'];
  327. }
  328. if (isset ($attributes['resolve'])) {
  329. $this->payload['resolve'] = (array) $attributes['resolve'];
  330. }
  331. if (isset ($attributes['namespace'])) {
  332. if ($attributes['namespace'] instanceof xPDOObject) {
  333. $this->payload['namespace'] = $attributes['namespace']->get('name');
  334. } else {
  335. $this->payload['namespace'] = $attributes['namespace'];
  336. }
  337. }
  338. }
  339. /**
  340. * Store this xPDOVehicle instance into an xPDOTransport.
  341. *
  342. * @param xPDOTransport &$transport The transport to store the vehicle in.
  343. * @return boolean Indicates if the vehicle was stored in the transport.
  344. */
  345. public function store(& $transport) {
  346. $stored = false;
  347. $cacheManager = $transport->xpdo->getCacheManager();
  348. if ($cacheManager && !empty ($this->payload)) {
  349. $this->_compilePayload($transport);
  350. $content = '<?php return ';
  351. $content .= var_export($this->payload, true);
  352. $content .= ';';
  353. $this->payload['filename'] = $this->payload['signature'] . '.vehicle';
  354. $vFileName = $transport->path . $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['filename'];
  355. if (!($stored = $cacheManager->writeFile($vFileName, $content))) {
  356. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not store vehicle to file ' . $vFileName);
  357. }
  358. }
  359. return $stored;
  360. }
  361. /**
  362. * Compile necessary resources in preparation for storing the vehicle.
  363. *
  364. * @access protected
  365. * @param xPDOTransport &$transport A reference to the transport the vehicle is being stored in.
  366. */
  367. protected function _compilePayload(& $transport) {
  368. $cacheManager = $transport->xpdo->getCacheManager();
  369. if ($cacheManager) {
  370. if (isset ($this->payload['resolve']) && is_array($this->payload['resolve'])) {
  371. foreach ($this->payload['resolve'] as $rKey => $r) {
  372. $type = $r['type'];
  373. $body = array ();
  374. switch ($type) {
  375. case 'file' :
  376. $fileSource = $r['source'];
  377. $body['source'] = $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'] . '/' . $rKey . '/';
  378. $fileTarget = $transport->path . $body['source'];
  379. $body['target'] = $r['target'];
  380. $fileName = isset ($r['name']) ? $r['name'] : basename($fileSource);
  381. $body['name'] = $fileName;
  382. if (!is_writable($fileTarget)) {
  383. $cacheManager->writeTree($fileTarget);
  384. }
  385. if (file_exists($fileSource) && is_writable($fileTarget)) {
  386. $copied = false;
  387. if (is_dir($fileSource)) {
  388. $copied = $cacheManager->copyTree($fileSource, $fileTarget . $fileName);
  389. }
  390. elseif (is_file($fileSource)) {
  391. $copied = $cacheManager->copyFile($fileSource, $fileTarget . $fileName);
  392. }
  393. if (!$copied) {
  394. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not copy file from {$fileSource} to {$fileTarget}{$fileName}");
  395. $body = null;
  396. }
  397. } else {
  398. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Source file {$fileSource} is missing or {$fileTarget} is not writable");
  399. $body = null;
  400. }
  401. break;
  402. case 'php' :
  403. $fileSource = $r['source'];
  404. $scriptName = basename($fileSource, '.php');
  405. $body['source'] = $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'] . '.' . $scriptName . '.resolver';
  406. $fileTarget = $transport->path . $body['source'];
  407. $body['name'] = $scriptName;
  408. $body = array_merge($r, $body);
  409. if (!$cacheManager->copyFile($fileSource, $fileTarget)) {
  410. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Source file {$fileSource} is missing or {$fileTarget} could not be written");
  411. }
  412. break;
  413. default :
  414. $transport->xpdo->log(xPDO::LOG_LEVEL_WARN, "xPDOVehicle does not support resolvers of type {$type}.");
  415. break;
  416. }
  417. if ($body) {
  418. $this->payload['resolve'][$rKey] = array (
  419. 'type' => $type,
  420. 'body' => $transport->xpdo->toJSON($body)
  421. );
  422. } else {
  423. $this->payload['resolve'][$rKey] = null;
  424. }
  425. }
  426. }
  427. if (isset($this->payload['validate']) && is_array($this->payload['validate'])) {
  428. foreach ($this->payload['validate'] as $vKey => $v) {
  429. $type = $v['type'];
  430. $body = array ();
  431. switch ($type) {
  432. case 'php' :
  433. $fileSource = $v['source'];
  434. $scriptName = basename($fileSource, '.php');
  435. $body['source'] = $transport->signature . '/' . $this->payload['class'] . '/' . $this->payload['signature'] . '.' . $scriptName . '.validator';
  436. $fileTarget = $transport->path . $body['source'];
  437. $body['name'] = $scriptName;
  438. $body = array_merge($v, $body);
  439. if (!$cacheManager->copyFile($fileSource, $fileTarget)) {
  440. $transport->xpdo->log(xPDO::LOG_LEVEL_ERROR, "Source file {$fileSource} is missing or {$fileTarget} could not be written");
  441. }
  442. break;
  443. default :
  444. $transport->xpdo->log(xPDO::LOG_LEVEL_WARN, "xPDOVehicle does not support validators of type {$type}.");
  445. break;
  446. }
  447. if ($body) {
  448. $this->payload['validate'][$vKey] = array (
  449. 'type' => $type,
  450. 'body' => $transport->xpdo->toJSON($body)
  451. );
  452. } else {
  453. $this->payload['validate'][$vKey] = null;
  454. }
  455. }
  456. }
  457. }
  458. }
  459. }