blob: cb5f62076ff4ae00203a673bcc4f2ee49a430f50 [file] [log] [blame]
<?php
/*******************************************************************************
* Copyright (c) 2010, 2012 Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wayne Beaton (Eclipse Foundation) - initial API and implementation
* Wayne Beaton (Eclipse Foundation) - Bug 369905
*******************************************************************************/
/*
* This script assumes that it is being included by another script. We
* assume that the $App variable has already been defined.
*/
require_once(dirname(__FILE__) . "/ProjectRoot.class.php");
require_once(dirname(__FILE__) . "/debug.php");
require_once(dirname(__FILE__) . "/common.php");
trace_file_info(__FILE__);
/**
* Get a project from the PMI instead of the foundation database.
*
* @param string $id a project id of the form 'technology.egit'
* @return PMIProject
*/
function get_project_from_pmi($id) {
if (!isValidProjectId($id)) return null;
if (preg_match('/^polarsys\.(.*)$/',$id, $matches)) {
$id = $matches[1];
$url = "https://polarsys.org/json/project/$id";
} else {
$url = "http://projects.eclipse.org/json/project/$id";
}
if (!$json = file_get_contents($url)) return null;
$all = json_decode($json, true);
return new PMIProject($all['projects'][$id]);
}
/**
* PMI stand-in for Project class. The intent is that this
* is a valid subtype. The current reality is that I'm implementing
* only those bits of functionality that I'm actually using as I
* need it.
*
* @see Project
* @see get_project_from_pmi
*/
class PMIProject {
var $data;
function __construct($data) {
$this->data = $data;
}
function getId() {
// TODO Get the name (when it's available)
return $this->data['id'][0]['value'];
}
/**
* @return string
*/
function getShortId() {
preg_match("/([^\.]+)$/", $this->getId(), $matches);
return $matches[1];
}
function getName() {
// TODO Get the name (when it's available)
return $this->data['title'];
}
function getDescription() {
if (!$field = @$this->data['description'][0]) return null;
if (!empty($field['summary'])) return $field['summary'];
return $field['safe_value'];
}
function getDevListUrl() {
return $this->data['dev_list']['url'];
}
function getPlanUrl() {
return $this->data['plan_url'][0]['url'];
}
function getSourceRepositories() {
$repositories = array();
foreach($this->data['source_repo'] as $repo) {
if ($local = getSourceRepository($repo['path'], $this))
$repositories[] = $local;
}
return $repositories;
}
function getBugzillaProduct() {
foreach($this->data['bugzilla'] as $record) {
if (strlen($record['product']) > 0)
return $record['product'];
}
return null;
}
function getBugzillaComponents() {
$components = array();
foreach($this->data['bugzilla'] as $record) {
if (strlen($record['component']) > 0)
$components[] = $record['component'];
}
return $components;
}
/**
* Return the parent of the project. Note that this does
* not cache. Subsequent calls will result in an additional
* query.
*/
function getParent() {
if (!$id = @$this->data['parent_project'][0]['id']) return;
return get_project_from_pmi($id);
}
function getTopLevelProject() {
if (!$parent = $this->getParent()) return $this;
return $this->getParent()->getTopLevelProject();
}
}
/**
* @deprecated use #getProjectRoot()
*/
function get_project_root() {
return getProjectRoot();
}
/**
* Lazily create an instance of ProjectRoot; this instance will read in and hold onto
* the project information. This way, we only read it once.
*
* PRIVATE: THIS IS NOT API
*/
function getProjectRoot() {
global $_project_root;
if (!$_project_root) $_project_root = new ProjectRoot();
return $_project_root;
}
/**
* Clear the project cache. Call this method if you need to reload the
* projects for any reason, or if you just want to try and reclaim some
* space (though I remain skeptical about PHP's garbage collection).
*/
function clear_project_cache() {
global $_project_root;
unset($_project_root);
}
/**
* Force projects to be preloaded. You might use this behaviour
* to override the default lazy behaviour (which is to load only
* the active projects) thereby including inactive projects in
* later queries.
*
* This function is no longer required. Projects load on demand;
* there is only one way to load.
*
* @deprecated
* @param bool $activeOnly Load only the active projects (default)
*/
function preloadProjects($activeOnly = true) {
_loadAllProjects($activeOnly);
}
/**
* Do the actual load of the projects. Projects are cached.
*
* PRIVATE: THIS IS NOT API
*
* @deprecated This function is unnecessary.
* @internal
* @param bool $activeOnly Load only the active projects (default)
*/
function _loadAllProjects($activeOnly = true) {
global $_project_root;
$_project_root = new ProjectRoot($activeOnly);
}
/**
* Answers an array containing all top-level projects. Only
* active projects are included.
*
* @return Project[]
*/
function getTopLevelProjects() {
return getProjectRoot()->getTopLevelProjects();
}
/**
* @deprecated
* @see #getTopLevelProjects()
*/
function get_project_hierarchy() {
return 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.
*
* @return Project
*/
function getProject($id) {
return getProjectRoot()->getProject($id);
}
/**
* @deprecated use #getProject($id)
* @return Project
*/
function get_project($id) {
return getProjectRoot()->get_project($id);
}
/**
* This function returns all the active projects.
* @return Project[]
*/
function getActiveProjects() {
return getProjectRoot()->getActiveProjects();
}
/**
* @deprecated use #getActiveProjects()
* @return Project[]
*/
function get_all_projects() {
return getActiveProjects();
}
/**
* This function returns an array containing those projects that have
* declared participation in the simultaneous release with the provided
* name.
*
* @param string $name
*/
function getAllProjectsInSimultaneousRelease($name) {
$all = get_all_projects();
$projects = array();
foreach($all as $project) {
/* @var $project Project */
if (in_array($name, $project->getSimultaneousReleaseNames())) $projects[] = $project;
}
return $projects;
}
/*
* List of field names in the database.
*
* PRIVATE: THIS FIELD IS NOT API.
*/
$project_fields = array('ProjectId', 'Name','Level','ParentProjectID','Description',
'UrlDownload','UrlIndex','DateActive','SortOrder','IsActive',
'BugsName','ProjectPhase','DiskQuotaGB','IsComponent','IsStandard');
$project_fields_string = implode(',', $project_fields);
define('PROJECT_LIVELINESS_ACTIVE', 1);
define('PROJECT_LIVELINESS_STALE', 2);
define('PROJECT_LIVELINESS_DEAD', 3);
define('PROJECT_REPO_ALL_GIT', 1);
define('PROJECT_REPO_SOME_GIT', 2);
define('PROJECT_REPO_NO_GIT', 3);
define('PROJECT_REPO_NONE', 4);
class Project {
/**
* @internal
* @var ProjectRoot
*/
var $root;
/**
* Project information from the 'Foundation' database
* @internal
*/
var $data;
/**
* Values from the Eclipse database
* @internal
*/
var $values = array();
/**
* @internal
* @var Project
*/
var $parent;
/**
* @internal
* @var Project[]
*/
var $children = array();
/**
* Keep track of the people associated with the project. This
* field is lazy-initialized; use the getPeople() method to access.
*
* @internal
*/
var $people = null;
/**
* An array containing the the projects that the receiver has
* been moved from (e.g., if the project moved from a->b->c, then
* project c would list (a,b) in this field).
*
* @internal
* @var Project[]
*/
var $movedFrom = array();
/**
* @param Project $root
* @param mixed $data
*/
function __construct($root, $data) {
$this->root = $root;
$this->data = $data;
}
/**
* @return string
*/
function getId() {
return $this->data['ProjectId'];
}
/**
* @return Project
*/
function getParent() {
return $this->parent;
}
function getUrl() {
return "http://projects.eclipse.org/projects/" . $this->getId();
}
/**
* @return string
*/
function getShortId() {
preg_match("/([^\.]+)$/", $this->getId(), $matches);
return $matches[1];
}
/**
* Answer the project's name as it appears in the Project metadata.
* If the project metadata has not been specified, get the value
* from the Eclipse Foundation Database. If the name has not been
* specified, the ID is answered instead.
*
* @return string
*/
function getName() {
if ($name = $this->findFirstValue('projectname')) return $name;
if ($name = $this->findFirstValue('projectshortname')) return $name;
return $this->data['Name'] ? $this->data['Name'] : $this->getId();
}
function getTopLevelProject() {
if (!$this->parent) return $this;
return $this->parent->getTopLevelProject();
}
function getSiblings() {
return $this->parent ? $this->parent->getChildren() : getTopLevelProjects();
}
/**
* Answer the project's name as it appears in the developer portal
* (this value is set by the project's commmitters). If the projectname
* field has not been specified, the project's name as it appears
* in the Eclipse Foundation Database is answered instead.
*
* @return string
*/
function getProjectName() {
$name = $this->findFirstValue('projectname');
if ($name) return $name;
return $this->getName();
}
/**
* Answer the short name of the project as entered in the
* 'projectshortname' field in the portal. If the value is
* not set in the portal, the receiver's name is returned instead.
*
* @return string
*/
function getShortName() {
$name = $this->findFirstValue('projectshortname');
return $name ? $name : $this->getName();
}
/**
* Note that this is a fairly expensive operation. We assume that
* this function will be called at most once in any session. The current
* implementation does not cache the results, so the caller should
* be careful to avoid subsequent calls (or change this implementation).
*
* @return string
*/
function getDescription() {
$id = $this->getId();
$raw = $this->getDescriptionUrl();
$url = normalizeFilePathUrl($raw);
if (!$url) {
trace("Description URL for $id not provided.");
return null;
}
if (preg_match('/\.php$/', $url)) {
trace("Ignoring PHP description URL for $id.");
return null;
}
trace("Description URL for $id is $raw, normalizes to $url.");
$description = @file_get_contents($url);
if (!$description) {
trace("Description for $id not found.");
return null;
}
$description = $this->cleanDescription($description);
if(!trim($description)) {
trace("Description for $id is empty.");
return null;
}
return $description;
}
/**
* The short description is an abridged version of the description. We first
* try to obtain the description using the #getParagraph method; if this does
* not return a value, we grab the description (assuming that it is available).
* Whatever value we start from, we strip out any HTML tags and limit the size
* to 100 words. Results vary based on the quality of the input :-(
*
* Note that this is a fairly expensive operation. We assume that
* this function will be called at most once in any session. The current
* implementation does not cache the results, so the caller should
* be careful to avoid subsequent calls (or change this implementation).
*
* @return string
*/
function getShortDescription() {
$description = $this->getParagraph();
if (!$description) $description = $this->getDescription();
return $this->extractShortDescription($description);
}
/**
* Answer a paragraph describing the receiver, extracted from
* the file referenced in the 'paragraphurl' field in the portal,
* or <code>null</code> if the field is not specified.
*
* @return string|null
*/
function getParagraph() {
$id = $this->getId();
$raw = $this->getParagraphUrl();
$url = normalizeFilePathUrl($raw);
if (!$url) {
trace("Paragraph URL for $id not provided.");
return null;
}
trace("Paragraph URL for $id is $raw, normalizes to $url.");
$paragraph = @file_get_contents($url);
if (!$paragraph) {
trace("Paragraph for $id not found.");
return null;
}
if(!trim($paragraph)) {
trace("Paragraph for $id is empty.");
return null;
}
return $paragraph;
}
/**
* Return the URL for the project. This information is
* represented in both the portal and the Foundation database.
* The value provided by a project committer in the metadata
* (portal) overrides any value that exists in the Foundation
* Database.
*
* @string
*/
function getProjectUrl() {
$url = $this->findFirstValue('projecturl');
if ($url) return $url;
return $this->data['UrlIndex'];
}
function getPlanUrl() {
$plan = $this->findFirstValue('projectplanurl');
if (preg_match('/^http:\/\/(www\.)?eclipse\.org\/projects\/project-plan\.php\?planurl=(.+\.xml)$/', $plan, $matches)) {
return $matches[2];
}
return $plan;
}
function getWikiUrl() {
return $this->findFirstValue('wikiurl');
}
function getIpLogUrl() {
return $this->findFirstValue('iplogurl');
}
function getLogoUrl() {
return $this->findFirstValue('logourl');
}
function getDocumentationUrl() {
return $this->findFirstValue('documentationurl');
}
function getDescriptionUrl() {
if ($url = $this->findFirstValue('descriptionurl')) return $url;
if ($root = $this->getWebRoot()) return "$root/description.html";
return null;
}
/**
* Answers the root directory for the website of the project.
*/
function getWebRoot() {
$short = end(preg_split('/\./', $this->getId()));
if (file_exists(normalizeFilePathUrl($short))) return $short;
return null;
}
function getParagraphUrl() {
return $this->findFirstValue('paragraphurl');
}
function getDownloadsUrl() {
return $this->findFirstValue('downloadsurl');
}
function getGettingStartedUrl() {
return $this->findFirstValue('gettingstartedurl');
}
/**
* @return String
*/
function getBugzillaProduct() {
return $this->findFirstValue('bugzilla', 'productname');
}
/**
* Return an array containing the component names for the project.
* If no components are specified for this project, then the
* result is an empty array.
*
* Components are stored in an single string. Each component is
* separated from the next using a comma. We try to be as forgiving
* as possible here and trim out leading or trailing spaces.
* Since I'm not sure exactly what limitations are placed on
* component names, this is probably the best we can do.
*
* @return String[]
*/
function getBugzillaComponents() {
$value = $this->findFirstValue('bugzilla', 'components');
if (!$value) return array();
$components = array();
$values = split(',', $value);
foreach($values as $component) {
$components[] = trim($component);
}
return $components;
}
function getBugzillaUrl() {
return $this->findFirstValue('bugzilla', 'url');
}
/**
*
* @return Review[]
*/
function getReviews() {
require_once(dirname(__FILE__) . "/Review.class.php");
return getReviewsForProject($this->getId());
}
function getProposal() {
require_once(dirname(__FILE__) . "/Proposal.class.php");
return getProposalForProject($this->getId());
}
function getScope() {
$proposal = $this->getProposal();
if (!$proposal) return;
return $proposal->getScope();
}
/**
* Answer the creation review of the receiver or null if
* the creation review cannot be found. If $checkParent
* (optional) is true (default), then we assume that--if
* the receiver does not have its own creation review--it
* shares creation with its parent.
*
* @return Review|null
*/
function getCreationReview($checkParent = true) {
foreach($this->getReviews() as $review) {
if ($review->isCreation()) return $review;
}
if (!$checkParent) return null;
if (!$this->parent) return null;
return $this->parent->getCreationReview();
}
/**
* Answer the termination review of the receiver or null if
* the creation review cannot be found.
*
* @return Review|null
*/
function getTerminationReview() {
foreach($this->getReviews() as $review) {
if ($review->isTermination()) return $review;
}
return null;
}
// At some point, we may consider an approach along these lines.
// For now, it's better to separate methods for each getter to
// better allow for code completion to work.
//
// function __call($name, $parameters) {
// global $projectAPI;
//
// $matches = array();
// if (preg_match('/^get([_\d\w]*)$/')) {
// $metadata = $projectAPI->findMetaDataForProjectInfo($matches[1]);
// $value = $this->findFirstValue($metadata['mainkey']);
//
// }
//
// return parent::__call($name, $description);
// }
/**
*
* @return Newsgroup[]
*/
function getNewsgroups() {
return $this->getProjectInfoValues('newsgroup');
}
function getMailingLists() {
return $this->getProjectInfoValues('mailinglist');
}
/**
* A project has releases if (a) there is at least one
* release documented in the metadata, and (b) there is no
* documented release with 'noreleases' as its name.
*
* @return bool
*/
function hasReleases() {
return $this->getReleases() ? true : false;
}
/**
* Answers the collection of releases made by the project sorted
* in ascending order (i.e. the oldest release is first).
*
* @return Release[]
*/
function getReleases() {
$releases = array();
foreach ($this->getProjectInfoValues('release') as $data) {
$release = new Release($this, $data);
if (!$release->isValid()) continue;
$releases[$release->getName()] = $release;
}
foreach($this->movedFrom as $project) {
$trace = trace("Adding releases from " . $project->getId());
foreach($project->getReleases() as $release) {
//if (!isset($releases[$release->getName()]))
$releases[$release->getName()] = $release;
//else
$trace->trace("Adding " . $release->getName());
}
}
usort($releases, array($this, 'sortReleases'));
return $releases;
}
/**
* This function is used by #renderReleases.
*
* @internal
* @param Release $release1
* @param Release $release2
*/
function sortReleases($release1, $release2) {
$date1 = $release1->getDate();
$date2 = $release2->getDate();
if ($date1 == $date2) return 0;
return $date1 < $date2 ? -1 : 1;
}
/**
* @param $date timestamp
* @return Release|null
*/
function getReleaseOnDate($date) {
$string = date('Y-m-d', $date);
$releases = $this->getReleases();
foreach ($releases as $release) {
/* @var $release Release */
if ($release->getDate() == $string) return $release;
}
return null;
}
/**
* @return Release
*/
function getGraduationRelease() {
$releases = array();
foreach($this->getReleases() as $release) {
if (preg_match('/(\d+)(\.\d+){1,2}/', $release->getName(), $matches)) {
$major = $matches[1];
if ((int)$major > 0) return $release;
}
}
return null;
}
/**
* @param $name string
* @return Release|null
*/
function getReleaseNamed($name) {
$releases = $this->getReleases();
foreach ($releases as $release) {
/* @var $release Release */
if (strcasecmp($release->getName(), $name) == 0) return $release;
}
return null;
}
function hasCommitActivity() {
foreach($this->getSourceRepositories() as $repository) {
if ($repository->providesCommitActivity()) return true;
}
return false;
}
/**
* This function answers true if the receiver has at least
* one source repository specified; it returns false otherwise.
*
* @return boolean
*/
function hasSourceRepositories() {
return $this->getSourceRepositories() ? true : false;
}
function getGitStatus() {
$types = $this->getSourceRepositoryTypes();
if (count($types) == 1 && isset($types['git'])) return PROJECT_REPO_ALL_GIT;
if (isset($types['git']) && count($types) > 1) return PROJECT_REPO_SOME_GIT;
if (count($types)) return PROJECT_REPO_NO_GIT;
return PROJECT_REPO_NONE;
}
/**
* This function answers an array of source repository types,
* e.g. array('git'=>'git', 'cvs'=>'cvs').
*
* @return multitype:string
*/
function getSourceRepositoryTypes() {
$types = array();
foreach($this->getSourceRepositories() as $repository) {
$type = $repository->getType();
$types[$type] = $type;
}
return $types;
}
/**
* This function returns a collection of SourceRepository instances,
* each representing one of the source repositories associated with
* the project.
*
* @return SourceRepository[]
*/
function getSourceRepositories() {
// TODO Cache these results.
$values = $this->findValues('sourcerepository');
$repositories = array();
foreach($values as $repository) {
if ($sourceRepository = getSourceRepository($repository, $this)) {
$repositories[$sourceRepository->getName()] = $sourceRepository;
}
}
return $repositories;
}
function getUniqueSourceRepositories() {
$repositories = $this->getSourceRepositories();
$this->removeRedundantRepositories($repositories);
return $repositories;
}
/**
* @internal
* @param SourceRepository[] $repositories
*/
function removeRedundantRepositories(&$repositories) {
foreach($this->getChildren() as $subproject) {
foreach($subproject->getUniqueSourceRepositories() as $subrepo) {
unset($repositories[$subrepo->getName()]);
}
$subproject->removeRedundantRepositories($repositories);
}
}
function getUpdateSiteUrl() {
return $this->findFirstValue('updatesiteurl');
}
function getProjectPhase() {
return $this->data['ProjectPhase'];
}
/**
* This function answers true if the receiver is marked as
* a &quot;Component&quot; in the Eclipse Foundation database.
* This is a relatively old concept that predates the 2007 version
* of the EDP. Components tend to have their own UNIX groups,
* but are considered fully part of the parent project.
*/
function isComponent() {
return $this->data['IsComponent'];
}
function isActive() {
return $this->data['IsActive'] == 1;
}
function isInIncubationPhase() {
return in_array($this->getProjectPhase(), array('Incubation', 'Incubation.nc'));
}
function isArchived() {
return $this->getProjectPhase() == 'Archived';
}
function isInIncubationConformingPhase() {
return $this->getProjectPhase() == 'Incubation';
}
function isInIncubationNonConformingPhase() {
return $this->getProjectPhase() == 'Incubation.nc';
}
function isTopLevel() {
return !$this->parent;
}
/**
* This function returns an array containing the names of the
* simultaneous releases that the receiver participates in.
*
* @return string[]
*/
function getSimultaneousReleaseNames() {
$rows = $this->findRows('simultaneousrelease');
$releases = array();
foreach ($rows as $row) {
if ($row['Value'])
$releases[$row['SubKey']] = true;
}
return array_keys($releases);
}
/**
* Returns an instance of SimultaneousReleaseInfo for the receiver,
* or <code>null</code> if no information has been provided.
*
* @return SimultaneousReleaseInfo
*/
function getSimultaneousReleaseInfo($releaseName = null) {
return getSimultaneousReleaseInfo($this->getId(), $releaseName);
}
function getLiveliness() {
$liveliness = PROJECT_LIVELINESS_DEAD;
foreach($this->getChildren() as $child) {
$liveliness = min($liveliness, $child->getLiveliness());
}
if ($liveliness == PROJECT_LIVELINESS_ACTIVE) return PROJECT_LIVELINESS_ACTIVE;
if ($this->hasRecentCommitActivity(3)) return PROJECT_LIVELINESS_ACTIVE;
if ($liveliness == PROJECT_LIVELINESS_STALE) return PROJECT_LIVELINESS_STALE;
if ($this->hasRecentCommitActivity(6)) return PROJECT_LIVELINESS_STALE;
return PROJECT_LIVELINESS_DEAD;
}
/**
* @return int
*/
function getActiveCommittersCount() {
global $App;
$id = $this->getId();
$result = $App->dashboard_sql("select count(distinct login) as count from ProjectCommitterActivity where project='$id'");
while($row = mysql_fetch_assoc($result)) {
return (int)$row['count'];
}
return 0;
}
/**
* @return int
*/
function getActiveOrganizationsCount() {
global $App;
$id = $this->getId();
$result = $App->dashboard_sql("select count(distinct company) as count from ProjectCompanyActivity where project='$id'");
while($row = mysql_fetch_assoc($result)) {
return (int)$row['count'];
}
return 0;
}
/**
* Answers whether or not the receiver has recent commit activity.
*
* @param int $months (Optional) how many months in the past to check.
* @return boolean
*/
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.
*/
function getCommitActivity() {
if (isset($this->_commitActivity)) return $this->_commitActivity;
global $App;
$id = $this->getId();
$result = $App->dashboard_sql("select period, count from ProjectCommitActivity where project='$id'");
$this->_commitActivity = array();
while($row = mysql_fetch_assoc($result)) {
$this->_commitActivity[$row['period']] = $row['count'];
}
return $this->_commitActivity;
}
/**
* Returns the last three month's worth of company commit activity
* for the receiver in the form of an array mapping company name to
* commit count.
*
* e.g.,
*
* array('IBM' => 52, 'Actuate Corporation' => 33)
*
* Note that this information is cached. The first time this method
* is called (on any instance), the cache is created.
*
* @returns [] array mapping company name to commit count.
*/
function getCompanyCommitActivity() {
if (isset($this->_companyActivity)) return $this->_companyActivity;
global $App;
$id = $this->getId();
$result = $App->dashboard_sql("select company, count from ProjectCompanyActivity where project='$id'");
$this->_companyActivity = array();
while($row = mysql_fetch_assoc($result)) {
$this->_companyActivity[$row['company']] = $row['count'];
}
return $this->_companyActivity;
}
/**
* Returns the last three month's worth of committer commit activity
* for the receiver in the form of an array mapping committer name to
* commit count.
*
* e.g.,
*
* array('Wayne Beaton' => 52, 'Doug Schaefer' => 33)
*
* Note that this information is cached. The first time this method
* is called (on any instance), the cache is created.
*
* @returns [] array mapping company name to commit count.
*/
function getCommitterCommitActivity() {
if (isset($this->_committerActivity)) return $this->_committerActivity;
global $App;
$id = $this->getId();
$result = $App->dashboard_sql("select login, count from ProjectCommitterActivity where project='$id'");
$this->_committerActivity = array();
while($row = mysql_fetch_assoc($result)) {
$this->_committerActivity[$row['login']] = $row['count'];
}
return $this->_committerActivity;
}
function getLicenses() {
global $App;
$id = $this->getId();
$sql = "select distinct LicenseId from ProjectLicenses where ProjectId='$id'";
$result = $App->foundation_sql($sql);
$licenses = array();
while ($row = mysql_fetch_assoc($result)) {
$licenses[] = $row['LicenseId'];
}
return $licenses;
}
/*
* This function assembles a collection of ProjectInfoValue instances
* for the given $key. Essentially, this is an object that represents
* a group of values in the receiver and presents a higher-level of
* abstraction for accessing those values.
*
* Values are stored in the DB in a table like this:
*
* +---------------+---------+-----------+----------------------------+
* | ProjectInfoID | MainKey | SubKey | Value |
* +---------------+---------+-----------+----------------------------+
* |... | | | |
* |8512 |newsgroup|name |eclipse.examples.users |
* |8512 |newsgroup|type |main |
* |8512 |newsgroup|description|A useful newsgroup |
* |9765 |newsgroup|name |eclipse.examples.dev |
* |9765 |newsgroup|type |standard |
* |... | | | |
* +---------------+---------+-----------+----------------------------+
*
* These values are associated with the technology.examples project.
*
* If we ask the Project instance to getNewsgroups(), this function will
* be invoked and find these five rows. It would, from these rows, create
* two instances of ProjectInfoValues: one representing the values for
* ProjectInfoID=8512, and a second representing the values for
* ProjectInfoID=9765. These instances can then be sent relatively
* high-level messages like getName(), getType(), and getDescription()
* to obtain useful information.
*
* PRIVATE: THIS FUNCTION IS NOT API.
*/
/* private */ function getProjectInfoValues($key) {
$this->root->load_project_info();
$infoValues = array();
foreach($this->values as $value) {
if ($value['MainKey'] == $key) {
$id = $value['ProjectInfoID'];
if (!isset($infoValues[$id])) $infoValues[$id] = new ProjectInfoValue($id, $key, $this);
}
}
return $infoValues;
}
/* private */ function findRows($mainkey) {
$this->root->load_project_info();
$rows = array();
foreach($this->values as $row) {
if ($row['MainKey'] == $mainkey) $rows[] = $row;
}
return $rows;
}
/* private */ function findFirstValue($mainkey, $subkey = '') {
$this->root->load_project_info();
foreach($this->values as $row) {
if ($row['MainKey'] != $mainkey) continue;
if ($row['SubKey'] != $subkey) continue;
return trim($row['Value']);
}
return null;
}
/* private */ function findValues($mainkey, $subkey = '') {
$values = array();
$this->root->load_project_info();
foreach($this->values as $row) {
if ($row['MainKey'] != $mainkey) continue;
if ($row['SubKey'] != $subkey) continue;
$values[] = trim($row['Value']);
}
return $values;
}
function isCommitterEmail($address) {
return true;
}
function hasChildren() {
return $this->children ? true : false;
}
function getChildren() {
return $this->children;
}
/**
* Return the people associated with this project. Note that
* the value is cached so that subsequent calls return the
* same objects.
*
* @return Committer[]
*/
function getPeople() {
require_once dirname(__FILE__) . '/Committer.class.php';
if ($this->people === null)
$this->people = getCommittersForProject($this->getId());
return $this->people;
}
/**
* Answers <code>true</code> if the receiver's
* inherit flag is set (meaning that metadata from the
* parent should be inherited), or <code>false</code>
* otherwise.
*/
function inheritsFromParent() {
return $this->findFirstValue('inherit', 'inherit');
}
/**
* This function will make sure that the given URL is likely valid by
* converting it (if necessary) into an absolute URL. It further checks
* to ensure that the URL is from an 'eclipse.org' property.
*
* @deprecated Use normalizeFilePathUrl or normalizeHttpUrl
* @param A string value representing an absolute or relative URL.
* @return A likely valid absolute URL that references an eclipse.org page
*/
function normalizeProjectDescriptionUrl($url) {
return $this->normalizeUrlAsHTTP($url);
}
/**
* This function normalizes the provided URL to an HTTP form. Input
* should be a valid eclipse.org URL or a relative URL (with or without
* a leading slash).
*
* e.g. The following URLs will all normalize to
* http://eclipse.org/woolsey/para.html
*
* - http://www.eclipse.org/woolsey/para.html
* - http://eclipse.org/woolsey/para.html
* - http://localhost/woolsey/para.html
* - woolsey/para.html
* - /woolsey/para.html)
*
* Note that URLs that do not correspond to eclipse.org addresses, will
* result in a <code>null</code> result.
*
* Usage:
*
* Project::normalizeHttpUrl('http://www.eclipse.org/woolsey/para.html');
*
* @param String $url
* @deprecated Use normalizeHttpUrl($url) from common.php
*/
public function normalizeHttpUrl($url) {
return normalizeHttpUrl($url);
}
/**
* This function normalizes the provided URL to valid file path on the
* eclipse.org web directory. Input should be a valid eclipse.org URL
* or a relative URL (with or without a leading slash).
*
* e.g. The following URLs will all normalize to
* /home/local/data/httpd/www.eclipse.org/html/woolsey/para.html
*
* - http://www.eclipse.org/woolsey/para.html
* - http://eclipse.org/woolsey/para.html
* - http://localhost/woolsey/para.html
* - woolsey/para.html
* - /woolsey/para.html)
*
* Note that URLs that do not correspond to eclipse.org addresses, will
* result in a <code>null</code> result.
*
* Usage:
*
* Project::normalizeFilePathUrl('http://www.eclipse.org/woolsey/para.html');
*
* @param String $url
* @deprecated Use normalizeFilePathUrl($url) from common.php
*/
public function normalizeFilePathUrl($url) {
return normalizeFilePathUrl($url);
}
/**
* This function normalizes the provided URL to a relative path.
* Input should be a valid eclipse.org URL or a relative URL
* (with or without a leading slash).
*
* e.g. The following URLs will all normalize to
* /woolsey/para.html
*
* - http://www.eclipse.org/woolsey/para.html
* - http://eclipse.org/woolsey/para.html
* - http://localhost/woolsey/para.html
* - woolsey/para.html
* - /woolsey/para.html)
*
* Note that URLs that do not correspond to eclipse.org addresses, will
* result in a <code>null</code> result.
*
* Usage:
*
* Project::extractcRelativeUrl('http://www.eclipse.org/woolsey/para.html');
*
* @param string $url
* @deprecated Use normalizeRelativeUrl($url) from common.php
*/
public function normalizeRelativeUrl($url) {
return normalizeRelativeUrl($url);
}
/**
* PRIVATE - THIS IS NOT API.
*
* This method cleans up the description by removing potentially harmful tags.
*
* @param string $description
*/
function cleanDescription($description) {
// If we've been given a phoenix page, extract out the interesting bits.
if(preg_match('/\<div id="midcolumn"\>/s', $description)) {
$description = preg_replace('/((^.*\<div id="midcolumn"\>)|(\<\/div\>\<div id="footer"\>.*))/s', '', $description);
$description = substr($description, 0, strripos($description, '</div>'));
}
return $description;
}
/**
* This function extracts a short description from the provided (potentially)
* long one. We attempt to find the first paragraph in what we assume is HTML
* text.
*
* @param string $description The (potentially) long description, a string containing HTML.
* @param int $maxWords (optional) The maximum number of words in the result.
* @return string a string with limited HTML content (no surrounding 'p' tags).
*/
function extractShortDescription($description, $maxWords=100) {
// Match everything between the first <p> tag and the next </p> or <br> tag.
$matches = array();
if (preg_match('/<p>(.*?)(?:(?:<\/p>)|(?:<br\s*\/?>))/s', $description, $matches)) {
//print_r($matches);
$description = $matches[1];
}
// Kill any tags that might occur in the text.
$description = preg_replace('/<[^>]*>/', '', $description);
// Restrict the word count in the result.
$words = str_word_count($description, 2);
if (count($words) > $maxWords) {
$indices = array_keys($words);
$description = substr($description, 0, $indices[$maxWords]) . '...';
}
return $description;
}
}
/*
* Each instance of this class represents a collection of values
* from the ProjectInfoValues table that share a common ProjectInfoID and MainKey.
* These values are actually stored in an array on the Project instance itself;
* instances of this class do not store the values themselves, but rather
* find the values on the Project instance.
*
* Instances can be queried for the values of the various SubKeys in the
* corresponding values using high-level function names. The function
* getName(), for example, would return the value in the row with SubKey
* 'name'.
*
* For more information, see the comment on Project#getProjectInfoValues($key).
*
* THIS CLASS IS NOT INTENDED TO BE INSTANTIATED BY CLIENTS.
*/
class ProjectInfoValue {
var $id;
var $key;
var $project;
function __construct($id, $key, $project) {
$this->id = $id;
$this->key = $key;
$this->project = $project;
}
function __call($name, $args) {
$match = array();
if (preg_match('/^get(.*)$/', $name, $match)) {
return $this->getValue($match[1]);
}
}
/* private */ function getValue($subkey) {
foreach ($this->project->values as $row) {
if ($row['MainKey'] != $this->key) continue;
if ($row['ProjectInfoID'] != $this->id) continue;
if (strcasecmp($row['SubKey'], $subkey) == 0) return $row['Value'];
}
return null;
}
}
/**
* We never actually instantiate this class, it is provided to
* expose API for values returned by the Project#getNewsgroups
* method. Actual values are instances of ProjectInfoValue and
* the methods indicated below work through reflection.
*/
abstract class Newsgroup {
abstract function getId();
abstract function getName();
abstract function getType();
abstract function getDescription();
}
class Release {
var $project;
var $name;
var $date;
function __construct($project, $data) {
$this->project = $project;
$this->data = $data;
$name = $data->getValue('name');
if (preg_match('/(\d+)(\.\d+)?(\.\d+)?[\s\_\-\.]*((M|RC)\d+)?/', $name, $matches)) {
$name = $matches[1];
$name .= isset($matches[2]) && $matches[2] ? $matches[2] : '.0';
$name .= isset($matches[3]) && $matches[3] ? $matches[3] : '.0';
if (isset($matches[4]) && $matches[4]) $name .= $matches[4];
$this->name = $name;
}
$this->date = strtotime($data->getValue('date'));
}
function getReview() {
foreach($this->project->getReviews() as $review) {
if (!$review->isRelease()) continue;
if ($review->getVersion() == $this->name) return $review;
}
return null;
}
function getName() {
return $this->name;
}
function isValid() {
if (!$this->name) return false;
if (!$this->date) return false;
if ($this->date < strtotime('2000-01-01')) return false;
return true;
}
function isMilestone() {
return preg_match('/((M|RC)\d+)$/', $this->name);
}
function getDate() {
return $this->date;
}
/**
* Fetch the URL for the project plan for this release if it has
* been specified. Note that we try to be very forgiving about the format.
*
* The following values will all result in a plan URL of "project/plan.xml"
* - http://www.eclipse.org/project/plan.xml
* - http://eclipse.org/project/plan.xml
* - http://www.eclipse.org/projects/project-plan.php?planurl=http://eclipse.org/project/plan.xml
* - http://www.eclipse.org/projects/project-plan.php?planurl=/project/plan.xml
* - http://www.eclipse.org/projects/project-plan.php?planurl=project/plan.xml
*
* @return string
*/
function getPlan() {
$plan = $this->data->getValue('plan');
if (preg_match('/^(http:\/\/(www\.)?eclipse\.org\/projects\/project-plan\.php\?planurl=)?(http:\/\/(www\.)?eclipse.org)?\/?(.+\.xml)$/', $plan, $matches)) {
return $matches[5];
}
return $plan;
}
function getStatus() {
return $this->data->getValue('status');
}
function getNoteworthyUrl() {
return $this->data->getValue('noteworthyurl');
}
function getDownload() {
return $this->data->getValue('download');
}
}
abstract class SourceRepository {
var $project;
var $path;
function __construct($project, $path) {
$this->project = $project;
$this->path = $path;
}
function getName() {
return $this->getPath();
}
function getPath() {
return $this->path;
}
function providesCommitActivity() {
return true;
}
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
*/
abstract function getUrl();
abstract function getType();
/**
* Return an HTTP link for the receiver. i.e. something that is suitable
* for a browser
*/
abstract function getLink();
}
class GitRepository extends SourceRepository {
function getName() {
if (preg_match('/org\.eclipse\.(.*)\.git$/', $this->path, $matches)) {
return $matches[1];
} else if (preg_match('/org\.eclipse\.(.*)$/', $this->path, $matches)) {
return $matches[1];
} else if (preg_match('/([^\/]*)\.git$/', $this->path, $matches)) {
return $matches[1];
} else if (preg_match('/([^\/]*)$/', $this->path, $matches)) {
return $matches[1];
} else {
return $this->path;
}
}
function getPath() {
return "/gitroot$this->path";
}
function getType() {
return "git";
}
function getLink() {
return "http://git.eclipse.org/c$this->path";
}
function getGitUrl() {
return "git://git.eclipse.org/gitroot$this->path";
}
function getUrl() {
return $this->getGitUrl();
}
}
class EGitRepository extends GitRepository {
function getName() {
return $this->getLink();
}
function getLink() {
if (preg_match('/^git:\/\/(egit\.eclipse\.org\/.*)$/', $this->path, $matches)) {
return 'http://' . $matches[1];
}
return $this->path;
}
function getPath() {
return $this->getLink();
}
function getGitUrl() {
return $this->path;
}
function providesCommitActivity() {
return false;
}
}
class CvsRepository extends SourceRepository {
function getType() {
return "cvs";
}
function getPath() {
return "/cvsroot$this->path";
}
function getLink() {
preg_match('/^\/?\w+\/(.*)$/', $this->path, $matches);
$root = $this->project->getTopLevelProject()->getId();
if (strlen($root) <= 4) {
$root = strtoupper($root);
} else {
$root = ucwords($root);
}
$path = $matches[1];
return "http://dev.eclipse.org/viewcvs/viewvc.cgi/$path/?root=${root}_Project";
}
function getUrl() {
if (preg_match('/^(\/cvsroot\/[^\/]+)\/(.+)$/', $this->getPath(), $matches)) {
$repository = $matches[1];
$path = $matches[2];
return ":pserver:anonymous@dev.eclipse.org:$repository:$path";
} else return null;
}
}
class SvnRepository extends SourceRepository {
function getType() {
return "svn";
}
/* private */ function isUppercaseRoot($root) {
if ($root == 'modeling') return false;
if ($root == 'technology') return false;
return true;
}
function getLink() {
preg_match('/^[^\/]*\/(.*)$/', $this->path, $matches);
$root = $this->project->getTopLevelProject()->getId();
if ($this->isUppercaseRoot($root)) {
$root = strtoupper($root);
} else {
$root = ucwords($root);
}
$project = $this->project->getShortId();
// Need to handle a few special cases...
if ($project == 'swtbot') $project = 'SWTBot';
else if ($project == 'g-eclipse') $project = 'GEclipse';
else if ($project == 'eclipselink') $project = 'PERSISTENCE';
else if ($project == 'dbaccess') $project = 'GEMINI.DBACCESS'; // Bug 369905
else if ($project == 'naming') $project = 'GEMINI.NAMING';
else if ($project == 'blueprint') $project = 'GEMINI.BLUEPRINT';
else if ($project == 'jpa') $project = 'GEMINI.JPA';
else $project = strtoupper($project);
return "http://dev.eclipse.org/viewcvs/viewvc.cgi/?root=${root}_${project}";
}
function getUrl() {
$path = $this->getPath();
return "http://dev.eclipse.org$path";
}
function getPath() {
return "/svnroot/$this->path";
}
}
/**
* 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");
}
/* private */ function _sortProjects_name($a, $b) {
return strcasecmp($a->getName(), $b->getName());
}
/* private */ function _sortProjects_top($a, $b) {
return strcasecmp($a->getTopLevelProject()->getName(), $b->getTopLevelProject()->getName());
}
/**
* This function answers an instance of SourceRepository corresponding to the
* value of the parameter.
*
* Example:
*
* getSourceRepository('/gitroot/woolsey/org.eclipse.woolsey.iplog.git')
* returns an instance of GitRepository
*
* @see SourceRepository
* @see GitRepository
* @see EGitRepository
* @see CvsRepository
* @see SvnRepository
*
* @param string $repository
* @return SourceRepository
*/
function getSourceRepository($repository, $project=null) {
global $projectNamePattern;
// We only care about source code repositories, skip websites.
if (preg_match('/org\.eclipse\/www/', $repository)) return null;
// EGit is a special case.
if (preg_match('/^git:\/\/egit\.eclipse\.org(\/.*)$/', $repository)) return new EGitRepository($project, $repository);
// Deal with Git.
if (preg_match('/\/gitroot((\/[\w\.\-]+)+)\/?$/', $repository, $matches)) return new GitRepository($project, $matches[1]);
if (preg_match('/^http:\/\/git\.eclipse\.org\/c(\/.*)$/', $repository)) return new GitRepository($project, $matches[1]);
// Deal with CVS.
if (preg_match('/^\/cvsroot((\/[\w\-\.]+)+)\/?$/', $repository, $matches)) return new CvsRepository($project, $matches[1]);
// Deal with SVN.
if (preg_match('/^(\/?svnroot)?\/?(\w+(\/[\w\-\.]+)+)\/?$/', $repository, $matches)) return new SvnRepository($project, $matches[2]);
return null;
}
?>