/** * Extracts information about this package (parses package.xml). */ protected function readPackageInfo() { // search package.xml in package archive // throw error message if not found if ($this->tar->getIndexByFilename(self::INFO_FILE) === false) { throw new SystemException("package information file '".(self::INFO_FILE)."' not found in '".$this->archive."'"); } // extract package.xml, parse with SimpleXML // and compile an array with XML::getElementTree() $xml = new XML(); try { $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE)); } catch (\Exception $e) { // bugfix to avoid file caching problems $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE)); } // parse xml $xpath = $xml->xpath(); $package = $xpath->query('/ns:package')->item(0); // package name $packageName = $package->getAttribute('name'); if (!Package::isValidPackageName($packageName)) { // package name is not a valid package identifier throw new SystemException("'".$packageName."' is not a valid package name."); } $this->packageInfo['name'] = $packageName; // get package information $packageInformation = $xpath->query('./ns:packageinformation', $package)->item(0); $elements = $xpath->query('child::*', $packageInformation); foreach ($elements as $element) { switch ($element->tagName) { case 'packagename': case 'packagedescription': case 'readme': case 'license': if (!isset($this->packageInfo[$element->tagName])) $this->packageInfo[$element->tagName] = array(); $languageCode = 'default'; if ($element->hasAttribute('language')) { $languageCode = $element->getAttribute('language'); } // fix case-sensitive names $name = $element->tagName; if ($name == 'packagename') $name = 'packageName'; else if ($name == 'packagedescription') $name = 'packageDescription'; $this->packageInfo[$name][$languageCode] = $element->nodeValue; break; case 'isapplication': $this->packageInfo['isApplication'] = intval($element->nodeValue); break; case 'packageurl': $this->packageInfo['packageURL'] = $element->nodeValue; break; case 'version': if (!Package::isValidVersion($element->nodeValue)) { throw new SystemException("package version '".$element->nodeValue."' is invalid"); } $this->packageInfo['version'] = $element->nodeValue; break; case 'date': DateUtil::validateDate($element->nodeValue); $this->packageInfo['date'] = strtotime($element->nodeValue); break; } } // get author information $authorInformation = $xpath->query('./ns:authorinformation', $package)->item(0); $elements = $xpath->query('child::*', $authorInformation); foreach ($elements as $element) { $tagName = ($element->tagName == 'authorurl') ? 'authorURL' : $element->tagName; $this->authorInfo[$tagName] = $element->nodeValue; } // get required packages $elements = $xpath->query('child::ns:requiredpackages/ns:requiredpackage', $package); foreach ($elements as $element) { if (!Package::isValidPackageName($element->nodeValue)) { throw new SystemException("'".$element->nodeValue."' is not a valid package name."); } // read attributes $data = array('name' => $element->nodeValue); $attributes = $xpath->query('attribute::*', $element); foreach ($attributes as $attribute) { $data[$attribute->name] = $attribute->value; } $this->requirements[$element->nodeValue] = $data; } // get optional packages $elements = $xpath->query('child::ns:optionalpackages/ns:optionalpackage', $package); foreach ($elements as $element) { if (!Package::isValidPackageName($element->nodeValue)) { throw new SystemException("'".$element->nodeValue."' is not a valid package name."); } // read attributes $data = array('name' => $element->nodeValue); $attributes = $xpath->query('attribute::*', $element); foreach ($attributes as $attribute) { $data[$attribute->name] = $attribute->value; } $this->optionals[] = $data; } // get excluded packages $elements = $xpath->query('child::ns:excludedpackages/ns:excludedpackage', $package); foreach ($elements as $element) { if (!Package::isValidPackageName($element->nodeValue)) { throw new SystemException("'".$element->nodeValue."' is not a valid package name."); } // read attributes $data = array('name' => $element->nodeValue); $attributes = $xpath->query('attribute::*', $element); foreach ($attributes as $attribute) { $data[$attribute->name] = $attribute->value; } $this->excludedPackages[] = $data; } // get instructions $elements = $xpath->query('./ns:instructions', $package); foreach ($elements as $element) { $instructionData = array(); $instructions = $xpath->query('./ns:instruction', $element); foreach ($instructions as $instruction) { $data = array(); $attributes = $xpath->query('attribute::*', $instruction); foreach ($attributes as $attribute) { $data[$attribute->name] = $attribute->value; } $instructionData[] = array( 'attributes' => $data, 'pip' => $instruction->getAttribute('type'), 'value' => $instruction->nodeValue ); } $fromVersion = $element->getAttribute('fromversion'); $type = $element->getAttribute('type'); if ($type == 'install') { $this->instructions['install'] = $instructionData; } else { $this->instructions['update'][$fromVersion] = $instructionData; } } // get php requirements $requirements = $xpath->query('./ns:phprequirements', $package); foreach ($requirements as $requirement) { $elements = $xpath->query('child::*', $requirement); foreach ($elements as $element) { switch ($element->tagName) { case 'version': $this->phpRequirements['version'] = $element->nodeValue; break; case 'setting': $this->phpRequirements['settings'][$element->getAttribute('name')] = $element->nodeValue; break; case 'extension': $this->phpRequirements['extensions'][] = $element->nodeValue; break; case 'function': $this->phpRequirements['functions'][] = $element->nodeValue; break; case 'class': $this->phpRequirements['classes'][] = $element->nodeValue; break; } } } // add com.woltlab.wcf to package requirements if (!isset($this->requirements['com.woltlab.wcf']) && $this->packageInfo['name'] != 'com.woltlab.wcf') { $this->requirements['com.woltlab.wcf'] = array('name' => 'com.woltlab.wcf'); } if ($this->package != null) { $validFromVersion = null; foreach ($this->instructions['update'] as $fromVersion => $update) { if (Package::checkFromversion($this->package->packageVersion, $fromVersion)) { $validFromVersion = $fromVersion; break; } } if ($validFromVersion === null) { $this->instructions['update'] = array(); } else { $this->instructions['update'] = $this->instructions['update'][$validFromVersion]; } } // set default values if (!isset($this->packageInfo['isApplication'])) $this->packageInfo['isApplication'] = 0; if (!isset($this->packageInfo['packageURL'])) $this->packageInfo['packageURL'] = ''; }
/** * Determines intermediate update steps using a backtracking algorithm in case there is no direct upgrade possible. * * @param string $package package identifier * @param array $fromversions list of all fromversions * @param string $currentVersion current package version * @param string $newVersion new package version * @return array list of update steps (old version => new version, old version => new version, ...) */ protected function findShortestUpdateThread($package, $fromversions, $currentVersion, $newVersion) { if (!isset($fromversions[$newVersion])) { throw new SystemException("An update of package " . $package . " from version " . $currentVersion . " to " . $newVersion . " is not supported."); } // find direct update foreach ($fromversions[$newVersion] as $fromversion) { if (Package::checkFromversion($currentVersion, $fromversion)) { return array($currentVersion => $newVersion); } } // find intermediate update $packageVersions = array_keys($fromversions); $updateThreadList = array(); foreach ($fromversions[$newVersion] as $fromversion) { $innerUpdateThreadList = array(); // find matching package versions foreach ($packageVersions as $packageVersion) { if (Package::checkFromversion($packageVersion, $fromversion) && Package::compareVersion($packageVersion, $currentVersion, '>') && Package::compareVersion($packageVersion, $newVersion, '<')) { $innerUpdateThreadList[] = $this->findShortestUpdateThread($package, $fromversions, $currentVersion, $packageVersion) + array($packageVersion => $newVersion); } } if (!empty($innerUpdateThreadList)) { // sort by length usort($innerUpdateThreadList, array($this, 'compareUpdateThreadLists')); // add to thread list $updateThreadList[] = array_shift($innerUpdateThreadList); } } if (empty($updateThreadList)) { throw new SystemException("An update of package " . $package . " from version " . $currentVersion . " to " . $newVersion . " is not supported."); } // sort by length usort($updateThreadList, array($this, 'compareUpdateThreadLists')); // take shortest return array_shift($updateThreadList); }
/** * Filters update instructions. */ protected function filterUpdateInstructions() { $validFromVersion = null; foreach ($this->instructions['update'] as $fromVersion => $update) { if (Package::checkFromversion($this->package->packageVersion, $fromVersion)) { $validFromVersion = $fromVersion; break; } } if ($validFromVersion === null) { $this->instructions['update'] = array(); } else { $this->instructions['update'] = $this->instructions['update'][$validFromVersion]; } }