460815: Project Activity News is empty 

Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=460815
diff --git a/classes/Activity.class.php b/classes/Activity.class.php
index 8851c71..0f92530 100644
--- a/classes/Activity.class.php
+++ b/classes/Activity.class.php
@@ -1,6 +1,6 @@
 <?php
 /*******************************************************************************
- * Copyright (c) 2010 Eclipse Foundation and others.
+ * Copyright (c) 2010, 2015 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
@@ -10,537 +10,48 @@
  *    Wayne Beaton (Eclipse Foundation)- initial API and implementation
  *******************************************************************************/
 
+require_once(dirname(__FILE__) . '/common.php');
 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.
  *
+ * @deprecated
  * @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;
+	return getReviews('complete', $age);
 }
 
 /**
- * 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 <,==,>
+ * @deprecated
+ * @param unknown $limit
  */
-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 getAllCompleteReviewActivity($limit = -1) {	
+	return getReviews('complete', $age);
 }
 
-function sortByActivityDate($a, $b) {
-    $aDate = $a->getDate();
-    $bDate = $b->getDate();
-
-    if ($aDate < $bDate) return 1;
-    if ($aDate > $bDate) return -1;
-    return 0;
+/**
+ * @deprecated
+ */
+function getUpcomingReviews() {	
+	return getReviews('upcoming');
 }
 
-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.
  * 
+ * @deprecated
  * @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;
+	return getProposals();
 }
 
 
diff --git a/classes/Proposal.class.php b/classes/Proposal.class.php
index 50a1f73..04934ef 100644
--- a/classes/Proposal.class.php
+++ b/classes/Proposal.class.php
@@ -1,6 +1,6 @@
 <?php
 /*******************************************************************************
- * Copyright (c) 2010, 2011 Eclipse Foundation and others.
+ * Copyright (c) 2010, 2015 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
@@ -332,4 +332,33 @@
 	return $proposal;
 }
 
+
+function getProposals() {
+	$proposals = array();
+
+	foreach(getForges() as $forge) {
+		$url = "$forge/json/proposals";
+		$json = getUrlContents($url);
+		if ($list = json_decode($json, true)) {
+			foreach($list as $row) {
+				$proposals[] = new Proposal($row);
+			}
+		}
+	}
+
+	usort($proposals, 'proposals_sortByDate');
+
+	return $proposals;
+}
+
+
+function proposals_sortByDate($a, $b) {
+	$aDate = $a->getDate();
+	$bDate = $b->getDate();
+
+	if ($aDate < $bDate) return 1;
+	if ($aDate > $bDate) return -1;
+	return 0;
+}
+
 ?>
\ No newline at end of file
diff --git a/classes/Review.class.php b/classes/Review.class.php
index bd26a3d..34fdb1d 100644
--- a/classes/Review.class.php
+++ b/classes/Review.class.php
@@ -1,6 +1,6 @@
 <?php
 /*******************************************************************************
- * Copyright (c) 2010 Eclipse Foundation and others.
+ * Copyright (c) 2010, 2015 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
@@ -10,6 +10,7 @@
  *    Wayne Beaton (Eclipse Foundation)- initial API and implementation
  *******************************************************************************/
 
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/common.php");
 require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/debug.php");
 
 class Review {
@@ -51,6 +52,14 @@
 		
 		return "Unknown";
 	}
+	
+	function getTitle() {
+		$projectName = $this->getProjectName();
+		$reviewName = $this->getReviewName();
+		$status = $this->getStatus();
+	
+		return "$projectName $reviewName $status";
+	}
 
 	function isCreation() {
 		return preg_match('/Creation/i', $this->getReviewName()) ? true : false;
@@ -103,6 +112,10 @@
 		if (!$date) return null;
 		return strtotime($date);
 	}
+	
+	function getDate() {
+		return $this->getReviewDate();
+	}
 
 	function getProjectUrl() {
 		return $this->info['ProjectURL'];
@@ -116,6 +129,10 @@
 		return $this->info['IPLogURL'];
 	}
 	
