| <?php | |
| /******************************************************************************* | |
| * Copyright (c) 2010, 2017 Eclipse Foundation and others. | |
| * | |
| * This program and the accompanying materials are made available under the | |
| * terms of the Eclipse Public License v. 2.0 which is available at | |
| * http://www.eclipse.org/legal/epl-2.0. | |
| * | |
| * SPDX-License-Identifier: EPL-2.0 | |
| ********************************************************************************/ | |
| require_once (dirname(__FILE__) . "/Forge.class.inc"); | |
| require_once (dirname(__FILE__) . "/License.class.inc"); | |
| require_once (dirname(__FILE__) . "/database.inc"); | |
| require_once (dirname(__FILE__) . "/debug.php"); | |
| require_once (dirname(__FILE__) . "/common.php"); | |
| define('PROJECT_LIVELINESS_NEVER_ACTIVE', 0); | |
| define('PROJECT_LIVELINESS_ACTIVE', 1); | |
| define('PROJECT_LIVELINESS_STALE', 2); | |
| define('PROJECT_LIVELINESS_INACTIVE', 2); | |
| define('PROJECT_LIVELINESS_DEAD', 4); | |
| define('PROJECT_REPO_ALL_GIT', 1); | |
| define('PROJECT_REPO_SOME_GIT', 2); | |
| define('PROJECT_REPO_NO_GIT', 3); | |
| define('PROJECT_REPO_NONE', 4); | |
| define('TRADEMARK_REGISTERED', 1); | |
| define('TRADEMARK_UNREGISTERED', 2); | |
| define('TRADEMARK_FILED', 3); | |
| define('TRADEMARK_ALL', TRADEMARK_REGISTERED | TRADEMARK_UNREGISTERED); | |
| /** | |
| * Get a project from the PMI instead of the foundation database. | |
| * Going directly to the PMI is no longer necessary now that the | |
| * implementation has been cleaned up. Now, we can just load the | |
| * project; if information is required from the PMI, it will be | |
| * loaded from there. | |
| * | |
| * @deprecated use Project::getProject() | |
| * @param string $id | |
| * a project id of the form 'technology.egit' | |
| * @return PMIProject | |
| */ | |
| function get_project_from_pmi($id) { | |
| return Project::getProject($id); | |
| } | |
| /** | |
| * Answers an array containing all top-level projects. | |
| * Only | |
| * active projects are included. | |
| * | |
| * @deprecated Use Project::getTopLevelProjects() | |
| * @return Project[] | |
| */ | |
| function getTopLevelProjects() { | |
| PMIProject::preload(); | |
| return Project::getTopLevelProjects(); | |
| } | |
| /** | |
| * Instances of the subclasses of Project represent a single Eclipse Foundation | |
| * Project. | |
| * Projects may be in any of the forges supported by the Eclipse | |
| * Foundation. | |
| * | |
| * This class has two subclasses. The ProductProxy class is basically the | |
| * entry-point; when you ask for a project, you first get one of these. These | |
| * instances are pretty cheap to make and can answer a lot of questions. When | |
| * requests are made that require more information, that information is | |
| * gathered from the PMI and results in the creation of a PMIProduct instance | |
| * (loading from the PMI is pretty expensive, so we try to avoid it when we | |
| * can). | |
| * | |
| * The entry points are all static methods on this class. Instances should | |
| * never be created using the constructors. | |
| * | |
| * Note that instances are cached as they are loaded; subsequent queries | |
| * will always return the cached instance. | |
| */ | |
| abstract class Project { | |
| static $config; | |
| static $projects = array(); | |
| /** | |
| * Initialize the class by loading the configuration from | |
| * projects.ini. The projects.ini file includes information about | |
| * how projects have moved (e.g. 'technology.ecf' became 'rt.ecf'), | |
| * information about Top Level Project specific brands, etc. | |
| */ | |
| static function init() { | |
| self::$config = parse_ini_file(dirname(__FILE__) . '/projects.ini'); | |
| } | |
| /** | |
| * Get all projects regardless of whether or not they are active | |
| * or archived. The Foundation database is used to get this information. | |
| * This implementation takes project moves that occurred in the day | |
| * when we duplicated information (e.g. there are records in the | |
| * database for 'technology.ecf' and 'rt.ecf') into consideration. | |
| * A project that has been moved will only be represented once in | |
| * this query. | |
| * | |
| * This move information is represented in the projects.ini file. | |
| * | |
| * Other pseudo projects are also skipped by this query. | |
| * | |
| * @return ProjectProxy[]|mixed[] | |
| */ | |
| public static function getAll() { | |
| // Skip all pseudo projects. | |
| $skip = array("'root'", "'foundation-internal'", "'galileo'"); | |
| // Skip all projects that were duplicated in a move operation. | |
| // TODO Consider encapsulating this access of the static field. | |
| foreach(self::$config['move'] as $from => $to) { | |
| $skip[] = "'{$from}'"; | |
| } | |
| $skip = implode(',', $skip); | |
| return self::getProjects(array( | |
| "IsComponent = 0", | |
| "(p.ProjectID not in ($skip))", | |
| "(p.ParentProjectID not in ('foundation-internal'))" | |
| )); | |
| } | |
| public static function getProject($id) { | |
| if (!isValidProjectId($id)) | |
| return null; | |
| $projects = self::getProjects(array( | |
| "(p.ProjectId = '$id')" | |
| )); | |
| if ($projects) | |
| return $projects[$id]; | |
| return null; | |
| } | |
| public static function getAllProjects($ids) { | |
| $all = array(); | |
| foreach($ids as $id) { | |
| if (isValidProjectId($id)) { | |
| $all[] = "'{$id}'"; | |
| } | |
| } | |
| $all = implode(',', $all); | |
| return self::getProjects(array( | |
| "(p.ProjectId in ($all))" | |
| )); | |
| } | |
| private static function getAlternateForgeIds() { | |
| $forges = array(); | |
| foreach (Forge::getForges() as $forge) { | |
| if (!$forge->isEclipseForge()) | |
| $forges[] = "'{$forge->getId()}'"; | |
| } | |
| return implode(',', $forges); | |
| } | |
| public static function getTopLevelProjects() { | |
| $forges = self::getAlternateForgeIds(); | |
| $where = array( | |
| "p.IsActive", | |
| "(p.ProjectID not in ('root', 'foundation-internal', 'galileo'))", | |
| "(p.ParentProjectID not in ('foundation-internal'))"); | |
| if ($forges) | |
| $where[] = "((p.ParentProjectID = 'root' and p.ProjectId not in ($forges)) or p.ParentProjectID in ($forges))"; | |
| else | |
| $where[] = "p.ParentProjectID = 'root'"; | |
| return self::getProjects($where); | |
| } | |
| public static function getActiveProjects() { | |
| $forges = self::getAlternateForgeIds(); | |
| $where = array( | |
| "p.IsActive", | |
| "(p.ProjectID not in ('root', 'foundation-internal', 'galileo'))", | |
| "(p.ParentProjectID not in ('foundation-internal'))"); | |
| if ($forges) | |
| $where[] = "(p.ProjectId not in ($forges))"; | |
| return self::getProjects($where); | |
| } | |
| public static function getProjectsForCommitter($id) { | |
| $forges = self::getAlternateForgeIds(); | |
| $where = array( | |
| "(p.ProjectID in (SELECT ProjectID FROM PeopleProjects where PersonId='$id' and Relation='CM' and InactiveDate is null))", | |
| "p.IsActive" | |
| ); | |
| if ($forges) | |
| $where[] = "(p.ProjectId not in ($forges))"; | |
| return self::getProjects($where); | |
| } | |
| /** | |
| * This function answers an array containing all those projects that | |
| * use the license with the provided id. | |
| * | |
| * @param string $id the license id as expressed in the Foundation DB (e.g. 'EPL-2.0') | |
| * @return Project[] | |
| */ | |
| public static function getProjectsWithLicense($id) { | |
| return self::getProjects(array( | |
| "p.ProjectId in (select ProjectId from ProjectLicenses where LicenseId='{$id}')", | |
| "p.IsActive" | |
| )); | |
| } | |
| public static function getSubprojects($id) { | |
| return self::getProjects(array( | |
| "(p.ParentProjectId = '$id')", | |
| "p.IsActive" | |
| )); | |
| } | |
| public static function visit($callable, $pre = null, $post = null) { | |
| if ($pre) call_user_func($pre, null); | |
| foreach (self::getTopLevelProjects() as $project) { | |
| $project->visitHierarchy($callable, $pre, $post, 0); | |
| } | |
| if ($post) call_user_func($post, null); | |
| } | |
| private static function getProjects($conditions) { | |
| $where = join(' and ', $conditions); | |
| $sql = " | |
| SELECT distinct | |
| p.ProjectId as id, p.Name as name, p.ParentProjectID as parent, p.ProjectPhase as phase, | |
| p.UrlDownload, | |
| group_concat(distinct l.LicenseId) as licenses, | |
| min(pp.ActiveDate) as provisioned_date, | |
| group_concat(distinct cm.PersonId) as committer_ids, | |
| group_concat(distinct pl.PersonId) as lead_ids | |
| FROM Projects as p | |
| left join ProjectLicenses as l on p.ProjectId=l.ProjectId | |
| left join PeopleProjects as pp on p.ProjectId=pp.ProjectId | |
| left join PeopleProjects as cm on p.ProjectId=cm.ProjectId and cm.Relation='CM' and cm.InactiveDate is null | |
| left join PeopleProjects as pl on p.ProjectId=pl.ProjectId and pl.Relation='PL' and pl.InactiveDate is null | |
| where | |
| $where | |
| group by p.ProjectId"; | |
| $projects = array(); | |
| query('foundation', $sql, null, function ($row) use (&$projects) { | |
| $id = $row['id']; | |
| if (isset(Project::$projects[$id])) { | |
| $projects[$id] = Project::$projects[$id]; | |
| } | |
| else { | |
| $project = new ProjectProxy($id, $row); | |
| Project::$projects[$id] = $project; | |
| $projects[$id] = $project; | |
| } | |
| }); | |
| // The configuration defines projects that should be removed | |
| // from query results. Remove those projects. | |
| // TODO Consider encapsulating this access of the static field. | |
| foreach(self::$config['remove'] as $remove) { | |
| unset($projects[$remove]); | |
| } | |
| return $projects; | |
| } | |
| private function __construct() { | |
| } | |
| /** | |
| * Answer the raw name of the receiver. This is | |
| * the name as it appears in the data. We almost | |
| * never want to use this directly. Instead, use | |
| * the nick name or formal name. | |
| * | |
| * @see Project::getNickName() | |
| * @see Project::getFormalName() | |
| */ | |
| public abstract function getName(); | |
| public abstract function getId(); | |
| public abstract function getParentId(); | |
| public abstract function getProjectPhase(); | |
| public abstract function getProxy(); | |
| /** | |
| * The short id is the last segment in the fully-qualified | |
| * id. e.g. the short id of 'technology.egit' is 'egit'. | |
| * | |
| * @return string | |
| */ | |
| public function getShortId() { | |
| preg_match("/([^\.]+)$/", $this->getId(), $matches); | |
| return $matches[1]; | |
| } | |
| public function getLocalId() { | |
| return $this->getForge()->getLocalProjectId($this->getId()); | |
| } | |
| public function getShortName() { | |
| return $this->getName(); | |
| } | |
| public function getChildren() { | |
| return self::getSubprojects($this->getId()); | |
| } | |
| public function getParent() { | |
| return self::getProject($this->getParentId()); | |
| } | |
| public function isInIncubationPhase() { | |
| return in_array($this->getProjectPhase(), array( | |
| 'Incubation', | |
| 'Incubation.nc' | |
| )); | |
| } | |
| /** | |
| * Components are an old concept. | |
| * Nothing is a component anymore. | |
| * | |
| * @return boolean Always false | |
| */ | |
| public function isComponent() { | |
| return false; | |
| } | |
| public function isArchived() { | |
| return $this->getProjectPhase() == 'Archived'; | |
| } | |
| public function isInIncubationConformingPhase() { | |
| return $this->getProjectPhase() == 'Incubation'; | |
| } | |
| public function isInIncubationNonConformingPhase() { | |
| return false; | |
| } | |
| public function getUrl() { | |
| $id = $this->getLocalId(); | |
| $base = $this->getForge()->getUrl(); | |
| return "{$base}/projects/{$id}"; | |
| } | |
| /** | |
| * This function answers the URL to use to get more data | |
| * about the project in JSON format, including releases, etc. | |
| */ | |
| public function getDataUrl() { | |
| if (!$forge = Forge::getForgeForProjectId($this->getId())) { | |
| return null; | |
| } | |
| $base = $forge->getUrl(); | |
| $local = $forge->getLocalProjectId($this->getId()); | |
| return "{$base}/json/project/$local"; | |
| } | |
| public function getTopLevelProject() { | |
| if ($this->isTopLevel()) | |
| return $this; | |
| if (!$parent = $this->getParent()) | |
| return $this; | |
| return $parent->getTopLevelProject(); | |
| } | |
| public function isTopLevel() { | |
| if (!$forge = $this->getForge()) | |
| return true; | |
| if ($forge->isEclipseForge()) | |
| return $this->getParentId() == 'root'; | |
| return $this->getParentId() == $forge->getId(); | |
| } | |
| public function getForge() { | |
| return Forge::getForgeForProjectId($this->getId()); | |
| } | |
| public function visitHierarchy($function, $pre = null, $post = null, $level) { | |
| call_user_func($function, $this, $level); | |
| if ($pre) call_user_func($pre, $this); | |
| foreach ($this->getChildren() as $child) { | |
| $child->visitHierarchy($function, $pre, $post, $level + 1); | |
| } | |
| if ($post) call_user_func($post, $this); | |
| } | |
| /** | |
| * This method answers the formal name of the project. If the | |
| * project's name does not include either 'Eclipse', or the | |
| * brand name associated with its Top Level Project, then the | |
| * brand name is prepended to the project name. | |
| * | |
| * Remove anything that's between parentheses. We actually | |
| * look for a space followed by an open parenthesis, because | |
| * some projects (e.g. "Eclipse e(fx)clipse") include them | |
| * in their proper name (and "Eclipse eclipse" just looks weird). | |
| * | |
| * e.g. | |
| * <li>"EGit" becomes "Eclipse EGit"</li> | |
| * <li>"e(fx)clipse" becomes "Eclipse e(fx)clipse"</li> | |
| * <li>"Eclipse Communication Framework (ECF)" becomes "Eclipse Communication Framework"</li> | |
| * <li>"PolarSys Capella" becomes "PolarSys Capella"</li> | |
| * <li>"Capella" becomes "PolarSys Capella"</li> | |
| * | |
| * @see Project::getNickName() | |
| * | |
| * @return string | |
| */ | |
| public function getFormalName() { | |
| $brand = $this->getBrandName(); | |
| $name = trim(preg_replace('/ \([^\)]*\)/', ' ', $this->getName())); | |
| if (!preg_match("/(?:$brand|Eclipse)/", $name)) { | |
| return "$brand $name"; | |
| } | |
| return $name; | |
| } | |
| /** | |
| * Sort out the project's brand. If the project name starts with one of our brands, | |
| * as defined in the projects.ini file, then that is the project's brand. | |
| * The project's brand might also be determined by the top-level project | |
| * (a top-level project may have a brand associated with it; in this case, | |
| * all projects that fall under that top-level project use that brand. | |
| * | |
| * @return string | |
| */ | |
| public function getBrandName() { | |
| foreach(@self::$config['brands'] as $brand) { | |
| if (preg_match("/^{$brand}/", $this->getName())) return $brand; | |
| } | |
| if ($brand = @self::$config['tlp'][$this->getTopLevelProject()->getId()]) | |
| return $brand; | |
| return 'Eclipse'; | |
| } | |
| /** | |
| * This method answers the nickname for the project. | |
| * The name of the project as listed in the foundation database is the best | |
| * source of this information. This is difficult to guess as sometimes the | |
| * forge name is part of the nick name and sometimes it's not. Values are | |
| * stored in the foundation database as the nickname with optional acronym in | |
| * parentheses. This method strips anything in parentheses from the returned | |
| * value. | |
| * | |
| * We actually look for a space followed by an open parenthesis, because | |
| * some projects (e.g. "Eclipse e(fx)clipse") include them | |
| * in their proper name (and "Eclipse eclipse" just looks weird). | |
| * | |
| * @see Project::getFormalName() | |
| * | |
| * @return string | |
| */ | |
| public function getNickName() { | |
| // We get the proxy and ask it for the name. | |
| return trim(preg_replace('/ \([^\)]*\)/', ' ', $this->getProxy()->getName())); | |
| } | |
| /** | |
| * | |
| * @deprecated | |
| * | |
| * @return NULL | |
| */ | |
| public function getNoteworthyUrl() { | |
| return null; | |
| } | |
| /** | |
| * | |
| * @deprecated | |
| * | |
| */ | |
| public function getNewsgroups() { | |
| return array(); | |
| } | |
| /** | |
| * | |
| * @deprecated | |
| * | |
| * @return null|Proposal | |
| */ | |
| public function getProposal() { | |
| // FIXME Implement. Or don't. | |
| return null; | |
| } | |
| public function getLiveliness() { | |
| if (!$this->getCommitActivity()) | |
| return PROJECT_LIVELINESS_NEVER_ACTIVE; | |
| if ($this->hasRecentCommitActivity(3)) | |
| return PROJECT_LIVELINESS_ACTIVE; | |
| if ($this->hasRecentCommitActivity(6)) | |
| return PROJECT_LIVELINESS_STALE; | |
| if ($this->hasRecentCommitActivity(12)) | |
| return PROJECT_LIVELINESS_INACTIVE; | |
| return PROJECT_LIVELINESS_DEAD; | |
| } | |
| /** | |
| * Answers whether or not the receiver has recent commit activity. | |
| * | |
| * @param int $months | |
| * (Optional) how many months in the past to check. | |
| * @return boolean | |
| */ | |
| public function hasRecentCommitActivity($months = 6) { | |
| $activity = $this->getCommitActivity(); | |
| $date = strtotime('now'); | |
| while ($months) { | |
| if (@$activity[date('Ym', $date)]) | |
| return true; | |
| $date = strtotime("-1 month", $date); | |
| $months--; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Returns the commit activity for the receiver in the form of an | |
| * array mapping period to commit count. | |
| * | |
| * e.g., | |
| * | |
| * array('201001' => 52, '201002' => 33, ...) | |
| * | |
| * Note that this information is cached. The first time this method | |
| * is called (on any instance), the cache is created. | |
| * | |
| * @returns [] array mapping period to commit count. | |
| */ | |
| public function getCommitActivity() { | |
| if (isset($this->_commitActivity)) | |
| return $this->_commitActivity; | |
| $id = $this->getId(); | |
| $sql = "select period, count from ProjectCommitActivity where project='$id'"; | |
| $this->_commitActivity = array(); | |
| query('dashboard', $sql, array(), function ($row) { | |
| $this->_commitActivity[$row['period']] = $row['count']; | |
| }); | |
| return $this->_commitActivity; | |
| } | |
| } | |
| /** | |
| * This class implements a lightweight stand-in to represent a | |
| * project. | |
| * The basic idea is that it is far less expensive to | |
| * query the Foundation Database for information about projects | |
| * than it is to query the PMI. However, the PMI has a lot more | |
| * information. Instances of this class provide as much information | |
| * as they can, but then defer to the PMI when more information is | |
| * required. | |
| * | |
| * @see Project | |
| */ | |
| class ProjectProxy extends Project { | |
| private $id; | |
| private $data; | |
| private $project; | |
| public function __construct($id, $data) { | |
| $this->id = $id; | |
| $this->data = $data; | |
| } | |
| public function __toString() { | |
| return "Project ({$this->getId()})"; | |
| } | |
| public function getId() { | |
| return $this->id; | |
| } | |
| public function getParentId() { | |
| return $this->data['parent']; | |
| } | |
| public function getProxy() { | |
| return $this; | |
| } | |
| public function getName() { | |
| return preg_replace ( '/ Root$/', '', $this->data ['name'] ); | |
| } | |
| public function getProjectPhase() { | |
| return $this->data['phase']; | |
| } | |
| /** | |
| * We don't directly track the date of project provisioning. | |
| * Rather, we leverage a feature of the provisioning process by | |
| * which the provisioning process is triggered when the first | |
| * committer is ready to be assigned to the project (this is | |
| * figured into the query that we pull from the Foundation DB). | |
| * | |
| * This function returns our best guess at the provisioning date | |
| * in UNIX date format, or null if no such date can be determined. | |
| * | |
| * @see Project::getProjects | |
| * @return NULL|number | |
| */ | |
| public function getProvisionedDate() { | |
| if (!isset($this->data['provisioned_date'])) return null; | |
| return strtotime($this->data['provisioned_date']); | |
| } | |
| public function hasProjectLeads() { | |
| return !empty($this->data['lead_ids']); | |
| } | |
| public function hasCommitters() { | |
| return !empty($this->data['committer_ids']); | |
| } | |
| public function getSpecificationWorkingGroupName() { | |
| $apiData = $this->getAPIData(); | |
| return @$apiData[0]['spec_project_working_group']['name']; | |
| } | |
| public function isSpecificationProject() { | |
| $apiData = $this->getAPIData(); | |
| return !empty($apiData[0]['spec_project_working_group']); | |
| } | |
| /** | |
| * Get the data related to this project from the Eclipse API. | |
| * | |
| * @return array|mixed | |
| */ | |
| private function getAPIData() { | |
| if (@$this->apiData === NULL) { | |
| $url = 'https://projects.eclipse.org/api/projects/' . preg_replace('/\./','_',$this->getId()); | |
| $ch = curl_init(); | |
| curl_setopt($ch, CURLOPT_URL, $url); | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
| $this->apiData = json_decode(curl_exec($ch), true); | |
| curl_close($ch); | |
| if (!$this->apiData) $this->apiData = array(); | |
| } | |
| return $this->apiData; | |
| } | |
| /** | |
| * | |
| * @deprecated | |
| * @return License[] | |
| */ | |
| public function getLicenses() { | |
| return License::getLicensesForProject($this->getId()); | |
| } | |
| /** | |
| * Answer a statement (suitable for use in a file header) | |
| * that describes the licenses of the project. | |
| * | |
| * @deprecated Use License::getLicensesStatement | |
| * @return string | |
| */ | |
| public function getLicensesStatement() { | |
| return License::getLicensesStatement($this->getLicenses()); | |
| } | |
| /** | |
| * @deprecated Use License::getSPDXLicenseExpression | |
| * @return string | |
| */ | |
| public function getSPDXLicenseExpression() { | |
| return License::getSPDXExpression($this->getLicenses()); | |
| } | |
| /** | |
| * @deprecated Use License::getDefaultFileHeader | |
| * @return string | |
| */ | |
| public function getDefaultFileHeader() { | |
| return License::getDefaultFileHeader($this->getLicenses()); | |
| } | |
| /** | |
| * @deprecated Use License::getAlternativeFileHeader | |
| * @return string | |
| */ | |
| public function getAlternativeFileHeader() { | |
| return License::getAlternativeFileHeader($this->getLicenses()); | |
| } | |
| /** | |
| * Answers an array containing the trademarks that are known | |
| * to be associated with the project. | |
| * | |
| * @return string[] | |
| */ | |
| function getTrademarks($type = TRADEMARK_ALL, $includeForge = false) { | |
| $trademarks = array(); | |
| if ($type & TRADEMARK_UNREGISTERED) { | |
| $this->trademarksDo($type, function($value) use (&$trademarks) { | |
| $trademarks[] = $value; | |
| }); | |
| } | |
| return $trademarks; | |
| } | |
| /** | |
| * Iterate over the trademarks that are known to be associated | |
| * with the project. | |
| * | |
| * The primary source of information is the name of the project | |
| * as it is represented in the Foundation Database. If the project | |
| * name includes something in parentheses, then this implementation | |
| * assumes that something is a nickname or alternative name. Or, if there | |
| * are two names separated by <space><dash><space> they are treated | |
| * as separate names. | |
| * | |
| * Further, the configuration (projects.ini) defines zero or more "brands" | |
| * (e.g. 'LocationTech' or 'PolarSys') that are connected to a particular | |
| * Top Level Project. When the brand applies to a project and is not | |
| * already represented in the name, the brand is prepended to the name. | |
| * 'Eclipse' is the default brand. | |
| * | |
| * The number of trademarks that may be associated with a project | |
| * is open-ended. The callable may be executed any number (or zero) | |
| * times. | |
| * | |
| * e.g. | |
| * <li>'LocationTech GeoGig' maps to 'LocationTech GeoGig'</li> | |
| * <li>'GeoGig' maps to 'LocationTech GeoGig' and 'GeoGig'</li> | |
| * <li>'Eclipse EMF' maps so 'EMF'</li> | |
| * <li>'EMF' maps to 'Eclipse EMF' and 'EMF'</li> | |
| * | |
| * @see Project::init() | |
| * @see Project::normalizedTrademarksDo() | |
| * | |
| * @param string $type TRADEMARK_REGISTERED | TRADEMARK_UNREGISTERED | |
| * @param callable $function Function that takes a single argument. | |
| */ | |
| function trademarksDo($type, $function) { | |
| if (preg_match('/(?:releng|website)$/', $this->getId())) return; | |
| $name = $this->getName(); | |
| // TODO Deal with different trademark types. | |
| if (preg_match('/^([^\\(]+)\s\\(([^\\)]+)\\)/', $name, $matches)) { | |
| $this->normalizedTrademarksDo($function,$matches[1]); | |
| $this->normalizedTrademarksDo($function,$matches[2]); | |
| } elseif (preg_match('/^([^-]*)\s\\-\s([^-]*)$/', $name, $matches)) { | |
| $this->normalizedTrademarksDo($function,$matches[1]); | |
| $this->normalizedTrademarksDo($function,$matches[2]); | |
| } else { | |
| $this->normalizedTrademarksDo($function,$name); | |
| } | |
| if ($extra = @$this::$config['trademarks'][$this->getId()]) { | |
| call_user_func($function, $extra); | |
| } | |
| } | |
| /** | |
| * Execute the function with each of the normalized trademarks extracted | |
| * from a term. The normalized trademarks for a particular | |
| * term are the term itself, along with the term prefixed | |
| * (if it does not already include) with the corresponding brand. | |
| * | |
| * e.g. | |
| * <li>"EclipseLink" normalizes as "EclipseLink" | |
| * <li>"EGit" normalizes as "EGit" and "Eclipse EGit" | |
| * <li>"Capella" normalizes as "Capella" and "PolarSys Capella" | |
| * | |
| * This script uses the branding information provided in | |
| * the project.ini file to map the Top Level project of the | |
| * receiver to a brand (default is "Eclipse"). | |
| * | |
| * @see Project::getBrandName() | |
| * @see Project::getFormalName() | |
| * @see ProjectProxy::trademarksDo() | |
| * | |
| * @param callable $function | |
| * @param string $term The project name (e.g. 'EGit') | |
| */ | |
| private function normalizedTrademarksDo($function, $term) { | |
| $term = trim($term); | |
| if (empty($term)) return; | |
| $term = preg_replace('/\s?\\[[^\\]]*\\]/','', $term); | |
| // First check to see if the name uses one of the general brands; | |
| // if so, then that's the project name. When one of the general | |
| // brands does not apply, check for use of the TLP-specific | |
| // brand; when that brand is not present, both the name from the | |
| // database and that name prepended with the TLP-specific brand | |
| // are considered trademarks. | |
| $brands = @self::$config['brands']; | |
| $brands=implode('|', $brands); | |
| if (preg_match("/^(?:{$brands})/", $term)) { | |
| call_user_func($function, "{$brand} {$term}"); | |
| } else { | |
| $brand = $this->getBrandName(); | |
| if (!preg_match("/(?:{$brand})/", $term)) { | |
| call_user_func($function, "{$brand} {$term}"); | |
| } | |
| call_user_func($function, $term); | |
| } | |
| return; | |
| } | |
| public function getDownloadUri() { | |
| if (!$url = $this->data['UrlDownload']) | |
| return null; | |
| // TODO We should probably ask the forge for the downloadUrl | |
| $forge = $this->getForge()->getId(); | |
| if (!preg_match("/^https?:\/\/download.{$forge}.org\/(.*)$/", $url, $matches)) | |
| return null; | |
| return $matches[1]; | |
| } | |
| public function __call($name, $arguments) { | |
| if (!$project = $this->getPMIProject()) | |
| return null; | |
| return call_user_func_array(array( | |
| $project, | |
| $name | |
| ), $arguments); | |
| } | |
| private function getPMIProject() { | |
| if ($this->project === null) { | |
| $project = PMIProject::getProject($this->getId()); | |
| $this->project = $project ? $project : FALSE; | |
| } | |
| return $this->project; | |
| } | |
| } | |
| /** | |
| * Representation of an Eclipse Foundation project with information | |
| * obtained from a PMI instance. | |
| * Instances of this class should rarely | |
| * be made directly; use the static methods on Project to obtain | |
| * instances. | |
| * | |
| * @see Project | |
| */ | |
| class PMIProject extends Project { | |
| private $data; | |
| private $forge; | |
| private static $cache = array(); | |
| /** | |
| * Obtain an representation of a project from a PMI instance. | |
| * | |
| * This method should not generally be used directly. Instead, | |
| * use the static methods on Project to obtain a reference to a project | |
| * and that reference will make this call if it is required. | |
| * | |
| * @param string $id | |
| * @return NULL|PMIProject | |
| */ | |
| public static function getProject($id) { | |
| if (isset(self::$cache[$id])) | |
| return self::$cache[$id]; | |
| if (!$forge = Forge::getForgeForProjectId($id)) | |
| return null; | |
| $base = $forge->getUrl(); | |
| $local = $forge->getLocalProjectId($id); | |
| $url = "{$base}/json/project/$local"; | |
| $ch = curl_init(); | |
| curl_setopt($ch, CURLOPT_URL, $url); | |
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | |
| $json = curl_exec($ch); | |
| curl_close($ch); | |
| if (!$json) | |
| return null; | |
| if (!$all = json_decode($json, true)) | |
| return null; | |
| if (!$data = $all['projects'][$local]) | |
| return null; | |
| return new PMIProject($data, $forge); | |
| } | |
| /** | |
| * @deprecated | |
| */ | |
| public static function preload() { | |
| } | |
| private function __construct($data, $forge) { | |
| $this->data = $data; | |
| $this->forge = $forge; | |
| } | |
| /** | |
| * Answer the project's id as it appears in the Foundation Database. | |
| * The PMI data contains the local id. If the project comes from an | |
| * alternate forge, then the forge id is prepended to the local value. | |
| * | |
| * @return string | |
| */ | |
| public function getId() { | |
| return $this->getForge()->getFoundationDBId($this->getLocalId()); | |
| } | |
| public function getLocalId() { | |
| return $this->data['id'][0]['value']; | |
| } | |
| public function getForge() { | |
| return $this->forge; | |
| } | |
| public function getProxy() { | |
| // TODO Consider caching this value | |
| return Project::getProject($this->getId()); | |
| } | |
| public function getName() { | |
| // TODO Get the name (when it's available) | |
| return $this->data['title']; | |
| } | |
| public function getDescription() { | |
| if (!$field = @$this->data['description'][0]) | |
| return null; | |
| if (!empty($field['summary'])) | |
| return $field['summary']; | |
| if (!empty($field['safe_value'])) | |
| return $field['safe_value']; | |
| $value = $field['value']; | |
| $paragraphs = explode("\n", $value); | |
| return $paragraphs[0]; | |
| } | |
| public function getScope() { | |
| if (!$field = @$this->data['scope'][0]) | |
| return null; | |
| if (!empty($field['summary'])) | |
| return $field['summary']; | |
| return @$field['safe_value']; | |
| } | |
| /** | |
| * Answer the project phase. | |
| * In the PMI, this is represented in | |
| * the "state" field. Two out of three values match what we do | |
| * in the Foundation Database. For the third, "Incubating", we | |
| * change it "Incubation" for consistency. | |
| * | |
| * @return string | |
| */ | |
| public function getProjectPhase() { | |
| $state = $this->data['state'][0]['value']; | |
| if ($state == 'Incubating') | |
| return 'Incubation'; | |
| return $state; | |
| } | |
| public function getDevListUrl() { | |
| return $this->data['dev_list']['url']; | |
| } | |
| public function getPlanUrl() { | |
| return @$this->data['plan_url'][0]['url']; | |
| } | |
| public function getWikiUrl() { | |
| return @$this->data['wiki_url'][0]['url']; | |
| } | |
| public function getDocumentationUrl() { | |
| return @$this->data['documentation_url'][0]['url']; | |
| } | |
| public function getDownloadsUrl() { | |
| return @$this->data['download_url'][0]['url']; | |
| } | |
| public function getGettingStartedUrl() { | |
| return @$this->data['gettingstarted_url'][0]['url']; | |
| } | |
| /** | |
| * Answer an array containing information about the source | |
| * repositories registered for the project. | |
| * | |
| * @return SourceRepository[] | |
| */ | |
| public function getSourceRepositories() { | |
| $repositories = array(); | |
| foreach ($this->data['source_repo'] as $repo) { | |
| $repositories[] = new SourceRepository($this, $repo); | |
| } | |
| return $repositories; | |
| } | |
| public function getBugzillaProduct() { | |
| foreach ($this->data['bugzilla'] as $record) { | |
| if (strlen($record['product']) > 0) | |
| return $record['product']; | |
| } | |
| return null; | |
| } | |
| public function getBugzillaComponents() { | |
| $components = array(); | |
| foreach ($this->data['bugzilla'] as $record) { | |
| if (strlen($record['component']) > 0) | |
| $components[] = $record['component']; | |
| } | |
| return $components; | |
| } | |
| public function getMailingLists() { | |
| // FIXME Implement this | |
| return array(); | |
| } | |
| /** | |
| * Answer the project's parent's id as it appears in the Foundation Database. | |
| * The PMI data contains the local id. If the project comes from an | |
| * alternate forge, then the forge id is prepended to the local value. | |
| * | |
| * @return string | |
| */ | |
| public function getParentId() { | |
| return $this->getForge()->getFoundationDBId(@$this->data['parent_project'][0]['id']); | |
| } | |
| /** | |
| * | |
| * @deprecated | |
| * | |
| * @return string | |
| */ | |
| function getProjectUrl() { | |
| return $this->getUrl(); | |
| } | |
| /** | |
| * Answer an array of information regarding project releases. | |
| * | |
| * @return PMIRelease[] | |
| */ | |
| function getReleases() { | |
| $releases = array(); | |
| if (isset($this->data['releases'])) { | |
| foreach ($this->data['releases'] as $data) { | |
| $releases[] = new PMIRelease($this, $data); | |
| } | |
| } | |
| return $releases; | |
| } | |
| public function getReviews() { | |
| // FIXME Consider implementing this. | |
| return array(); | |
| } | |
| /** | |
| * Answers the release that falls after a particular time. | |
| * If a date is not provided in the parameter, the current | |
| * date and time is assumed. | |
| * | |
| * @param int $time | |
| * UNIX date. | |
| */ | |
| public function getNextRelease($since = null) { | |
| if (!$since) | |
| $since = time(); | |
| $releases = $this->getReleases(); | |
| usort($releases, function ($a, $b) { | |
| return $a->getDate() == $b->getDate() ? 0 : ($a->getDate() < $b->getDate() ? -1 : 1); | |
| }); | |
| foreach ($releases as $release) { | |
| if ($release->getDate() > $since) | |
| return $release; | |
| } | |
| return null; | |
| } | |
| } | |
| class PMIRelease { | |
| private $project; | |
| private $data; | |
| function __construct($project, $data) { | |
| $this->project = $project; | |
| $this->data = $data; | |
| } | |
| public function getTitle() { | |
| return $this->data['title']; | |
| } | |
| public function getName() { | |
| return $this->getTitle(); | |
| } | |
| /** | |
| * Answers the UNIX date of the release. | |
| * | |
| * @return int | |
| */ | |
| public function getDate() { | |
| foreach ($this->data['date'] as $data) { | |
| return strtotime($data['value']); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Answers the URL for the release record. | |
| * If the information | |
| * is provided with the PMI data, then return that value. Otherwise, | |
| * make a very good guess at the value using the project URL and | |
| * the standard pattern for naming releases. | |
| * | |
| * Most of the time, the guess will be correct. But since the PMI | |
| * uses pathauto and there some cases where the project team e.g. | |
| * creates two releases with the same name, there is some risk that | |
| * the URL will be invalid. This risk is relatively low, however, | |
| * as we we usually (always?) have a URL from the PMI. | |
| * | |
| * @return string | |
| */ | |
| public function getUrl() { | |
| if ($url = $this->data['url']) | |
| return $url; | |
| $url = $this->project->getUrl(); | |
| $title = $this->getTitle(); | |
| return "$url/releases/$title"; | |
| } | |
| public function getNoteworthyUrl() { | |
| return @$this->data['noteworthy'][0]['url']; | |
| } | |
| } | |
| class SourceRepository { | |
| private $project; | |
| private $data; | |
| public function __construct($project, $data) { | |
| $this->project = $project; | |
| $this->data = $data; | |
| } | |
| /** | |
| * Answer the project that owns the reciever. | |
| * | |
| * @return Project | |
| */ | |
| public function getProject() { | |
| return $this->project; | |
| } | |
| public function getName() { | |
| return $this->data['name']; | |
| } | |
| public function getPath() { | |
| return $this->data['path']; | |
| } | |
| public function providesCommitActivity() { | |
| return true; | |
| } | |
| public function isReal() { | |
| // TODO test to see if the receiver represents a real repository. | |
| return true; | |
| } | |
| /** | |
| * Get the repository-technology specific URL for the repository. | |
| * e.g. git://git.eclipse.org/gitroot/jgit/jgit.git | |
| */ | |
| public function getUrl() { | |
| return $this->data['url']; | |
| } | |
| public function getType() { | |
| return $this->data['type']; | |
| } | |
| /** | |
| * Return an HTTP link for the receiver. | |
| * i.e. something that is suitable | |
| * for a browser | |
| */ | |
| public function getLink() { | |
| return $this->getUrl(); | |
| } | |
| } | |
| /** | |
| * Sort the array of projects by the given key. | |
| * Valid keys | |
| * are 'name', or 'top' (top level project). | |
| * | |
| * @param Project[] $projects | |
| * @param string $key | |
| */ | |
| function sortProjects(&$projects, $key = 'name') { | |
| if (!in_array($key, array( | |
| 'name', | |
| 'top' | |
| ))) | |
| $key = 'name'; | |
| return usort($projects, "_sortProjects_$key"); | |
| } | |
| /** | |
| * Sort by Project name | |
| * | |
| * @internal | |
| * | |
| * @param Project $a | |
| * @param Project $b | |
| * @return number | |
| */ | |
| function _sortProjects_name($a, $b) { | |
| return strcasecmp($a->getName(), $b->getName()); | |
| } | |
| /** | |
| * Sort project by Top Level Project. | |
| * | |
| * @internal | |
| * | |
| * @param Project $a | |
| * @param Project $b | |
| * @return number | |
| */ | |
| function _sortProjects_top($a, $b) { | |
| return strcasecmp($a->getTopLevelProject()->getName(), $b->getTopLevelProject()->getName()); | |
| } | |
| // Initialize the class | |
| Project::init(); | |
| ?> |