| <?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(); |
| } |
| |
| /** |
| * This function returns an instance of Project representing the project |
| * with the provided $id. |
| * Note that this function preserves object |
| * identity: if called twice with the same value, it will return the |
| * exact same object. |
| * |
| * @deprecated Use Project::getProject($id) |
| * @return Project |
| */ |
| function getProject($id) { |
| return Project::getProject($id); |
| } |
| |
| /** |
| * |
| * @deprecated Use Project::getProject($id) |
| * @return Project |
| */ |
| function get_project($id) { |
| return Project::getProject($id); |
| } |
| |
| /** |
| * This function returns all the active projects. |
| * |
| * @deprecated Use Project::getActiveProjects() |
| * @return Project[] |
| */ |
| function getActiveProjects() { |
| return Project::getActiveProjects(); |
| } |
| |
| /** |
| * 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($function, $pre, $post) { |
| $pre(null); |
| foreach (self::getTopLevelProjects() as $project) { |
| $project->visitHierarchy($function, $pre, $post, 0); |
| } |
| $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, $post, $level) { |
| $function($this, $level); |
| $pre($this); |
| foreach ($this->getChildren() as $child) { |
| $child->visitHierarchy($function, $pre, $post, $level + 1); |
| } |
| $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']; |
| return explode("\n", $value)[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()); |
| } |
| |
| class ProjectBundlePatterns { |
| static $patterns; |
| |
| public static function getAll() { |
| self::load(); |
| return self::$patterns; |
| } |
| |
| public function getPatterns($id) { |
| self::load(); |
| return @self::$patterns[$id]; |
| } |
| |
| private static function load() { |
| if (self::$patterns !== null) return; |
| |
| self::$patterns = array(); |
| |
| foreach(Project::getActiveProjects() as $project) { |
| $id = $project->getId(); |
| if ($project->getShortId() == 'incubator') continue; |
| self::$patterns[$id] = array( |
| 'forge' => $project->getForge()->getId(), |
| 'id' => $project->getLocalId(), |
| 'shortname' => $project->getShortId(), |
| 'name' => $project->getName() |
| ); |
| } |
| |
| $mvnRegex = '([\w\-\*\.]+):([\w\-\*\.]+):([\w\-\*\.]+):([\w\-\*\.]+)'; |
| if ($fp = fopen(dirname(__FILE__) . '/../ip-check/cq-map.txt', 'r')) { |
| while ($line = fgets($fp)) { |
| if (preg_match('/\s*([a-z][a-z0-9-_.]+),\s*(\(.*\))/', $line, $matches)) { |
| $id = $matches[1]; |
| if (!isset(self::$patterns[$id])) continue; |
| $expression = $matches[2]; |
| if (!in_array($expression,self::$patterns[$id]['expressions'])) { |
| self::$patterns[$id]['expressions'][] = $expression; |
| } |
| } elseif (preg_match("/^([a-z][a-z0-9-_.]+),\\s*$mvnRegex/", $line, $matches)) { |
| $id = $matches[1]; |
| // Skip things that aren't projects. |
| if (!isset(self::$patterns[$id])) continue; |
| $groupid = $matches[2]; |
| $artifactid = $matches[3]; |
| $version = $matches[4] == 'jar' ? $matches[5] : $matches[4]; |
| $maven = "$groupid:$artifactid:$version"; |
| if (!in_array($maven, self::$patterns[$id]['maven'])) { |
| self::$patterns[$id]['maven'][] = $maven; |
| } |
| } elseif (preg_match('/^([a-z][a-z0-9-_.]+),\s*(.+\.jar)/i', $line, $matches)) { |
| $id = $matches[1]; |
| $bundle = $matches[2]; |
| if (!in_array($bundle, self::$patterns[$id]['bundles'])) { |
| self::$patterns[$id]['bundles'][] = $bundle; |
| } |
| } |
| } |
| fclose($fp); |
| } |
| } |
| } |
| |
| // Initialize the class |
| Project::init(); |
| ?> |