+	function getLink() {
+		return $this->getSlidesUrl();
+	}
+	
 	function getSlidesUrl() {
 		return $this->info['SlidesURL'];
 	}
@@ -167,7 +184,8 @@
 		$reviewDate = $this->getReviewDate();
 		
 		$projectName = htmlentities($projectName);
-		$reviewName = htmlentities($reviewName) . ' Review';
+		$reviewName = htmlentities($reviewName);
+		$suffix = 'Review';
 		
 		if ($projectUrl) $projectName = "<a href=\"$projectUrl\">$projectName</a>";
 		
@@ -178,10 +196,10 @@
 			$reviewDate= 'unscheduled';
 		}
 		
-		$icons = '';
 		if ($reviewUrl) 
-			$icons .= "<a href=\"$reviewUrl\"><img style=\"vertical-align:top\" title=\"Review Documentation\" src=\"http://dev.eclipse.org/small_icons/mimetypes/x-office-presentation.png\"/></a>";
+			$suffix = "<a href=\"$reviewUrl\">$suffix</a>";
 		
+		$icons = '';
 		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>";
 		}
@@ -201,7 +219,7 @@
 			$icons .= "<img style=\"vertical-align:top\" title=\"Review Withdrawn\" src=\"http://dev.eclipse.org/small_icons/actions/process-stop.png\">";
 		}
 	
