0 && is_numeric(substr($part, 0, $dotPos))) { $version = $part; while (($part = next($exploded)) !== false) { $version .= '-' . $part; } break; } else { $name .= '-' . $part; $part = next($exploded); } } return array( $name, $version ); } /** * Compares two package versions by signature. * * @static * @param string $signature1 A package signature. * @param string $signature2 Another package signature to compare. * @return bool|int Returns -1 if the first version is lower than the second, 0 if they * are equal, and 1 if the second is lower if the package names in the provided * signatures are equal; otherwise returns false. */ public static function compareSignature($signature1, $signature2) { $value = false; $parsed1 = self::parseSignature($signature1); $parsed2 = self::parseSignature($signature2); if ($parsed1[0] === $parsed2[0]) { $value = version_compare($parsed1[1], $parsed2[1]); } return $value; } /** * Prepares and returns a new xPDOTransport instance. * * @param xPDO &$xpdo The xPDO instance accessing this package. * @param string $signature The unique signature of the package. * @param string $path Valid path to the physical transport package. * @param array $options An optional array of attributes for constructing the instance. */ public function __construct(& $xpdo, $signature, $path, array $options = array()) { $this->xpdo= & $xpdo; $this->signature= $signature; $this->path= $path; if (!empty($options) && array_key_exists(self::PACKAGE_NAME, $options) && array_key_exists(self::PACKAGE_VERSION, $options)) { $this->name= $options[self::PACKAGE_NAME]; $this->version= $options[self::PACKAGE_VERSION]; } else { $nameAndVersion= self::parseSignature($this->signature); if (count($nameAndVersion) == 2) { $this->name= $nameAndVersion[0]; $this->version= $nameAndVersion[1]; } } $xpdo->loadClass('transport.xPDOVehicle', XPDO_CORE_PATH, true, true); } /** * Get an {@link xPDOVehicle} instance from an unpacked transport package. * * @param string $objFile Full path to a payload file to import. The * payload file, when included must return a valid {@link xPDOVehicle::$payload}. * @param array $options An array of options to be applied when getting the * object. * @return xPDOVehicle The vehicle represented in the file. */ public function get($objFile, $options= array ()) { $vehicle = null; $objFile = $this->path . $this->signature . '/' . $objFile; $vehiclePackage = isset($options['vehicle_package']) ? $options['vehicle_package'] : ''; $vehiclePackagePath = isset($options['vehicle_package_path']) ? $options['vehicle_package_path'] : ''; $vehicleClass = isset($options['vehicle_class']) ? $options['vehicle_class'] : ''; if (empty($vehiclePackage)) $vehiclePackage = $options['vehicle_package'] = 'transport'; if (empty($vehicleClass)) $vehicleClass = $options['vehicle_class'] = 'xPDOObjectVehicle'; if ($className = $this->xpdo->loadClass("{$vehiclePackage}.{$vehicleClass}", $vehiclePackagePath, true, true)) { $vehicle = new $className(); if (file_exists($objFile)) { $payload = include ($objFile); if ($payload) { $vehicle->payload = $payload; } } } else { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehiclePackage}.{$vehicleClass}) could not be loaded."); } return $vehicle; } /** * Install vehicles in the package into the sponsor {@link xPDO} instance. * * @param array $options Install options to be applied to the process. * @return boolean true if the vehicles were successfully installed. */ public function install($options= array ()) { $installed= false; $saved = array(); $this->_preserved = array(); if (!is_array($options)) { $options= array(xPDOTransport::PACKAGE_ACTION => xPDOTransport::ACTION_INSTALL); } elseif (!isset($options[xPDOTransport::PACKAGE_ACTION])) { $options[xPDOTransport::PACKAGE_ACTION]= xPDOTransport::ACTION_INSTALL; } if (!empty ($this->vehicles)) { foreach ($this->vehicles as $vIndex => $vehicleMeta) { $vOptions = array_merge($options, $vehicleMeta); if ($vehicle = $this->get($vehicleMeta['filename'], $vOptions)) { $vehicleInstalled = $vehicle->install($this, $vOptions); if (!$vehicleInstalled && isset($vehicle->payload[xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL]) && !empty($vehicle->payload[xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL])) { $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."); return false; } else { $saved[$vehicle->payload['guid']] = $vehicleInstalled; } } } $this->writePreserved(); if (!empty($saved)) { $installed = true; } } else { $this->xpdo->log(xPDO::LOG_LEVEL_WARN, 'No vehicles are defined in the transport package (' . $this->signature . ') manifest for installation'); } return $installed; } /** * Uninstall vehicles in the package from the sponsor {@link xPDO} instance. * * @param array $options Uninstall options to be applied to the process. * @return boolean true if the vehicles were successfully uninstalled. */ public function uninstall($options = array ()) { $processed = array(); if (!is_array($options)) { $options= array(xPDOTransport::PACKAGE_ACTION => xPDOTransport::ACTION_UNINSTALL); } elseif (!isset($options[xPDOTransport::PACKAGE_ACTION])) { $options[xPDOTransport::PACKAGE_ACTION]= xPDOTransport::ACTION_UNINSTALL; } if (!empty ($this->vehicles)) { $this->_preserved = $this->loadPreserved(); $vehicleArray = array_reverse($this->vehicles, true); foreach ($vehicleArray as $vIndex => $vehicleMeta) { $vOptions = array_merge($options, $vehicleMeta); if ($this->xpdo->getDebug() === true) { $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Removing Vehicle: " . print_r($vOptions, true)); } if ($vehicle = $this->get($vehicleMeta['filename'], $vOptions)) { $processed[$vehicleMeta['guid']] = $vehicle->uninstall($this, $vOptions); } else { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Could not load vehicle: ' . print_r($vOptions, true)); } } } else { $this->xpdo->log(xPDO::LOG_LEVEL_WARN, 'No vehicles are defined in the transport package (' . $this->signature . ') for removal'); } $uninstalled = (array_search(false, $processed, true) === false); return $uninstalled; } /** * Wrap artifact with an {@link xPDOVehicle} and register in the transport. * * @param mixed $artifact An artifact to load into the transport. * @param array $attributes A set of attributes related to the artifact; these * can be anything from rules describing how to pack or unpack the artifact, * or any other data that might be useful when dealing with a transportable * artifact. * @return bool TRUE if the artifact is successfully registered in the transport. */ public function put($artifact, $attributes = array ()) { $added= false; if (!empty($artifact)) { $vehiclePackage = isset($attributes['vehicle_package']) ? $attributes['vehicle_package'] : ''; $vehiclePackagePath = isset($attributes['vehicle_package_path']) ? $attributes['vehicle_package_path'] : ''; $vehicleClass = isset($attributes['vehicle_class']) ? $attributes['vehicle_class'] : ''; if (empty($vehiclePackage)) $vehiclePackage = $attributes['vehicle_package'] = 'transport'; if (empty($vehicleClass)) $vehicleClass = $attributes['vehicle_class'] = 'xPDOObjectVehicle'; if ($className = $this->xpdo->loadClass("{$vehiclePackage}.{$vehicleClass}", $vehiclePackagePath, true, true)) { /** @var xPDOVehicle $vehicle */ $vehicle = new $className(); $vehicle->put($this, $artifact, $attributes); if ($added= $vehicle->store($this)) { $this->registerVehicle($vehicle); } } else { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, "The specified xPDOVehicle class ({$vehiclePackage}.{$vehicleClass}) could not be loaded."); } } return $added; } /** * Pack the {@link xPDOTransport} instance in preparation for distribution. * * @return boolean Indicates if the transport was packed successfully. */ public function pack() { if (empty($this->vehicles)) { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Attempt to pack a transport package with no vehicles.'); return false; } $this->writeManifest(); $fileName = $this->path . $this->signature . '.transport.zip'; return xPDOTransport::_pack($this->xpdo, $fileName, $this->path, $this->signature); } /** * Pack the resources from path relative to source into an archive with filename. * * @uses compression.xPDOZip OR compression.PclZip * @todo Refactor this to be implemented in a service class external to xPDOTransport. * * @param xPDO &$xpdo A reference to an xPDO instance. * @param string $filename A valid zip archive filename. * @param string $path An absolute file system path location of the resources to pack. * @param string $source A relative portion of path to include in the archive. * @return boolean True if packed successfully. */ public static function _pack(& $xpdo, $filename, $path, $source) { $packed = false; $packResults = false; $errors = array(); 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)) { if ($xpdo->getDebug() === true) { $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Using xPDOZip / native ZipArchive", null, __METHOD__, __FILE__, __LINE__); } $archive = new xPDOZip($xpdo, $filename, array(xPDOZip::CREATE => true, xPDOZip::OVERWRITE => true)); if ($archive) { $packResults = $archive->pack("{$path}{$source}", array(xPDOZip::ZIP_TARGET => "{$source}/")); $archive->close(); if (!$archive->hasError() && !empty($packResults)) { $packed = true; } else { $errors = $archive->getErrors(); } } } elseif (class_exists('PclZip') || include(XPDO_CORE_PATH . 'compression/pclzip.lib.php')) { $archive = new PclZip($filename); if ($archive) { $packResults = $archive->create("{$path}{$source}", PCLZIP_OPT_REMOVE_PATH, "{$path}"); if ($packResults) { $packed = true; } else { $errors = $archive->errorInfo($xpdo->getDebug() === true); } } } if (!$packed) { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Error packing {$path}{$source} to {$filename}: " . print_r($errors, true)); } if ($xpdo->getDebug() === true) { $xpdo->log(xPDO::LOG_LEVEL_DEBUG, "Results of packing {$path}{$source} to {$filename}: " . print_r($packResults, true), null, __METHOD__, __FILE__, __LINE__); } return $packed; } /** * Write the package manifest file. * * @return boolean Indicates if the manifest was successfully written. */ public function writeManifest() { $written = false; if (!empty ($this->vehicles)) { if (!empty($this->attributes['setup-options']) && is_array($this->attributes['setup-options'])) { $cacheManager = $this->xpdo->getCacheManager(); $cacheManager->copyFile($this->attributes['setup-options']['source'],$this->path . $this->signature . '/setup-options.php'); $this->attributes['setup-options'] = $this->signature . '/setup-options.php'; } $manifest = array( xPDOTransport::MANIFEST_VERSION => $this->manifestVersion, xPDOTransport::MANIFEST_ATTRIBUTES => $this->attributes, xPDOTransport::MANIFEST_VEHICLES => $this->vehicles ); $content = var_export($manifest, true); $cacheManager = $this->xpdo->getCacheManager(); if ($content && $cacheManager) { $fileName = $this->path . $this->signature . '/manifest.php'; $content = "writeFile($fileName, $content))) { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error writing manifest to ' . $fileName); } } } return $written; } /** * Write objects preserved during install() to file for use by uninstall(). * * @return boolean Indicates if the preserved file was successfully written. */ public function writePreserved() { $written = false; if (!empty($this->_preserved)) { $content = var_export($this->_preserved, true); $cacheManager = $this->xpdo->getCacheManager(); if ($content && $cacheManager) { $fileName = $this->path . $this->signature . '/preserved.php'; $content = "writeFile($fileName, $content))) { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error writing preserved objects to ' . $fileName); } } } return $written; } /** * Load preserved objects from the previous install(). * * @return array An array of preserved objects, or an empty array. */ public function loadPreserved() { $preserved = array(); $fileName = $this->path . $this->signature . '/preserved.php'; if (file_exists($fileName)) { $content = include($fileName); if (is_array($content)) { $preserved = $content; } else { $this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error loading preserved objects from ' . $fileName); } } return $preserved; } /** * Register an xPDOVehicle with this transport instance. * * @param xPDOVehicle &$vehicle A reference to the vehicle being registered. */ public function registerVehicle(& $vehicle) { $this->vehicles[] = $vehicle->register($this); } /** * Get an attribute of the package manifest. * * @param string $key The key of the attribute to retrieve. * @return mixed The value of the attribute or null if it is not set. */ public function getAttribute($key) { $value = null; if (array_key_exists($key, $this->attributes)) $value = $this->attributes[$key]; return $value; } /** * Set an attribute of the package manifest. * * @param string $key The key identifying the attribute to set. * @param mixed $value The value to set the attribute to. */ public function setAttribute($key, $value) { $this->attributes[$key]= $value; } /** * Get dependency requirements for this xPDOTransport. * * @param array $requires An optional array of dependent package constraints * to override/supplement those specified in the package metadata. * * @return array An array of dependency requirements for the package. */ public function getDependencies(array $requires = array()) { $requiresAttribute = $this->getAttribute('requires'); if (is_array($requiresAttribute)) { $requires = array_merge($requiresAttribute, $requires); } return $requires; } /** * Check if dependencies are satisfied for the package. * * Override this method to check implementation specific package * dependencies. This implementation only checks platform dependencies. * * @param array $options An array of options for the checks. * * @return array An array containing any unsatisfied dependencies. */ public function checkDependencies(array $options = array()) { $unsatisfied = $this->getDependencies(); return self::checkPlatformDependencies($unsatisfied); } /** * Check if any specified platform dependencies are satisfied. * * @param array $dependencies An array of dependencies to test. * * @return array An array containing any unsatisfied dependencies. */ public static function checkPlatformDependencies($dependencies) { if (is_array($dependencies)) { foreach ($dependencies as $depName => $depRequire) { switch ($depName) { case 'php': if (self::satisfies(XPDO_PHP_VERSION, $depRequire)) { unset($dependencies[$depName]); } break; default: break; } } } return $dependencies; } /** * Test if a version satisfies a version constraint. * * @param string $version The version to test. * @param string $constraint The constraint to satisfy. * * @return bool TRUE if the version satisfies the constraint; FALSE otherwise. */ public static function satisfies($version, $constraint) { $satisfied = false; $constraint = trim($constraint); if (substr($constraint, 0, 1) === '~') { $requirement = substr($constraint, 1); $constraint = ">={$requirement},<" . self::nextSignificantRelease($requirement); } if (strpos($constraint, ',') !== false) { $exploded = explode(',', $constraint); array_walk($exploded, 'trim'); $satisfies = array(); foreach ($exploded as $requirement) { $satisfies[] = self::satisfies($version, $requirement); } $satisfied = (false === array_search(false, $satisfies, true)); } elseif (($wildcardPos = strpos($constraint, '.*')) > 0) { $requirement = substr($constraint, 0, $wildcardPos + 1); $requirements = array( ">=" . $requirement, "<" . self::nextSignificantRelease($requirement) ); $satisfies = array(); foreach ($requirements as $requires) { $satisfies[] = self::satisfies($version, $requires); } $satisfied = (false === array_search(false, $satisfies, true)); } elseif (in_array(substr($constraint, 0, 1), array('<', '>', '!'))) { $operator = substr($constraint, 0, 1); $versionPos = 1; if (substr($constraint, 1, 1) === '=') { $operator .= substr($constraint, 1, 1); $versionPos++; } $requirement = substr($constraint, $versionPos); $satisfied = version_compare($version, $requirement, $operator); } elseif ($constraint === '*') { $satisfied = true; } elseif (version_compare($version, $constraint) === 0) { $satisfied = true; } return $satisfied; } /** * Get the next significant release version for a given version string. * * @param string $version A valid SemVer version string. * * @return string The next significant version for the specified version. */ public static function nextSignificantRelease($version) { $parsed = explode('.', $version, 3); if (count($parsed) > 1) array_pop($parsed); $parsed[count($parsed) - 1]++; if (count($parsed) === 1) $parsed[] = '0'; return implode('.', $parsed); } /** * Get an xPDOTransport instance from an existing package. * * @param xPDO &$xpdo A reference to an xPDO instance. * @param string $source Path to the packed transport. * @param string $target Path to unpack the transport to. * @param int $state The packed state of the transport. * * @return null|xPDOTransport An xPDOTransport instance or null. */ public static function retrieve(& $xpdo, $source, $target, $state= xPDOTransport::STATE_PACKED) { $instance= null; $signature = basename($source, '.transport.zip'); if (file_exists($source)) { if (is_writable($target)) { $manifest = xPDOTransport :: unpack($xpdo, $source, $target, $state); if ($manifest) { $instance = new xPDOTransport($xpdo, $signature, $target); if (!$instance) { $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)); } $manifestVersion = xPDOTransport :: manifestVersion($manifest); switch ($manifestVersion) { case '0.1': $instance->vehicles = xPDOTransport :: _convertManifestVer1_1(xPDOTransport :: _convertManifestVer1_0($manifest)); case '0.2': $instance->vehicles = xPDOTransport :: _convertManifestVer1_1(xPDOTransport :: _convertManifestVer1_0($manifest[xPDOTransport::MANIFEST_VEHICLES])); $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES]; break; case '1.0': $instance->vehicles = xPDOTransport :: _convertManifestVer1_1($manifest[xPDOTransport::MANIFEST_VEHICLES]); $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES]; break; default: $instance->vehicles = $manifest[xPDOTransport::MANIFEST_VEHICLES]; $instance->attributes = $manifest[xPDOTransport::MANIFEST_ATTRIBUTES]; break; } } else { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not unpack package {$source} to {$target}. SIG: {$signature}"); } } else { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not unpack package: {$target} is not writable. SIG: {$signature}"); } } else { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Package {$source} not found. SIG: {$signature}"); } return $instance; } /** * Store the package to a specified resource location. * * @todo Implement ability to store a package to a specified location, supporting various * transport methods. * @param mixed $location The location to store the package. * @return bool TRUE if the package is stored successfully. */ public function store($location) { $stored= false; if ($this->state === xPDOTransport::STATE_PACKED) {} return $stored; } /** * Unpack the package to prepare for installation and return a manifest. * * @param xPDO &$xpdo A reference to an xPDO instance. * @param string $from Filename of the archive containing the transport package. * @param string $to The root path where the contents of the archive should be extracted. This * path must be writable by the user executing the PHP process on the server. * @param integer $state The current state of the package, i.e. packed or unpacked. * @return array The manifest which is included after successful extraction. */ public static function unpack(& $xpdo, $from, $to, $state = xPDOTransport::STATE_PACKED) { $manifest= null; if ($state !== xPDOTransport::STATE_UNPACKED) { $resources = xPDOTransport::_unpack($xpdo, $from, $to); } else { $resources = true; } if ($resources) { $manifestFilename = $to . basename($from, '.transport.zip') . '/manifest.php'; if (file_exists($manifestFilename)) { $manifest= @include ($manifestFilename); } else { $xpdo->log(xPDO::LOG_LEVEL_ERROR, "Could not find package manifest at {$manifestFilename}"); } } return $manifest; } /** * Unpack a zip archive to a specified location. * * @uses compression.xPDOZip OR compression.PclZip * @todo Refactor this to be implemented in a service class external to xPDOTransport. * * @param xPDO &$xpdo A reference to an xPDO instance. * @param string $from An absolute file system location to a valid zip archive. * @param string $to A file system location to extract the contents of the archive to. * @return array|boolean An array of unpacked resources or false on failure. */ public static function _unpack(& $xpdo, $from, $to) { $resources = false; 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)) { $archive = new xPDOZip($xpdo, $from); if ($archive) { $resources = $archive->unpack($to); $archive->close(); } } elseif (class_exists('PclZip') || include(XPDO_CORE_PATH . 'compression/pclzip.lib.php')) { $archive = new PclZip($from); if ($archive) { $resources = $archive->extract(PCLZIP_OPT_PATH, $to); } } return $resources; } /** * Returns the structure version of the given manifest array. * * @static * @param array $manifest A valid xPDOTransport manifest array. * @return string Version string of the manifest structure. */ public static function manifestVersion($manifest) { $version = false; if (is_array($manifest)) { if (isset($manifest[xPDOTransport::MANIFEST_VERSION])) { $version = $manifest[xPDOTransport::MANIFEST_VERSION]; } elseif (isset($manifest[xPDOTransport::MANIFEST_VEHICLES])) { $version = '0.2'; } else { $version = '0.1'; } } return $version; } /** * Converts older manifest vehicles to 1.0 format. * * @static * @access private * @param array $manifestVehicles A structure representing vehicles from a pre-1.0 manifest * format. * @return array Vehicle definition structures converted to 1.0 format. */ protected static function _convertManifestVer1_0($manifestVehicles) { $manifest = array(); foreach ($manifestVehicles as $vClass => $vehicles) { foreach ($vehicles as $vKey => $vehicle) { $entry = array( 'class' => $vClass, 'native_key' => $vehicle['native_key'], 'filename' => $vehicle['filename'], ); if (isset($vehicle['namespace'])) { $entry['namespace'] = $vehicle['namespace']; } $manifest[] = $entry; } } return $manifest; } /** * Converts 1.0 manifest vehicles to 1.1 format. * * @static * @access private * @param array $vehicles A structure representing vehicles from a pre-1.1 manifest format. * @return array Vehicle definition structures converted to 1.1 format. */ protected static function _convertManifestVer1_1($vehicles) { $manifest = array(); foreach ($vehicles as $vKey => $vehicle) { $entry = $vehicle; if (!isset($vehicle['vehicle_class'])) { $entry['vehicle_class'] = 'xPDOObjectVehicle'; } if (!isset($vehicle['vehicle_package'])) { $entry['vehicle_package'] = 'transport'; } $manifest[] = $entry; } return $manifest; } }