| <?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; |
| |
| $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; |
| } |
| |
| |
| ?> |