| <?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(' ', ' ', $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; | |
| $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"; | |
| $activity = getActivity($sql); | |
| unset($activity['2850']); | |
| unset($activity['2852']); | |
| if ($upcoming = json_decode(@file_get_contents('http://projects.eclipse.org/json/reviews/upcoming'), true)) { | |
| foreach($upcoming as $row) { | |
| $id = $row['Id']; | |
| $status = $row['Status']; | |
| $activity[$id] = new Review($row); | |
| } | |
| } | |
| if ($upcoming = json_decode(@file_get_contents('http://polarsys.org/json/reviews/upcoming'), true)) { | |
| foreach($upcoming as $row) { | |
| $id = $row['Id']; | |
| $status = $row['Status']; | |
| $activity[$id] = new Review($row); | |
| } | |
| } | |
| 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 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; | |
| if ($App->devmode) { | |
| return array( | |
| new Proposal(array('ProposalName' => 'Proposal One', 'Status' => 'Proposal Posted', 'Date' => '-10 Days', 'ProposalURL' => '/proposalOne')), | |
| new Proposal(array('ProposalName' => 'Proposal Two', 'Status' => 'Proposal Updated 1', 'Date' => '-32 Days', 'ProposalURL' => '/proposalTwo')), | |
| new Proposal(array('ProposalName' => 'Proposal Three', 'Status' => 'Proposal Updated 1', 'Date' => '-4 Months', 'ProposalURL' => '/proposalThree')), | |
| new Proposal(array('ProposalName' => 'Proposal Four', 'Status' => 'Proposal Withdrawn', 'Date' => '-5 Months', 'ProposalURL' => '/proposalFour')) | |
| ); | |
| } | |
| return getActivity(generateNewProjectProposalsSql($age)); | |
| } | |
| /* 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 = $row['Id']; | |
| $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; | |
| } | |
| ?> |