blob: 8851c71273fa7b2f5350cb898bb5dbdb6adf3298 [file] [log] [blame]
<?php
/*******************************************************************************
* Copyright (c) 2010 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
*******************************************************************************/
require_once(dirname(__FILE__) . '/debug.php');
$proposal_statuses=array(
"Proposal Posted",
"Proposal Updated 1",
"Proposal Updated 2",
"Proposal Updated 3",
"Proposal Updated 4",
"Proposal Updated 5",
"Proposal Updated 6",
"Proposal Updated 7",
"Proposal Withdrawn"
);
$review_statuses=array(
"IP Log Posted",
"Legal Review Requested",
"Project Archived",
"Provisioning Complete",
"Review Pending",
"Review Scheduled",
"Review Successful",
"Review Unsuccessful",
"Review Withdrawn",
"Slides Posted",
"Waiting Provisioning"
);
class Activity {
var $data;
function __construct($data) {
$this->data = $data;
}
function getDate() {
$raw = $this->data['Date'];
return strtotime($raw);
}
function getBugNumber() {
if (!isset($this->data['BugNumber'])) return null;
return $this->data['BugNumber'];
}
function getLink() {
return $this->encodeUrl($this->getRawLink());
}
function getStatus() {
return $this->getRawStatus();
}
function getRawStatus() {
return $this->data['Status'];
}
function getProjectId() {
return $this->data['ProjectId'];
}
function getSlidesUrl() {
if (!isset($this->data['SlidesURL'])) return null;
return $this->encodeUrl($this->data['SlidesURL']);
}
function getProjectUrl() {
if (!isset($this->data['ProjectURL'])) return null;
return $this->encodeUrl($this->data['ProjectURL']);
}
function getIPLogUrl() {
if (!isset($this->data['IPLogURL'])) return null;
return $this->encodeUrl($this->data['IPLogURL']);
}
function isWithdrawn() {
return in_array($this->data['Status'], array('Proposal Withdrawn', 'Review Withdrawn'));
}
/*
* This function generates a Globally-unique Id (GUID) for this
* news item. To keep with the "globally unique" theme, I'm using
* the URL for this page as a base. To keep it unique within that
* scope, I'm appending the id of the item from our database and the
* status. The URL generated by this method is not intended to be
* meaningful; i.e. using it will not bring you to this specific
* item. In the RSS output, we set 'isPermaLink' to false to reflect this.
*/
function getGuid() {
$id = $this->data['Id'];
// Get the status from the parent. Receiver's impl may change the value.
$status = str_replace(' ', '', $this->getRawStatus());
return "http://www.eclipse.org/projects/reviews-rss.php?$id.$status";
}
/* private */ function encodeUrl($url) {
if (!$url) return $url;
// if( strpos($url, "http:") === false )
// $url = "http://www.eclipse.org" . $url;
//
// return urlencode($url);
return $url;
}
}
class Review extends Activity {
function getName() {
// If the project name isn't specified for a review, then
// we've likely gone through a creation review. Make sure that
// the title reflects this.
return $this->data['ProjectName'] ? $this->data['ProjectName'] : $this->data['ProposalName'];
}
function getTitle() {
$projectName = $this->getName();
$reviewName = $this->getReviewName();
$status = $this->getStatus();
return "$projectName $reviewName $status";
}
function getDescription() {
return $this->getReviewName();
}
function getReviewName() {
return $this->data['ReviewName'] ? $this->data['ReviewName'] : "Creation";
}
/**
* This method answers the date that the review either occurred or will occur
* on. The date is returned as a UNIX date (i.e. not a string).
*/
function getReviewDate() {
return strtotime($this->data['ReviewDate']);
}
function isUnsuccessful() {
return in_array($this->data['Status'], array ('Review Withdrawn', 'Review Unsuccessful'));
}
function isSuccessful() {
return in_array($this->data['Status'], array ("Project Archived", "Provisioning Complete", "Review Successful", "Waiting Provisioning"));
}
function toHtmlString() {
global $App;
$name = htmlentities($this->getName());
$description = htmlentities($this->getDescription()) . ' Review';
$projectUrl = $this->getProjectUrl();
if ($projectUrl) $name = "<a href=\"$projectUrl\">$name</a>";
$slidesUrl = $this->getSlidesUrl();
$iplogUrl = $this->getIPLogUrl();
$date = $this->getReviewDate();
if ($date) {
$date = $App->getFormattedDate($date, 'short');
} else {
$date = "Not scheduled";
}
$date = str_replace(' ', '&nbsp;', $date);
$icons = '';
if ($slidesUrl)
$icons .= "<a href=\"$slidesUrl\"><img style=\"vertical-align:top\" title=\"Review Documentation\" src=\"http://dev.eclipse.org/small_icons/mimetypes/x-office-presentation.png\"/></a>";
if ($iplogUrl) {
$icons .= "<a href=\"$iplogUrl\"><img style=\"vertical-align:top\" title=\"IP Log\" src=\"http://dev.eclipse.org/small_icons/status/dialog-information.png\"/></a>";
}
$status = $this->getStatus();
if ($this->isSuccessful()) {
$icons .= "<img style=\"vertical-align:top\" title=\"$status\" src=\"/projects/images/ok.gif\">";
}
if ($this->isUnsuccessful()) {
$date = "<strike>$date</strike>";
$name = "<strike>$name</strike>";
$description = "<strike>$description</strike>";
$icons .= "<img style=\"vertical-align:top\" title=\"$status\" src=\"http://dev.eclipse.org/small_icons/actions/process-stop.png\">";
}
return "$name $description $icons $date";
}
/**
* Return an appropriate link for the receiver. If the receiver
* represents a the date that the IP Log was posted and the IPLogURL
* value has been set, return that value. Then, if the SlidesURL has
* been set, return that. Otherwise, return the ProjectURL.
*
*/
function getRawLink() {
if (strcmp("IP Log Posted", $this->getStatus()) == 0) return $this->data['IPLogURL'];
if ($this->data['SlidesURL']) return $this->data['SlidesURL'];
return $this->data['ProjectURL'];
}
}
class Proposal extends Activity {
function getName() {
return $this->data['ProposalName'];
}
function getTitle() {
$proposalName = $this->getName();
$status = $this->getStatus();
return "$proposalName $status";
}
function getDescription() {
return "Project Proposal";
}
function getStatus() {
/*
* There are a number of statuses that are of the form
* 'Proposal Updated n'; here, we remove the n.
*/
$status = parent::getStatus();
if (strpos($status, "Proposal Updated") === 0) return "Proposal Updated";
return $status;
}
function isUnsuccessful() {
return in_array($this->data['Status'], array ('Proposal Withdrawn'));
}
/**
* A proposal is never successful. If it's successful, then it would have been turned into
* a review.
*/
function isSuccessful() {
return false;
}
function getRawLink() {
return $this->data['ProposalURL'];
}
}
/**
* This function obtains the recent project proposal and
* review activity.
*
* @param $age How many days back in time do we go?
* @param $limit Limit the number of rows returned (-1 means all rows).
* @param $includeHistory Do we include all changes on the project (true), or only the most recent activity (false).
* @return void
*/
function getRecentActivity($age, $limit=-1, $includeHistory=false) {
global $App;
if ($App->devmode) {
return array(
new Review(array('ProjectName' => 'A really long review name', 'Date' => 'today', 'Status' => 'A status that causes some wrapping')),
new Review(array('ProjectName' => 'A Proposal of some merit', 'Date' => '-2 days', 'Status' => 'Proposal Posted'))
);
}
return getActivity(generateRecentActivitySql($age, $limit, $includeHistory));
}
function getAllCompleteReviewActivity($limit = -1) {
$sql = "
SELECT
r.id as Id,
r.ProjectName, r.ProjectId,
ProjectURL, ProposalURL, SlidesURL, IPLogURL, BugNumber, ReviewDate,
ReviewName, ProposalName,
s.status as Status, s.value as Date
FROM
ProjectReviews as r
join ProjectReviewStatus as s on (r.id = s.id and s.value != 0)
WHERE
Status in ('Review Successful', 'Review Unsuccessful', 'Review Withdrawn')
and ReviewDate != 0
order by ReviewDate desc";
if ($limit > 0) {
$sql .= " limit $limit";
}
return getActivity($sql);
}
/*private*/ function generateRecentActivitySql($age, $limit, $includeHistory) {
$sql = "
SELECT
r.id as Id,
r.ProjectName, r.ProjectId,
ProjectURL, ProposalURL, SlidesURL, IPLogURL, BugNumber,
ReviewName, ProposalName,
s.status as Status, s.value as Date
FROM
ProjectReviews as r
join ProjectReviewStatus as s on (r.id = s.id and date(s.value) > subdate(now(), interval $age day))";
if (!$includeHistory) {
$sql .= " join (select ID, Max(value) as v from ProjectReviewStatus group by id) as m on (m.Id = r.id and m.v = s.value)";
}
$sql .= " order by date desc";
if ($limit > 0) {
$sql .= " limit $limit";
}
return $sql;
}
function getUpcomingReviews() {
global $App;
$activity = array();
$start = findPreviousThursday();
$end = findNextWednesday();
$sql = "
SELECT
r.id as Id,
r.ProjectName, r.ProjectId,
ProjectURL, ProposalURL, SlidesURL, IPLogURL, BugNumber,
ReviewName, ProposalName,
s.status as Status,
date(r.ReviewDate) as Date
FROM
ProjectReviews as r
join ProjectReviewStatus as s on (r.id = s.id)
join (select ID, Max(value) as v from ProjectReviewStatus group by id) as m on (m.Id = r.id and m.v = s.value)
WHERE
r.ReviewDate > subdate(now(), interval 1 week)
ORDER By date(ReviewDate) between '$start' and '$end' desc, date(ReviewDate), ProjectId";
foreach(getActivity($sql) as $review) {
$key = $review->getName() . $review->getReviewName();
$activity[$key] = $review;
}
$forges = array(
'http://projects.eclipse.org/json/reviews/upcoming',
'http://polarsys.org/json/reviews/upcoming',
'http://locationtech.org/json/reviews/upcoming'
);
foreach($forges as $forge) {
if ($upcoming = json_decode(@file_get_contents($forge), true)) {
foreach($upcoming as $row) {
$review = new Review($row);
$key = $review->getName() . $review->getReviewName();
$activity[$key] = $review;
}
}
}
usort($activity, 'sortUpcomingReviews');
return $activity;
}
/**
* Compare two Reviews for sorting. The most recently completed
* reviews are sorted first, followed by everything else. Within
* the groups, everything is sorted by date and then by project id.
*
* @internal
* @param Review $a
* @param Review $b
* @return -1, 0, 1 if <,==,>
*/
function sortUpcomingReviews($a, $b) {
$now = strtotime('now');
$lastWeek = strtotime('-1 week');
$aDate = $a->getDate();
$bDate = $b->getDate();
$aRecent = ($aDate < $now) && ($aDate > $lastWeek);
$bRecent = ($bDate < $now) && ($bDate > $lastWeek);
if ($aRecent == $bRecent) {
if ($aDate < $bDate) return -1;
if ($aDate > $bDate) return 1;
return strcasecmp($a->getProjectId(),$b->getProjectId());
}
if ($aRecent == 1) return -1;
return 1;
}
function sortByActivityDate($a, $b) {
$aDate = $a->getDate();
$bDate = $b->getDate();
if ($aDate < $bDate) return 1;
if ($aDate > $bDate) return -1;
return 0;
}
function findPreviousThursday($start = 'now') {
// $date = new DateTime($start);
// $diff = $date->format('w') - 4; // 4 == Thursday
// if ($diff < 0) $diff += 7;
// $thursday = $date->sub(new DateInterval('P' . $diff . 'D'));
// return $thursday->format('Y-m-d');
$date = strtotime($start);
$diff = date('w', $date) - 4; // 4 == Thursday
if ($diff < 0) $diff += 7;
$thursday = $date - ($diff * 24 * 60 * 60);
return date('Y-m-d', $thursday);
}
function findNextWednesday($start = 'now') {
// $date = new DateTime($start);
// $diff = 3 - $date->format('w'); // 3 == Wednesday
// if ($diff < 0) $diff += 7;
// $wednesday = $date->add(new DateInterval('P' . $diff . 'D'));
// return $wednesday->format('Y-m-d');
$date = strtotime($start);
$diff = 3 - date('w', $date); // 3 == Wednesday
if ($diff < 0) $diff += 7;
$wednesday = $date + ($diff * 24 * 60 * 60);
return date('Y-m-d', $wednesday);
}
/**
* This function returns a collection of Proposal-type activities
* reaching the specified number of months (default 6) into the past.
*
* @param int $age How many months in the past do we go back?
* @return Array of Proposals
*/
function getNewProjectProposals($age = 6) {
global $App;
$activity = getActivity(generateNewProjectProposalsSql($age));
$sources = array(
'http://projects.eclipse.org/json/proposals',
'http://www.polarsys.org/json/proposals',
'http://www.locationtech.org/json/proposals'
);
foreach($sources as $source) {
if ($forge = json_decode(@file_get_contents($source), true)) {
foreach($forge as $row) {
$id = $row['Id'];
$activity[$id] = new Proposal($row);
}
}
}
// if ($forge = json_decode(@file_get_contents('http://locationtech.org/json/proposals'), true)) {
// foreach($forge as $row) {
// $id = $row['Id'];
// $activity[$id] = new Proposal($row);
// }
// }
usort($activity, 'sortByActivityDate');
return $activity;
}
/* private */ function generateNewProjectProposalsSql($age) {
global $proposal_statuses;
$status = "'" . implode("','", $proposal_statuses) . "'";
$sql = "
SELECT
r.id as Id,
r.ProjectName, r.ProjectId,
ProjectURL, ProposalURL, SlidesURL, IPLogURL, BugNumber
ReviewName, ProposalName,
s.status as Status,
s.value as Date
FROM
ProjectReviews as r
join ProjectReviewStatus as s on (r.id = s.id)
join (select ID, Max(value) as v from ProjectReviewStatus group by id) as m on (m.Id = r.id and m.v = s.value)
WHERE
Status in ($status)
and s.value > subdate(now(), interval $age month)
ORDER By Date desc";
return $sql;
}
/**
* This function does the actual work of executing the SQL and
* extracting the result. The query string passed to this method
* must request a minimum of two columns from the database: 'Id'
* and 'Status'. More realistically, a lot more columns than that
* are required to make the results useful.
*
* @see #getUpcomingReviews()
*/
/*private*/ function getActivity($sql) {
global $App;
global $review_statuses;
global $proposal_statuses;
$result = $App->foundation_sql($sql);
$activity = array();
while($row = mysql_fetch_assoc($result)) {
$id = (int)$row['Id'];
if ($id == 2850) continue;
if ($id == 2852) continue;
$status = $row['Status'];
if (in_array($status, $review_statuses)) $activity[$id] = new Review($row);
else if (in_array($status, $proposal_statuses)) $activity[$id] = new Proposal($row);
}
return $activity;
}
?>