-		return "$reviewDate $projectName $reviewName $icons";
+		return "$reviewDate ${icons}${projectName} $reviewName $suffix";
 	}
 	
 	/**
@@ -236,6 +254,7 @@
  * This function provides the list of all reviews. Note that the
  * results are cached. 
  * 
+ * @deprecated
  * @return Review[]
  */
 function get_reviews() {
@@ -283,7 +302,7 @@
 	$forges = array(
 		'http://projects.eclipse.org/json/reviews/complete',
 		'http://polarsys.org/json/reviews/complete',
-		//'http://locationtech.org/json/reviews/upcoming'
+		'http://locationtech.org/json/reviews/complete'
 	);
 	
 	foreach($forges as $forge) {
@@ -305,6 +324,10 @@
 	return $reviews;
 }
 
+/**
+ * @deprecated
+ * @param unknown $projectId
+ */
 function getReviewsForProject($projectId) {
 	_loadReviews();
 	global $_ProjectIdToReviewMap;
@@ -315,6 +338,33 @@
 	return array();
 }
 
+/**
+ * State should be one of 'complete' or 'upcoming'
+ * 
+ * @param unknown $state
+ * @param unknown $limit
+ */
+function getReviews($state = 'upcoming', $limit=null) {
+	$reviews = array();
+	
+	foreach(getForges() as $forge) {
+		$url = "$forge/json/reviews/$state";
+		if ($list = json_decode(getUrlContents($url), true)) {
+			foreach($list as $row) {
+				$review = new Review($row);
+				$review->statuses[] = new ReviewStatus($row);
+				$reviews[$review->getId()] = $review;
+			}
+		}
+	}
+	usort($reviews, 'sortByReviewDate');
+
+	if ($limit) 
+		return array_slice($reviews, 0, $limit);
+
+	return $reviews;
+}
+
 function sortByReviewDate($a, $b) {
 	$dateA = $a->getReviewDate();
 	$dateB = $b->getReviewDate();
@@ -322,4 +372,35 @@
 	if ($dateA == $dateB) return 0;
 	return $dateA > $dateB ? -1 : 1;
 }
+
+/**
+ * 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;
+}
 ?>
\ No newline at end of file
diff --git a/classes/common.php b/classes/common.php
index ffa8e96..a88aade 100644
--- a/classes/common.php
+++ b/classes/common.php
@@ -1,6 +1,6 @@
 <?php
 /*******************************************************************************
- * Copyright (c) 2010, 2011 Eclipse Foundation and others.
+ * Copyright (c) 2010, 2015 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
@@ -457,4 +457,38 @@
 
 class ValidationException extends Exception {}
 
+function getForges() {
+	return array(
+			'https://projects.eclipse.org',
+			'https://www.polarsys.org',
+			'https://www.locationtech.org'
+	);
+}
+
+function getUrlContents($url) {
+	$ch = curl_init();
+	curl_setopt($ch,CURLOPT_URL,$url);
+	curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
+	$page = curl_exec($ch);
+	curl_close($ch);
+	return $page;
+}
+
+function findPreviousThursday($start = 'now') {
+	$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 = 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);
+}
 ?>
\ No newline at end of file
diff --git a/reviews-rss.php b/reviews-rss.php
index 586650f..3ac0489 100644
--- a/reviews-rss.php
+++ b/reviews-rss.php
@@ -1,6 +1,6 @@
 <?php
 /*******************************************************************************
- * Copyright (c) 2010 Eclipse Foundation and others.
+ * Copyright (c) 2010, 2015 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
@@ -23,7 +23,8 @@
 
 //header("Content-Type: application/rss+xml");
 require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php");
-require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Activity.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Review.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/debug.php");
 $App = new App();
 
 $age = 90;
@@ -32,19 +33,18 @@
 	if (is_numeric($value) && $value > 0) $age = $value;
 }
 
-$activities = getRecentActivity(30,-1, false);
+$activities = getReviews('complete', $age);
 
 ?>
 <rss version="2.0">
 <channel>
-<title>Eclipse Proposals and Reviews</title>
+<title>Eclipse Reviews</title>
 <link>
 http://www.eclipse.org/projects/whatsnew.php
 </link>
 <description>
-Upcoming Creation, Validation, and Release Reviews of Projects and
-Proposals along with state changes to Proposals (new proposals, updated
-proposals, approved projects, etc) at the Eclipse Foundation.
+Upcoming reviews of Projects and
+Proposals at the Eclipse Foundation.
 </description>
 <language>
 en-us
@@ -62,11 +62,12 @@
 webmaster@eclipse.org
 </webMaster>
 <?php
+
 foreach($activities as $activity) {
 	$title = htmlentities($activity->getTitle());
 	$link = $activity->getLink();
 	$date = date('r', $activity->getDate());
-	$guid = $activity->getGuid();
+	$guid = sha1($link);
 
 	echo "<item>\n";
 	echo "\t<title>$title</title>\n";
diff --git a/tests/test-Activity.class.php b/tests/test-Activity.class.php
deleted file mode 100644
index dbcfceb..0000000
--- a/tests/test-Activity.class.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php");
-$App = new App();
-
-require_once '../classes/Activity.class.php';
-require_once '../classes/TestRunner.class.php';
-
-class ActivityTests extends TestCase {
-	function testFindPreviousThursday() {
-		$this->assertEquals('2011-05-26', findPreviousThursday('2011-05-31'));
-		$this->assertEquals('2011-05-26', findPreviousThursday('2011-06-01'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-02'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-03'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-04'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-05'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-06'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-07'));
-		$this->assertEquals('2011-06-02', findPreviousThursday('2011-06-08'));
-		$this->assertEquals('2011-06-09', findPreviousThursday('2011-06-09'));
-		$this->assertEquals('2011-06-09', findPreviousThursday('2011-06-10'));
-		$this->assertEquals('2011-06-09', findPreviousThursday('2011-06-11'));
-	}
-	
-	function testFindNextWednesday() {
-		$this->assertEquals('2011-05-25', findNextWednesday('2011-05-23'));
-		$this->assertEquals('2011-05-25', findNextWednesday('2011-05-24'));
-		$this->assertEquals('2011-05-25', findNextWednesday('2011-05-25'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-26'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-27'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-28'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-29'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-30'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-05-31'));
-		$this->assertEquals('2011-06-01', findNextWednesday('2011-06-01'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-02'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-03'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-04'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-05'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-06'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-07'));
-		$this->assertEquals('2011-06-08', findNextWednesday('2011-06-08'));
-		$this->assertEquals('2011-06-15', findNextWednesday('2011-06-09'));
-	}
-}
-
-$App = new App();
-
-$runner = new TestRunner("ActivityTests");
-$runner->run_tests();
-
-?>
\ No newline at end of file
diff --git a/tests/test-reviews.php b/tests/test-reviews.php
deleted file mode 100644
index 3f98fb7..0000000
--- a/tests/test-reviews.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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
- *******************************************************************************/
-
-include_once $_SERVER['DOCUMENT_ROOT'] . '/projects/common/activity.class.php';
-
-echo generateRecentActivitySql(10, 25, true);
-echo "<br><br>";
-
-echo generateRecentActivitySql(10, 25, false);
-echo "<br><br>";
-
-echo generateRecentActivitySql(10, -1, false);
-echo "<br><br>";
-
-
-echo generateNewProjectProposalsSql(6);
-echo "<br><br>";
-?>
\ No newline at end of file
diff --git a/web-parts/activity.php b/web-parts/activity.php
index 06079ae..d4a6886 100755
--- a/web-parts/activity.php
+++ b/web-parts/activity.php
@@ -14,29 +14,17 @@
  * This script assumes that it is being included by another script. We
  * assume that the $App variable has already been defined.
  */
+require_once ($_SERVER ['DOCUMENT_ROOT'] . "/projects/classes/Review.class.php");
+$reviews = getreviews ('complete', 6);
 
-	require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Activity.class.php");
-	$activities = getRecentActivity(30,7, false);
+print '<div class="block-box"><h3>Recent Activity <a href="/projects/reviews-rss.php"><i class="fa fa-rss"></i></a></h3><div class="content">';
+print '<ul class="list-unstyled reset">';
 
-	print '<div class="block-box"><h3>Recent Activity <a href="/projects/reviews-rss.php"><i class="fa fa-rss"></i></a></h3><div class="content">';
-	print '<ul class="list-unstyled reset">';
-
-  foreach ($activities as $activity) :
-		$title = htmlentities($activity->getTitle());
-		$link = $activity->getLink();
-		$date = $App->getFormattedDate($activity->getDate(), 'long');
-		$date = str_replace(' ', '&nbsp;', $date);
-    $html_date = '<span class="orange date">' . $date . ' </span>';
-
-    // Only print unique dates.
-    if(isset($last_date) && $last_date == $date){
-      $html_date = "";
-    }
-
-    $last_date = $date;
-		print '<li>' . $html_date . '<a href="' . $link . '">' . $title . '</a></li>';
-
-  endforeach;
+/** @var \Review $review */
+foreach ( $reviews as $review ) {
+	
+	print '<li>' . $review->asHtml() . '</li>';
+}
 
 print '</ul>';
 print '</div></div>';
diff --git a/web-parts/proposals.php b/web-parts/proposals.php
index e622f60..890c95d 100755
--- a/web-parts/proposals.php
+++ b/web-parts/proposals.php
@@ -15,20 +15,21 @@
  * assume that the $App variable has already been defined.
  */
 
-	require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Activity.class.php");
-	$activities = getNewProjectProposals();
+	require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Proposal.class.php");
+	$activities = getProposals();
 
   print '<div class="block-box"><h3>New Project Proposals</h3><div class="content">';
   if (!$activities) :
 		print '<p>There are no new project proposals at this time.</p>';
 	else:
 	  print '<ul class="list-unstyled reset">';
+		/** @var \Proposal $activity */
 		foreach ($activities as $activity) :
 			$name = htmlentities($activity->getName());
 
 			if ($activity->isWithdrawn()) continue;
 
-			$link = $activity->getLink();
+			$link = $activity->getProposalUrl();
 
 			$date = $App->getFormattedDate($activity->getDate(), 'long');
 			$date = str_replace(' ', '&nbsp;', $date);
diff --git a/web-parts/reviews.php b/web-parts/reviews.php
index 4f7b81d..2cd91df 100755
--- a/web-parts/reviews.php
+++ b/web-parts/reviews.php
@@ -14,77 +14,38 @@
  * This script assumes that it is being included by another script. We
  * assume that the $App variable has already been defined.
  */
+require_once ($_SERVER ['DOCUMENT_ROOT'] . "/projects/classes/Review.class.php");
+require_once (dirname ( __FILE__ ) . '/../classes/debug.php');
 
-	require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Activity.class.php");
-	require_once(dirname(__FILE__) . '/../classes/debug.php');
+$activities = getReviews ( 'upcoming' );
 
-	$activities = getUpcomingReviews();
+print '<div class="block-box"><h3>Upcoming Reviews <a href="/projects/reviews-rss.php"><i class="fa fa-rss"></i></a></h3><div class="content">';
 
-	print '<div class="block-box"><h3>Upcoming Reviews <a href="/projects/reviews-rss.php"><i class="fa fa-rss"></i></a></h3><div class="content">';
-
-  if (!$activities) :
-	  print '<p>There are no reviews scheduled at this time.</p>';
-  else:
-	  print '<p>The following reviews are scheduled, or have recently completed.</p>';
-		$lastDate = null;
-		print '<ul class="list-unstyled reset">';
-		foreach($activities as $activity) :
-
-			$get_name = htmlentities($activity->getName());
-		  $name = "";
-			$description = htmlentities($activity->getDescription()) . ' Review';
-			$slidesUrl = $activity->getSlidesUrl();
-			$iplogUrl = $activity->getIPLogUrl();
-			$bugNumber = $activity->getBugNumber();
-
-			$date = $App->getFormattedDate($activity->getDate(), 'long');
-			$date = str_replace(' ', '&nbsp;', $date);
-
-			$icons = '';
-
-			if ($iplogUrl) {
-				$icons .= '<a href="' . $iplogUrl . '"><i class="fa fa-lightbulb-o"></i></a>';
+if (! $activities) {
+	print '<p>There are no reviews scheduled at this time.</p>';
+} else {
+	print '<p>The following reviews are scheduled, or have recently completed.</p>';
+	$lastDate = null;
+	print '<ul class="list-unstyled reset">';
+	/** @var \Review $activity */
+	foreach ( $activities as $activity ) {
+		$date = $App->getFormattedDate ( $activity->getReviewDate(), 'long' );
+		$date = str_replace ( ' ', '&nbsp;', $date );
+		
+		$html_date = '<span class="orange date">' . $date . ' </span>';
+		if ($lastDate) {
+			if ($lastDate != $date) {
+				echo '</ul><hr class="reset"/><br/><ul class="list-unstyled reset">';
+			} else {
+				// Only print unique dates.
+				$html_date = "";
 			}
-
-			if ($bugNumber) {
-				$icons .= '<a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=' . $bugNumber . '"><i class="fa fa-bug"></i></a>';
-			}
-
-			$status = $activity->getStatus();
-
-			if ($activity->isSuccessful()) {
-				$name .= '<i class="fa fa-check green"></i> ';
-			}
-
-	    if ($slidesUrl) {
-				$name .= '<a href="' . $slidesUrl . '">' . $get_name . ' ' . $description . '</a>';
-			}else{
-        $name .= $get_name . ' ' . $description;
-			}
-
-			if ($activity->isUnsuccessful()) {
-				$date = '<strike>' . $date .' </strike>';
-				$name = '<strike>' . $get_name . '</strike>';
-				$description = '<strike>$description</strike>';
-				$icons .= '<i class="fa fa-stop red"></i>';
-			}
-
-      $html_date = '<span class="orange date">' . $date . ' </span>';
-			if ($lastDate) {
-				if ($lastDate != $date) {
-					echo '</ul><hr class="reset"/><br/><ul class="list-unstyled reset">';
-				}
-				else {
-					// Only print unique dates.
-					$html_date = "";
-				}
-			}
-
-			$lastDate = $date;
-
-
-			print '<li>' . $html_date . $name  . ' ' . $icons . '</li>';
-		endforeach;
-		print '</ul>';
-	endif;
-	print '</div></div>';
+		}
+		
+		$lastDate = $date;
+		
+		print '<li>' . $activity->asHtml() . '</li>';
+	}
+	print '</ul>';
+}
+print '</div></div>';