blob: abcbc437988f7e48814cee451764284fa8229efe [file] [log] [blame]
<?php
/*******************************************************************************
* Copyright (c) 2016, 2018 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__) . '/../classes/database.inc';
require_once dirname(__FILE__) . '/../classes/common.php';
require_once dirname(__FILE__) . '/../classes/debug.php';
require_once dirname(__FILE__) . '/../classes/Project.class.php';
class CQ {
var $data;
var $name;
var $version;
function __construct($data) {
$this->data = $data;
$summary = $data[0]['summary'];
if (preg_match('/(.+)\s+version:?\s*v?(\d+(?:\.\d+)+)/i', $summary, $matches)) {
$this->name = $matches[1];
$this->version = $matches[2];
} else if (preg_match('/(.+)\s+v?(\d+(?:\.\d+)+)/i', $summary, $matches)) {
$this->name = $matches[1];
$this->version = $matches[2];
} else {
$this->name = preg_replace('/\([^\)]*\)/', '', $summary);
if (preg_match('/(\d+(?:\.\d+)+)/i', $summary, $matches)) {
$this->version = $matches[0];
} else {
$this->version = 'n/a';
}
}
}
function getId() {
return $this->data[0]['id'];
}
function getSummary() {
return $this->data[0]['summary'];
}
function getName() {
return $this->name;
}
function getVersion() {
return $this->version;
}
function getLicense() {
return $this->data[0]['license'];
}
private function getComment($index = 0) {
return $this->data[0]['comment'];
}
function getProjectUrl() {
$comment = $this->getComment(0);
if (preg_match('/^Project URL:\s*(\S+)$/m', $comment, $matches)) {
return trim($matches[1]);
}
if ($parent = $this->getParent())
return $parent->getProjectUrl();
}
function getSourceUrl() {
$comment = $this->getComment(0);
if (preg_match('/^Source URL:\s*(\S+)$/m', $comment, $matches)) {
return trim($matches[1]);
}
if ($parent = $this->getParent())
return $parent->getSourceUrl();
}
function getRoot() {
if ($parent = $this->getParent())
return $parent->getRoot();
return $this;
}
function getParent() {
if (@$this->parent === null) {
$this->parent = $this->findParent();
}
return $this->parent;
}
private function findParent() {
if (!$id = $this->getParentId()) return FALSE;
$sql = "
select
b.bug_id as id, b.short_desc as summary, b.cf_license as license, l.thetext as comment
from bugs as b
join longdescs as l on b.bug_id=l.bug_id
where
b.bug_id=$id
order by b.bug_id, l.comment_id";
if ($cqs = self::findCqs($sql)) return $cqs[0];
return FALSE;
}
public function getParentId() {
$summary = $this->getSummary();
if (preg_match('/ATO\s+Orbit\s+(\d+)/', $summary, $matches))
return $matches[1];
if (preg_match('/ATO\s*CQ\s*([0-9]+)/', $summary, $matches))
return $matches[1];
if (preg_match('/PB\s*CQ\s*([0-9]+)/', $summary, $matches))
return $matches[1];
if (preg_match('/PB\s*([0-9]+)/', $summary, $matches))
return $matches[1];
if (preg_match('/Orbit\s*(?:CQ)?\s*([0-9]+)/', $summary, $matches))
return $matches[1];
return null;
}
public function __toString() {
return "{$this->getName()} ({$this->getVersion()})";
}
/**
* Answers the approved third party CQs for a project.
*
* @param string $id A project id, e.g. 'technology.egit'
* @return CQ[]
*/
static function findCqs($id) {
$sql = "
select
b.bug_id as id, b.short_desc as summary, b.cf_license as license, l.thetext as comment
from bugs as b
join longdescs as l on b.bug_id=l.bug_id
join components as c on b.component_id=c.id
join keywords as kw on b.bug_id=kw.bug_id
join keyworddefs as kwd on kw.keywordid=kwd.id
where
b.bug_severity in ('approved', 'license_certified')
and kwd.name='thirdparty'
and c.name='$id'
order by b.short_desc";
$data = array();
query('ipzilla', $sql, array(), function($row) use (&$data) {
$id = $row['id'];
$data[$id][] = $row;
});
$cqs = array();
foreach($data as $id => $rows) {
$cqs[] = new CQ($rows);
}
usort($cqs, function($a, $b) {
if (($compare = strcasecmp($a->getName(), $b->getName())) !== 0) {
return $compare;
}
return version_compare($a->getVersion(), $b->getVersion());
});
return $cqs;
}
}
/**
* Execute a function with the CQ for each piece of third
* party content leveraged by the project. The caller can
* provide an optional function to execute in the event that
* the project has no third party CQs.
*
* @param string $id The project id
* @param callable $function Function to execute once with each CQ.
* @param callable $else Function to execute if there are no CQs.
*/
function thirdPartyLibrariesDo($id, $function, $else = null) {
$cqs = CQ::findCqs ($id);
if ($cqs) foreach ($cqs as $cq ) call_user_func($function,$cq);
else if ($else) call_user_func($else);
}
function getDefaultFileHeader($licenses, $width=80) {
return renderAsHeaderComment(License::getDefaultFileHeader($licenses), $width);
}
function getAlternativeFileHeader($licenses, $width=80) {
return renderAsHeaderComment(License::getAlternativeFileHeader($licenses), $width);
}
function renderNoticeFile($id, $width=80) {
generateNoticeFileContent($id, new PlainTextRender($width));
}
function generateNoticeFileContent($id, $render) {
$project = Project::getProject($id);
$render
->title("Notices for " . $project->getFormalName())
->paragraph(
"This content is produced and maintained by the
{$project->getFormalName()} project.")
->unorderedList(array("Project home: {$project->getUrl()}"))
->section("Trademarks")
->paragraph(getTrademarksStatement($project))
->section("Copyright")
->paragraph("All content is the property of the respective authors "
."or their employers. For more information regarding authorship "
."of content, please consult the listed source code repository logs.")
->section("Declared Project Licenses")
->paragraph(valueOrDefaultWhenEmpty(License::getLicensesStatement($project->getLicenses()), "[Provide license information here]"))
->paragraph("SPDX-License-Identifier: " . valueOrDefaultWhenEmpty($project->getSPDXLicenseExpression(), "[Provide SPDX expression here]"))
->section("Source Code")
->paragraph("The project maintains the following source code repositories:")
->unorderedList($project->getSourceRepositories(), function($repository) {return $repository->getUrl();});
$render
->section("Third-party Content")
->paragraph("This project leverages the following third party content.");
thirdPartyLibrariesDo($id, function($cq) use (&$render) {
$properties = array();
if ($cq->getLicense()) $properties[] = "License: {$cq->getLicense()}";
if ($cq->getProjectUrl()) $properties[] = "Project: {$cq->getProjectUrl()}";
if ($cq->getSourceUrl()) $properties[] = "Source: {$cq->getSourceUrl()}";
$render
->paragraph("{$cq->getName()} ({$cq->getVersion()})", 3)
->unorderedList($properties);
}, function() use (&$render) {
$render->paragraph("None");
});
$render
->section("Cryptography")
->paragraph(
"Content may contain encryption software.
The country in which you are currently may have restrictions on
the import, possession, and use, and/or re-export to another country,
of encryption software. BEFORE using any encryption software, please
check the country's laws, regulations and policies concerning the import,
possession, or use, and re-export of encryption software, to see if
this is permitted.")
->end();
}
function getTrademarksStatement(Project $project) {
$trademarks = array();
if ($unregistered = $project->getTrademarks(TRADEMARK_UNREGISTERED, true)) {
$content = implodeWithConjunction($unregistered, 'and');
$content .= count($unregistered) == 1 ? ' is a trademark' : ' are trademarks';
$content .= ' of the Eclipse Foundation.';
$trademarks[] = $content;
}
if ($registered = $project->getTrademarks(TRADEMARK_REGISTERED, true)) {
$content = implodeWithConjunction($registered, 'and');
$content .= count($registered) == 1 ? ' is a registered trademark' : ' are registered trademarks';
$content .= ' of the Eclipse Foundation.';
$trademarks[] = $content;
}
if ($trademarks) {
return join(' ', $trademarks);
}
return "No trademarks.";
}
function renderContributingFile($id, $width=80) {
generateContributingFileContent($id, new PlainTextRender($width));
}
function generateContributingFileContent($id, $render) {
$project = Project::getProject($id);
$forge = $project->getForge();
$render
->title("Contributing to " . $project->getFormalName())
->paragraph("Thanks for your interest in this project.")
->section("Project description")
->paragraph(trim(html_entity_decode(strip_tags($project->getDescription()))))
->unorderedList(array($project->getUrl()))
->section("Developer resources")
->paragraph("Information regarding source code management, builds, coding standards, and more.")
->unorderedList(array($project->getUrl() . "/developer"))
->paragraph("The project maintains the following source code repositories")
->unorderedList($project->getSourceRepositories(),
function($each) {
return $each->getUrl();
});
if ($product = $project->getBugzillaProduct()) {
if ($forge->isEclipseForge()) {
$base = 'https://bugs.eclipse.org/bugs';
} else {
$base = "https://{$forge->getId()}.org/bugs";
}
$search = "$base/buglist.cgi?product=$product";
$create = "$base/enter_bug.cgi?product=$product";
if ($components = $project->getBugzillaComponents()) {
$search .= "&" . $components[1];
$create .= "&" . $components[1];
}
$render
->paragraph("This project uses Bugzilla to track ongoing development and issues.")
->unorderedList(
array(
"Search for issues: {$search}",
"Create a new report: {$create}")
)
->paragraph("Be sure to search for existing bugs before you create another one."
." Remember that contributions are always welcome!");
}
$links = array();
// @formatter:off
$render
->section("Eclipse Development Process")
->paragraph("This Eclipse Foundation open project is governed by the
Eclipse Foundation Development Process and operates under the terms
of the Eclipse IP Policy.");
$links[] = "https://eclipse.org/projects/dev_process";
$links[] = "https://www.eclipse.org/org/documents/Eclipse_IP_Policy.pdf";
// @formatter:on
// We include a statement regarding contributions under the specification
// process when the project is involved in specification work. For now,
// we consider a project to be under the specification process if the
// project is a specification project. This should be more generally
// expanded to include projects that implement specifications. We don't
// track this in an easily queriable manner. Ideally, I believe, we should
// include projects that are of interest to a working group that implements
// a specification process. e.g. (in Java to be concise),
//
// project.getWorkingGroups().findAny(wg -> wg.hasSpecificationProjects())
//
if ($project->isSpecificationProject()) {
// FIXME Hack: hard coded content specific to Jakarta EE.
if (strcmp('Jakarta EE', $project->getSpecificationWorkingGroupName()) == 0) {
// @formatter:off
$render
->paragraph(
"The Jakarta EE Specification Committee has adopted the Jakarta EE
Specification Process (JESP) in accordance with the Eclipse Foundation
Specification Process v1.2 (EFSP) to ensure that the specification process
is complied with by all Jakarta EE specification projects.");
// @formatter:on
$links[] = "https://jakarta.ee/about/jesp/";
} else {
// @formatter:off
$render
->section("Specifications")
->paragraph(
"This specification project operates under the terms of the Eclipse Foundation
Specification process.");
// @formatter:on
$links[] = "https://www.eclipse.org/projects/efsp/";
}
$links[] = "https://www.eclipse.org/legal/efsp_non_assert.php";
}
$render->unorderedList($links);
$render
->section("Eclipse Contributor Agreement")
->paragraph(
"In order to be able to contribute to Eclipse Foundation projects
you must electronically sign the Eclipse Contributor Agreement (ECA).")
->unorderedList(array("http://www.eclipse.org/legal/ECA.php"))
->paragraph(
"The ECA provides the Eclipse Foundation with a permanent record
that you agree that each of your contributions will comply with
the commitments documented in the Developer Certificate of Origin (DCO).
Having an ECA on file associated with the email address matching the
\"Author\" field of your contribution's Git commits fulfills the
DCO's requirement that you sign-off on your contributions.")
->paragraph(
"For more information, please see the Eclipse Committer Handbook:
https://www.eclipse.org/projects/handbook/#resources-commit")
->section("Contact")
->paragraph("Contact the project developers via the project's \"dev\" list.")
->unorderedList(array($project->getDevListUrl()))
->end();
}
/**
* A very simple class for rendering structured content
* in plaintext.
*/
class PlainTextRender {
var $width;
function __construct($width) {
$this->width = $width;
}
function title($text) {
$this->section($text, 1);
return $this;
}
function section($text, $level=2) {
for($index=0;$index<$level;$index++) echo "#";
echo " ";
echo $text;
echo "\n\n";
return $this;
}
function paragraph($text, $alternate = '') {
echo wordwrap($this->normalize($text != null ? $text : $alternate), $this->width);
echo "\n\n";
return $this;
}
private function normalize($text) {
return preg_replace('/\s+/', ' ', $text);
}
function unorderedList($values, $each = 'doNothing') {
foreach($values as $value) {
echo "* ";
$lines = explode("\n",wordwrap($each($value), $this->width-3));
echo implode("\n ", $lines);
echo "\n";
}
echo "\n";
return $this;
}
function end() {}
}
function doNothing($value) {
return $value;
}
function valueOrDefaultWhenEmpty($value, $default) {
return !empty($value) ? $value : $default;
}