/** * @param Project $project */ public function compare(Project $project) { // If the project status is marked as something bad, there's nothing else // to consider. if ($this->getProjectStatus()) { switch ($this->getProjectStatus()) { case 'insecure': $project->setStatus(self::UPDATE_NOT_SECURE); break; case 'unpublished': case 'revoked': $project->setStatus(self::UPDATE_REVOKED); break; case 'unsupported': $project->setStatus(self::UPDATE_NOT_SUPPORTED); break; case 'not-fetched': $project->setStatus(self::UPDATE_NOT_FETCHED); break; default: // Assume anything else (e.g. 'published') is valid and we should // perform the rest of the logic in this function. break; } } if ($project->getStatus()) { // We already know the status for this project, so there's nothing else to // compute. Record the project status into $project_data and we're done. $project->setProjectStatus($this->getProjectStatus()); return; } // Figure out the target major version. $existing_major = $project->getExistingMajor(); $supported_majors = array(); if ($this->getSupportedMajors()) { $supported_majors = explode(',', $this->getSupportedMajors()); } elseif ($this->getDefaultMajor()) { // Older release history XML file without supported or recommended. $supported_majors[] = $this->getDefaultMajor(); } if (in_array($existing_major, $supported_majors)) { // Still supported, stay at the current major version. $target_major = $existing_major; } elseif ($this->getRecommendedMajor()) { // Since 'recommended_major' is defined, we know this is the new XML // format. Therefore, we know the current release is unsupported since // its major version was not in the 'supported_majors' list. We should // find the best release from the recommended major version. $target_major = $this->getRecommendedMajor(); $project->setStatus(self::UPDATE_NOT_SUPPORTED); } elseif ($this->getDefaultMajor()) { // Older release history XML file without recommended, so recommend // the currently defined "default_major" version. $target_major = $this->getDefaultMajor(); } else { // Malformed XML file? Stick with the current version. $target_major = $existing_major; } // Make sure we never tell the admin to downgrade. If we recommended an // earlier version than the one they're running, they'd face an // impossible data migration problem, since Drupal never supports a DB // downgrade path. In the unfortunate case that what they're running is // unsupported, and there's nothing newer for them to upgrade to, we // can't print out a "Recommended version", but just have to tell them // what they have is unsupported and let them figure it out. $target_major = max($existing_major, $target_major); $release_patch_changed = null; $patch = ''; // If the project is marked as UPDATE_FETCH_PENDING, it means that the // data we currently have (if any) is stale, and we've got a task queued // up to (re)fetch the data. In that case, we mark it as such, merge in // whatever data we have (e.g. project title and link), and move on. if ($this->getFetchStatus() == self::UPDATE_FETCH_PENDING) { $project->setStatus(self::UPDATE_FETCH_PENDING); $project->setReason('No available update data'); $project->setFetchStatus($this->getFetchStatus()); return; } // Defend ourselves from XML history files that contain no releases. if (!$this->getReleases()) { $project->setStatus(self::UPDATE_UNKNOWN); $project->setReason('No available releases found'); return; } foreach ($this->getReleases() as $version => $release) { // First, if this is the existing release, check a few conditions. if ($project->getExistingVersion() == $version) { if ($release->hasTerm('Release type') && in_array('Insecure', $release->getTerm('Release type'))) { $project->setStatus(self::UPDATE_NOT_SECURE); } elseif ($release->getStatus() == 'unpublished') { $project->setStatus(self::UPDATE_REVOKED); } elseif ($release->hasTerm('Release type') && in_array('Unsupported', $release->getTerm('Release type'))) { $project->setStatus(self::UPDATE_NOT_SUPPORTED); } } // Otherwise, ignore unpublished, insecure, or unsupported releases. if ($release->getStatus() == 'unpublished' || $release->hasTerm('Release type') && (in_array('Insecure', $release->getTerm('Release type')) || in_array('Unsupported', $release->getTerm('Release type')))) { continue; } // See if this is a higher major version than our target and yet still // supported. If so, record it as an "Also available" release. // Note: some projects have a HEAD release from CVS days, which could // be one of those being compared. They would not have version_major // set, so we must call isset first. if ($release->getVersionMajor() > $target_major) { if (in_array($release->getVersionMajor(), $supported_majors)) { if (!$project->hasAlsoAvailable($release->getVersionMajor())) { $project->addAlsoAvailable($release->getVersionMajor(), $version); $project->setRelease($version, $release); } } // Otherwise, this release can't matter to us, since it's neither // from the release series we're currently using nor the recommended // release. We don't even care about security updates for this // branch, since if a project maintainer puts out a security release // at a higher major version and not at the lower major version, // they must remove the lower version from the supported major // versions at the same time, in which case we won't hit this code. continue; } // Look for the 'latest version' if we haven't found it yet. Latest is // defined as the most recent version for the target major version. if (!$project->getLatestVersion() && $release->getVersionMajor() == $target_major) { $project->setLatestVersion($version); $project->setRelease($version, $release); } // Look for the development snapshot release for this branch. if (!$project->getDevVersion() && $release->getVersionMajor() == $target_major && $release->getVersionExtra() == Project::INSTALL_TYPE_DEV) { $project->setDevVersion($version); $project->setRelease($version, $release); } // Look for the 'recommended' version if we haven't found it yet (see // phpdoc at the top of this function for the definition). if (!$project->getRecommended() && $release->getVersionMajor() == $target_major && $release->getVersionPatch()) { if ($patch != $release->getVersionPatch()) { $patch = $release->getVersionPatch(); $release_patch_changed = $release; } if (!$release->getVersionExtra() && $patch == $release->getVersionPatch()) { $project->setRecommended($release_patch_changed->getVersion()); if ($release_patch_changed instanceof Release) { $project->setRelease($release_patch_changed->getVersion(), $release_patch_changed); } } } // Stop searching once we hit the currently installed version. if ($project->getExistingVersion() == $version) { break; } // If we're running a dev snapshot and have a timestamp, stop // searching for security updates once we hit an official release // older than what we've got. Allow 100 seconds of leeway to handle // differences between the datestamp in the .info file and the // timestamp of the tarball itself (which are usually off by 1 or 2 // seconds) so that we don't flag that as a new release. if ($project->getInstallType() == Project::INSTALL_TYPE_DEV) { if (!$project->getDatestamp()) { // We don't have current timestamp info, so we can't know. continue; } elseif ($release->getDate() && $project->getDatestamp() + 100 > $release->getDate()->getTimestamp()) { // We're newer than this, so we can skip it. continue; } } // See if this release is a security update. if ($release->hasTerm('Release type') && in_array('Security update', $release->getTerm('Release type'))) { $project->addSecurityUpdate($release->getVersion(), $release); } } // If we were unable to find a recommended version, then make the latest // version the recommended version if possible. if (!$project->getRecommended() && $project->getLatestVersion()) { $project->setRecommended($project->getLatestVersion()); } // Check to see if we need an update or not. if ($project->hasSecurityUpdates()) { // If we found security updates, that always trumps any other status. $project->setStatus(self::UPDATE_NOT_SECURE); } if ($project->getStatus()) { // If we already know the status, we're done. return; } // If we don't know what to recommend, there's nothing we can report. // Bail out early. if (!$project->getRecommended()) { $project->setStatus(self::UPDATE_UNKNOWN); $project->setReason('No available releases found'); return; } // If we're running a dev snapshot, compare the date of the dev snapshot // with the latest official version, and record the absolute latest in // 'latest_dev' so we can correctly decide if there's a newer release // than our current snapshot. if ($project->getInstallType() == Project::INSTALL_TYPE_DEV) { if ($project->getDevVersion() && $this->getRelease($project->getDevVersion())->getDate() > $this->getRelease($project->getLatestVersion())->getDate()) { $project->setLatestDev($project->getDevVersion()); } else { $project->setLatestDev($project->getLatestVersion()); } } // Figure out the status, based on what we've seen and the install type. switch ($project->getInstallType()) { case Project::INSTALL_TYPE_OFFICIAL: if ($project->getExistingVersion() == $project->getRecommended() || $project->getExistingVersion() == $project->getLatestVersion()) { $project->setStatus(self::UPDATE_CURRENT); } else { $project->setStatus(self::UPDATE_NOT_CURRENT); } break; case Project::INSTALL_TYPE_DEV: $latest = $this->getRelease($project->getLatestDev()); if (!$project->getDatestamp()) { $project->setStatus(self::UPDATE_NOT_CHECKED); $project->setReason('Unknown release date'); } elseif ($project->getDatestamp() + 100 > $latest->getDate()->getTimestamp()) { $project->setStatus(self::UPDATE_CURRENT); } else { $project->setStatus(self::UPDATE_NOT_CURRENT); } break; default: $project->setStatus(self::UPDATE_UNKNOWN); $project->setReason('Invalid info'); } }