*/ class Mage_Connect_Packager { /** * Default Config File name */ const CONFIG_FILE_NAME = 'downloader/connect.cfg'; /** * Default Cache Config File name */ const CACHE_FILE_NAME = 'downloader/cache.cfg'; /** * Install states of package */ const INSTALL_STATE_INSTALL = 'install'; const INSTALL_STATE_UPGRADE = 'upgrade'; const INSTALL_STATE_WRONG_VERSION = 'wrong_version'; const INSTALL_STATE_ALREADY_INSTALLED = 'already_installed'; const INSTALL_STATE_INCOMPATIBLE = 'incompatible'; /** * Install states messages * * @var array */ protected $installStates = array( self::INSTALL_STATE_INSTALL => 'Ready to install', self::INSTALL_STATE_UPGRADE => 'Ready to upgrade', self::INSTALL_STATE_ALREADY_INSTALLED => 'Already installed', self::INSTALL_STATE_WRONG_VERSION => 'Wrong version', ); /** * Archiver object * @var Mage_Archive */ protected $_archiver = null; /** * HTTP Client (Curl/Socket etc) * @var Mage_HTTP_IClient */ protected $_http = null; /** * Get Archiver object * * @return Mage_Archive */ public function getArchiver() { if(is_null($this->_archiver)) { $this->_archiver = new Mage_Archive(); } return $this->_archiver; } /** * Returns HTTP Client * @return Mage_HTTP_IClient|null */ public function getDownloader() { if(is_null($this->_http)) { $this->_http = Mage_HTTP_Client::getInstance(); } return $this->_http; } /** * Get config data and cache config data remotely * * @param string $ftpString * @return array */ public function getRemoteConf($ftpString) { $ftpObj = new Mage_Connect_Ftp(); $ftpObj->connect($ftpString); $cfgFile = self::CONFIG_FILE_NAME; $cacheFile = self::CACHE_FILE_NAME; $wd = $ftpObj->getcwd(); $remoteConfigExists = $ftpObj->fileExists($cfgFile); $tempConfigFile = tempnam(sys_get_temp_dir(),'conf'); if(!$remoteConfigExists) { $remoteCfg = new Mage_Connect_Config($tempConfigFile); $remoteCfg->store(); $ftpObj->upload($cfgFile, $tempConfigFile); } else { $ftpObj->get($tempConfigFile, $cfgFile); $remoteCfg = new Mage_Connect_Config($tempConfigFile); } $ftpObj->chdir($wd); $remoteCacheExists = $ftpObj->fileExists($cacheFile); $tempCacheFile = tempnam(sys_get_temp_dir(),'cache'); if(!$remoteCacheExists) { $remoteCache = new Mage_Connect_Singleconfig($tempCacheFile); $remoteCache->clear(); $ftpObj->upload($cacheFile, $tempCacheFile); } else { $ftpObj->get($tempCacheFile, $cacheFile); $remoteCache = new Mage_Connect_Singleconfig($tempCacheFile); } $ftpObj->chdir($wd); return array($remoteCache, $remoteCfg, $ftpObj); } /** * Get Cache config data remotely * * @param string $ftpString * @return array */ public function getRemoteCache($ftpString) { $ftpObj = new Mage_Connect_Ftp(); $ftpObj->connect($ftpString); $remoteConfigExists = $ftpObj->fileExists(self::CACHE_FILE_NAME); if(!$remoteConfigExists) { $configFile=tempnam(sys_get_temp_dir(),'conf'); $remoteCfg = new Mage_Connect_Singleconfig($configFile); $remoteCfg->clear(); $ftpObj->upload(self::CACHE_FILE_NAME, $configFile); } else { $configFile=tempnam(sys_get_temp_dir(),'conf'); $ftpObj->get($configFile, self::CACHE_FILE_NAME); $remoteCfg = new Mage_Connect_Singleconfig($configFile); } return array($remoteCfg, $ftpObj); } /** * Get config data remotely * * @param string $ftpString * @return array */ public function getRemoteConfig($ftpString) { $ftpObj = new Mage_Connect_Ftp(); $ftpObj->connect($ftpString); $cfgFile = self::CONFIG_FILE_NAME; $wd = $ftpObj->getcwd(); $remoteConfigExists = $ftpObj->fileExists($cfgFile); $tempConfigFile = tempnam(sys_get_temp_dir(),'conf_'); if(!$remoteConfigExists) { $remoteCfg = new Mage_Connect_Config($tempConfigFile); $remoteCfg->store(); $ftpObj->upload($cfgFile, $tempConfigFile); } else { $ftpObj->get($tempConfigFile, $cfgFile); $remoteCfg = new Mage_Connect_Config($tempConfigFile); } $ftpObj->chdir($wd); return array($remoteCfg, $ftpObj); } /** * Write Cache config remotely * * @param Mage_Connect_Singleconfig $cache * @param Mage_Connect_Ftp $ftpObj * @return void */ public function writeToRemoteCache($cache, $ftpObj) { $wd = $ftpObj->getcwd(); $ftpObj->upload(self::CACHE_FILE_NAME, $cache->getFilename()); @unlink($cache->getFilename()); $ftpObj->chdir($wd); } /** * Write config remotely * * @param Mage_Connect_Config $cache * @param Mage_Connect_Ftp $ftpObj * @throws RuntimeException */ public function writeToRemoteConfig($cache, $ftpObj) { $wd = $ftpObj->getcwd(); $ftpObj->upload(self::CONFIG_FILE_NAME, $cache->getFilename()); @unlink($cache->getFilename()); $ftpObj->chdir($wd); } /** * Remove empty directories recursively up * * @param string $dir * @param Mage_Connect_Ftp $ftp */ protected function removeEmptyDirectory($dir, $ftp = null) { if ($ftp) { if (count($ftp->nlist($dir))==0) { if ($ftp->rmdir($dir)) { $this->removeEmptyDirectory(dirname($dir), $ftp); } } } else { $content = scandir($dir); if ($content === false) return; if (count(array_diff($content, array('.', '..'))) == 0) { if (@rmdir($dir)) { $this->removeEmptyDirectory(dirname($dir), $ftp); } else { throw new RuntimeException('Failed to delete dir ' . $dir . "\r\n Check permissions"); } } } } /** * Uninstall Package * * @param string $chanName * @param string $package * @param Mage_Connect_Singleconfig $cacheObj * @param Mage_Connect_Config $configObj * @throws RuntimeException */ public function processUninstallPackage($chanName, $package, $cacheObj, $configObj) { $package = $cacheObj->getPackageObject($chanName, $package); $contents = $package->getContents(); $targetPath = rtrim($configObj->magento_root, "\\/"); $failedFiles = array(); foreach ($contents as $file) { $fileName = basename($file); $filePath = dirname($file); $dest = $targetPath . DIRECTORY_SEPARATOR . $filePath . DIRECTORY_SEPARATOR . $fileName; if(@file_exists($dest)) { if (!@unlink($dest)) { $failedFiles[] = $dest; } } $this->removeEmptyDirectory(dirname($dest)); } if (!empty($failedFiles)) { $msg = sprintf("Failed to delete files: %s \r\n Check permissions", implode("\r\n", $failedFiles)); throw new RuntimeException($msg); } $destDir = $targetPath . DS . Mage_Connect_Package::PACKAGE_XML_DIR; $destFile = $package->getReleaseFilename() . '.xml'; @unlink($destDir . DS . $destFile); } /** * Uninstall Package over FTP * * @param $chanName * @param $package * @param Mage_Connect_Singleconfig $cacheObj * @param Mage_Connect_Ftp $ftp * @throws RuntimeException */ public function processUninstallPackageFtp($chanName, $package, $cacheObj, $ftp) { $ftpDir = $ftp->getcwd(); $package = $cacheObj->getPackageObject($chanName, $package); $contents = $package->getContents(); $failedFiles = array(); foreach ($contents as $file) { $ftp->delete($file); if ($ftp->fileExists($file)) { $failedFiles[] = $file; continue; } $this->removeEmptyDirectory(dirname($file), $ftp); } if (!empty($failedFiles)) { $msg = sprintf("Failed to delete files: %s \r\n Check permissions", implode("\r\n", $failedFiles)); throw new RuntimeException($msg); } $remoteXml = Mage_Connect_Package::PACKAGE_XML_DIR . DS . $package->getReleaseFilename() . '.xml'; $ftp->delete($remoteXml); $ftp->chdir($ftpDir); } /** * Validation of mode permissions * * @param int $mode * @return int */ protected function validPermMode($mode) { $mode = intval($mode); if ($mode < 73 || $mode > 511) { return false; } return true; } /** * Return correct global dir mode in octal representation * * @param Mage_Connect_Config $config * @return int */ protected function _getDirMode($config) { if ($this->validPermMode($config->global_dir_mode)) { return $config->global_dir_mode; } else { return $config->getDefaultValue('global_dir_mode'); } } /** * Return global file mode in octal representation * * @param Mage_Connect_Config $config * @return int */ protected function _getFileMode($config) { if ($this->validPermMode($config->global_file_mode)) { return $config->global_file_mode; } else { return $config->getDefaultValue('global_file_mode'); } } /** * Convert FTP path * * @param string $str * @return string */ protected function convertFtpPath($str) { return str_replace("\\", "/", $str); } /** * Install package over FTP * * @param Mage_Connect_Package $package * @param string $file * @param Mage_Connect_Config $configObj * @param Mage_Connect_Ftp $ftp * @throws RuntimeException */ public function processInstallPackageFtp($package, $file, $configObj, $ftp) { $ftpDir = $ftp->getcwd(); $contents = $package->getContents(); $arc = $this->getArchiver(); $target = dirname($file) . DS . $package->getReleaseFilename(); if (!@mkdir($target, 0777, true)) { throw new RuntimeException("Can't create directory ". $target); } $tar = $arc->unpack($file, $target); $modeFile = $this->_getFileMode($configObj); $modeDir = $this->_getDirMode($configObj); $failedFiles = array(); foreach ($contents as $file) { $source = $tar . DS . $file; if (file_exists($source) && is_file($source)) { $args = array(ltrim($file,"/"), $source); if($modeDir||$modeFile) { $args[] = $modeDir; $args[] = $modeFile; } if (call_user_func_array(array($ftp,'upload'), $args) === false) { $failedFiles[] = $source; } } } if (!empty($failedFiles)) { $msg = sprintf("Failed to upload files: %s \r\n Check permissions", implode("\r\n", $failedFiles)); throw new RuntimeException($msg); } $localXml = $tar . Mage_Connect_Package_Reader::DEFAULT_NAME_PACKAGE; if (is_file($localXml)) { $remoteXml = Mage_Connect_Package::PACKAGE_XML_DIR . DS . $package->getReleaseFilename() . '.xml'; $ftp->upload($remoteXml, $localXml, $modeDir, $modeFile); } $ftp->chdir($ftpDir); Mage_System_Dirs::rm(array("-r",$target)); } /** * Package installation to FS * * @param Mage_Connect_Package $package * @param string $file * @param Mage_Connect_Config $configObj * @return void */ public function processInstallPackage($package, $file, $configObj) { $contents = $package->getContents(); $arc = $this->getArchiver(); $target = dirname($file) . DS . $package->getReleaseFilename(); @mkdir($target, 0777, true); $tar = $arc->unpack($file, $target); $modeFile = $this->_getFileMode($configObj); $modeDir = $this->_getDirMode($configObj); $targetPath = rtrim($configObj->magento_root, "\\/"); $packageXmlDir = $targetPath . DS . Mage_Connect_Package::PACKAGE_XML_DIR; if (!$this->isDirWritable($packageXmlDir)) { throw new RuntimeException('Directory ' . $packageXmlDir . ' is not writable. Check permission'); } $this->_makeDirectories($contents, $targetPath, $modeDir); foreach ($contents as $file) { $fileName = basename($file); $filePath = dirname($file); $source = $tar . DS . $file; $dest = $targetPath . DS . $filePath . DS . $fileName; if (is_file($source)) { @copy($source, $dest); if($modeFile) { @chmod($dest, $modeFile); } } else { @mkdir($dest, $modeDir); } } $packageXml = $tar . Mage_Connect_Package_Reader::DEFAULT_NAME_PACKAGE; if (is_file($packageXml)) { $destDir = $targetPath . DS . Mage_Connect_Package::PACKAGE_XML_DIR; $destFile = $package->getReleaseFilename() . '.xml'; $dest = $destDir . DS . $destFile; @copy($packageXml, $dest); @chmod($dest, $modeFile); } Mage_System_Dirs::rm(array("-r",$target)); } /** * Make directories * * @param array $content * @param string $targetPath * @param int $modeDir * @throws RuntimeException */ protected function _makeDirectories($content, $targetPath, $modeDir) { $failedDirs = array(); $createdDirs = array(); foreach ($content as $file) { $dirPath = dirname($file); if (is_dir($dirPath) && $this->isDirWritable($dirPath)) { continue; } if (!mkdir($targetPath . DS . $dirPath, $modeDir, true)) { $failedDirs[] = $targetPath . DS . $dirPath; } else { $createdDirs[] = $targetPath . DS . $dirPath; } } if (!empty($failedDirs)) { foreach ($createdDirs as $createdDir) { $this->removeEmptyDirectory($createdDir); } $msg = sprintf("Failed to create directory:\r\n%s\r\n Check permissions", implode("\r\n", $failedDirs)); throw new RuntimeException($msg); } } /** * Get local modified files * * @param string $chanName * @param string $package * @param Mage_Connect_Singleconfig $cacheObj * @param Mage_Connect_Config $configObj * @return array */ public function getLocalModifiedFiles($chanName, $package, $cacheObj, $configObj) { $p = $cacheObj->getPackageObject($chanName, $package); $hashContents = $p->getHashContents(); $listModified = array(); foreach ($hashContents as $file=>$hash) { if (md5_file($configObj->magento_root . DS . $file)!==$hash) { $listModified[] = $file; } } return $listModified; } /** * Get remote modified files * * @param string $chanName * @param string $package * @param Mage_Connect_Singleconfig $cacheObj * @param Mage_Connect_Ftp $ftp * @return array */ public function getRemoteModifiedFiles($chanName, $package, $cacheObj, $ftp) { $p = $cacheObj->getPackageObject($chanName, $package); $hashContents = $p->getHashContents(); $listModified = array(); foreach ($hashContents as $file=>$hash) { $localFile = uniqid("temp_remote_"); if(!$ftp->fileExists($file)) { continue; } $ftp->get($localFile, $file); if (file_exists($localFile) && md5_file($localFile)!==$hash) { $listModified[] = $file; } @unlink($localFile); } return $listModified; } /** * Get upgrades list * * @param string|array $channels * @param Mage_Connect_Singleconfig $cacheObject * @param Mage_Connect_Config $configObj * @param Mage_Connect_Rest $restObj optional * @param bool $checkConflicts * @return array */ public function getUpgradesList($channels, $cacheObject, $configObj, $restObj = null, $checkConflicts = false) { if(is_scalar($channels)) { $channels = array($channels); } if(!$restObj) { $restObj = Mage_Connect_Rest_Builder::getAdapter($configObj->protocol); } $updates = array(); foreach ($channels as $chan) { if(!$cacheObject->isChannel($chan)) { continue; } $chanName = $cacheObject->chanName($chan); $localPackages = $cacheObject->getInstalledPackages($chanName); $localPackages = $localPackages[$chanName]; if(!count($localPackages)) { continue; } $channel = $cacheObject->getChannel($chan); $uri = $channel[Mage_Connect_Singleconfig::K_URI]; $restObj->setChannel($uri); $remotePackages = $restObj->getPackagesHashed(); /** * Iterate packages of channel $chan */ $state = $configObj->preferred_state ? $configObj->preferred_state : "stable"; foreach ($localPackages as $localName=>$localData) { if(!isset($remotePackages[$localName])) { continue; } $package = $remotePackages[$localName]; $neededToUpgrade = false; $remoteVersion = $localVersion = trim($localData[Mage_Connect_Singleconfig::K_VER]); foreach ($package as $version => $s) { if($cacheObject->compareStabilities($s, $state) < 0) { continue; } if(version_compare($version, $localVersion, ">")) { $neededToUpgrade = true; $remoteVersion = $version; } if($checkConflicts) { $conflicts = $cacheObject->hasConflicts($chanName, $localName, $remoteVersion); if(false !== $conflicts) { $neededToUpgrade = false; } } } if(!$neededToUpgrade) { continue; } if(!isset($updates[$chanName])) { $updates[$chanName] = array(); } $updates[$chanName][$localName] = array("from"=>$localVersion, "to"=>$remoteVersion); } } return $updates; } /** * Get uninstall list * * @param string $chanName * @param string $package * @param Mage_Connect_Singleconfig $cache * @param Mage_Connect_Config $config * @param bool $withDepsRecursive * @return array|null */ public function getUninstallList($chanName, $package, $cache, $config, $withDepsRecursive = true) { static $level = 0; static $hash = array(); $chanName = $cache->chanName($chanName); $level++; try { $chanName = $cache->chanName($chanName); if(!$cache->hasPackage($chanName, $package)) { $level--; if($level == 0) { $hash = array(); return array('list'=>array()); } return null; } $dependencies = $cache->getPackageDependencies($chanName, $package); $data = $cache->getPackage($chanName, $package); $version = $data['version']; $hash[$chanName . "/" . $package] = array ( 'name' => $package, 'channel' => $chanName, 'version' => $version, 'packages' => $dependencies, ); if($withDepsRecursive) { $fields = array('name','channel','min','max'); foreach ($dependencies as $row) { /** * Converts an array to variables * @var $pChannel string Channel Name * @var $pName string Package Name */ foreach ($fields as $key) { $varName = "p" . ucfirst($key); $$varName = $row[$key]; } $method = __FUNCTION__; $keyInner = $pChannel . "/" . $pName; if(!isset($hash[$keyInner])) { $this->$method($pChannel, $pName, $cache, $config, $withDepsRecursive, false); } } } } catch (Exception $e) { } $level--; if(0 === $level) { $out = $this->processDepsHash($hash); $hash = array(); return array('list'=>$out); } return null; } /** * Add data to package dependencies hash array * * @param array $hash Package dependencies hash array * @param string $name Package name * @param string $channel Package chaannel * @param string $downloaded_version Package downloaded version * @param string $stability Package stability * @param string $versionMin Required package minimum version * @param string $versionMax Required package maximum version * @param string $installState Package install state * @param string $message Package install message * @param array|string $dependencies Package dependencies * @return bool */ private function addHashData(&$hash, $name, $channel, $downloaded_version = '', $stability = '', $versionMin = '', $versionMax = '', $installState = '', $message = '', $dependencies = '') { /** * When we are building dependencies tree we should base this calculations not on full key as on a * unique value but check it by parts. First part which should be checked is EXTENSION_NAME also this * part should be unique globally not per channel. */ $key = $name; $hash[$key] = array ( 'name' => $name, 'channel' => $channel, 'downloaded_version' => $downloaded_version, 'stability' => $stability, 'min' => $versionMin, 'max' => $versionMax, 'install_state' => $installState, 'message' => (isset($this->installStates[$installState]) ? $this->installStates[$installState] : '') . $message, 'packages' => $dependencies, ); return true; } /** * Get dependencies list/install order info * * @param string $chanName * @param string $package * @param Mage_Connect_Singleconfig $cache * @param Mage_Connect_Config $config * @param mixed $versionMax * @param mixed $versionMin * @param boolean $withDepsRecursive * @param boolean $forceRemote * @param Mage_Connect_Rest $rest * @return mixed */ public function getDependenciesList( $chanName, $package, $cache, $config, $versionMax = false, $versionMin = false, $withDepsRecursive = true, $forceRemote = false, $rest = null) { static $level = 0; static $_depsHash = array(); static $_deps = array(); static $_failed = array(); $install_state = self::INSTALL_STATE_INSTALL; $version = ''; $message = ''; $level++; try { $chanName = $cache->chanName($chanName); if (!$rest) { $rest = Mage_Connect_Rest_Builder::getAdapter($config->protocol); } $rest->setChannel($cache->chanUrl($chanName)); $releases = $rest->getReleases($package); if (!$releases || !count($releases)) { throw new Exception("No releases for '{$package}', skipping"); } $state = $config->preferred_state ? $config->preferred_state : 'stable'; /** * Check current package version first */ $installedPackage = $cache->getPackage($chanName, $package); if ($installedPackage && is_array($installedPackage)) { $installedRelease = array(array( 'v' => $installedPackage['version'], 's' => $installedPackage['stability'], )); $version = $cache->detectVersionFromRestArray($installedRelease, $versionMin, $versionMax, $state); } if (!$version) { $version = $cache->detectVersionFromRestArray($releases, $versionMin, $versionMax, $state); } if (!$version) { $versionState = $cache->detectVersionFromRestArray($releases, $versionMin, $versionMax); if ($versionState) { /** @var $packageInfo Mage_Connect_Package */ $packageInfo = $rest->getPackageReleaseInfo($package, $versionState); if (false !== $packageInfo) { $stability = $packageInfo->getStability(); throw new Exception("Extension is '{$stability}' please check(or change) stability settings". " on Magento Connect Manager"); } } throw new Exception("Version for '{$package}' was not detected"); } $packageInfo = $rest->getPackageReleaseInfo($package, $version); if (false === $packageInfo) { throw new Exception("Package release '{$package}' not found on server"); } $stability = $packageInfo->getStability(); /** * check is package already installed */ if ($installedPackage = $cache->isPackageInstalled($package)) { if ($chanName == $installedPackage['channel']) { /** * check versions */ if (version_compare($version, $installedPackage['version'], '>')) { $install_state = self::INSTALL_STATE_UPGRADE; } elseif (version_compare($version, $installedPackage['version'], '<')) { $version = $installedPackage['version']; $stability = $installedPackage['stability']; $install_state = self::INSTALL_STATE_WRONG_VERSION; } else { $install_state = self::INSTALL_STATE_ALREADY_INSTALLED; } } else { $install_state = self::INSTALL_STATE_INCOMPATIBLE; } } $deps_tmp = $packageInfo->getDependencyPackages(); /** * Select distinct packages grouped by name */ $dependencies = array(); foreach ($deps_tmp as $row) { if (isset($dependencies[$row['name']])) { if ($installedPackageDep = $cache->isPackageInstalled($row['name'])) { if ($installedPackageDep['channel'] == $row['channel']) { $dependencies[$row['name']]=$row; } } elseif ($config->root_channel == $row['channel']) { $dependencies[$row['name']] = $row; } } else { $dependencies[$row['name']] = $row; } } /** * When we are building dependencies tree we should base this calculations not on full key as on a * unique value but check it by parts. First part which should be checked is EXTENSION_NAME also this part * should be unique globally not per channel. */ if (self::INSTALL_STATE_INCOMPATIBLE != $install_state) { $this->addHashData($_depsHash, $package, $chanName, $version, $stability, $versionMin, $versionMax, $install_state, $message, $dependencies); } if ($withDepsRecursive && self::INSTALL_STATE_INCOMPATIBLE != $install_state) { $flds = array('name','channel','min','max'); foreach ($dependencies as $row) { /** * Converts an array to variables * @var $pChannel string Channel Name * @var $pName string Package Name * @var $pMax string Maximum version number * @var $pMin string Minimum version number */ foreach ($flds as $key) { $varName = "p" . ucfirst($key); $$varName = $row[$key]; } $method = __FUNCTION__; /** * When we are building dependencies tree we should base this calculations not on full key as * on a unique value but check it by parts. First part which should be checked is EXTENSION_NAME * also this part should be unique globally not per channel. */ $keyInner = $pName; if(!isset($_depsHash[$keyInner])) { $_deps[] = $row; $this->$method($pChannel, $pName, $cache, $config, $pMax, $pMin, $withDepsRecursive, $forceRemote, $rest); } else { $downloaded = $_depsHash[$keyInner]['downloaded_version']; $hasMin = $_depsHash[$keyInner]['min']; $hasMax = $_depsHash[$keyInner]['max']; if($pMin === $hasMin && $pMax === $hasMax) { continue; } if($cache->versionInRange($downloaded, $pMin, $pMax)) { continue; } $names = array("pMin","pMax","hasMin","hasMax"); for ($i=0, $c=count($names); $i<$c; $i++) { if(!isset($$names[$i])) { continue; } if(false !== $$names[$i]) { continue; } $$names[$i] = $i % 2 == 0 ? "0" : "999999999"; } if(!$cache->hasVersionRangeIntersect($pMin,$pMax, $hasMin, $hasMax)) { $reason = "Detected {$pName} conflict of versions: {$hasMin}-{$hasMax} and {$pMin}-{$pMax}"; unset($_depsHash[$keyInner]); $_failed[] = array( 'name'=>$pName, 'channel'=>$pChannel, 'max'=>$pMax, 'min'=>$pMin, 'reason'=>$reason ); continue; } $newMaxIsLess = version_compare($pMax, $hasMax, "<"); $newMinIsGreater = version_compare($pMin, $hasMin, ">"); $forceMax = $newMaxIsLess ? $pMax : $hasMax; $forceMin = $newMinIsGreater ? $pMin : $hasMin; $this->$method($pChannel, $pName, $cache, $config, $forceMax, $forceMin, $withDepsRecursive, $forceRemote, $rest); } } } unset($rest); } catch (Exception $e) { $_failed[] = array( 'name'=>$package, 'channel'=>$chanName, 'max'=>$versionMax, 'min'=>$versionMin, 'reason'=>$e->getMessage() ); } $level--; if($level == 0) { $out = $this->processDepsHash($_depsHash, false); $deps = $_deps; $failed = $_failed; $_depsHash = array(); $_deps = array(); $_failed = array(); return array('deps' => $deps, 'result' => $out, 'failed'=> $failed); } return null; } /** * Process dependencies hash. Makes topological sorting and gives operation order list * * @param array $depsHash * @param bool $sortReverse * @return array */ protected function processDepsHash(&$depsHash, $sortReverse = true) { $nodes = array(); $graph = new Mage_Connect_Structures_Graph(); foreach ($depsHash as $key=>$data) { $node = new Mage_Connect_Structures_Node(); $nodes[$key] =& $node; unset($data['packages']); $node->setData($data); $graph->addNode($node); unset($node); } if (count($nodes) > 1) { foreach ($depsHash as $key=>$data) { $packages = $data['packages']; foreach ($packages as $pdata) { $pName = $pdata['name']; if (isset($nodes[$key], $nodes[$pName])) { $nodes[$key]->connectTo($nodes[$pName]); } } } } if (!$graph->isAcyclic()) { throw new Exception("Dependency references are cyclic"); } $result = $graph->topologicalSort(); $sortReverse ? krsort($result) : ksort($result); $out = array(); foreach ($result as $nodes) { foreach ($nodes as $n) { /** @var $n Mage_Connect_Structures_Node */ $out[] = $n->getData(); } } unset($graph, $nodes); return $out; } /** * Check if directory writable * * @param string $dir * @return boolean */ protected function isDirWritable($dir) { if (is_dir($dir) && is_writable($dir)) { if (stripos(PHP_OS, 'win') === 0) { $dir = ltrim($dir, DIRECTORY_SEPARATOR); $file = $dir . DIRECTORY_SEPARATOR . uniqid(mt_rand()).'.tmp'; $exist = file_exists($file); $fp = @fopen($file, 'a'); if ($fp === false) { return false; } fclose($fp); if (!$exist) { unlink($file); } } return true; } return false; } }