Experimental new PMC tool.

Change-Id: I689244bae9c8fe2a91af588a116b534ecae9f454
diff --git a/classes/Release.class.inc b/classes/Release.class.inc
new file mode 100755
index 0000000..5aef9ff
--- /dev/null
+++ b/classes/Release.class.inc
@@ -0,0 +1,144 @@
+<?php
+/**
+ * *****************************************************************************
+ * Copyright (c) 2017 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/Project.class.php';
+require_once dirname ( __FILE__ ) . '/../classes/debug.php';
+require_once dirname ( __FILE__ ) . '/../classes/database.inc';
+
+class Release {
+	var $project;
+	var $name;
+	var $date;
+
+	/**
+	 * This method answers an array containing all of
+	 * @param unknown $root
+	 * @param unknown $date
+	 * @return Release
+	 */
+	public static function releasesSince($root = null, $date = null) {
+		$releases = array ();
+		$sql = "
+			select
+				project as id, name, date
+			from ProjectReleases
+			where id regexp :pattern
+				and date > date(:date)
+			order by date";
+		$args = array(
+			':pattern' => $root ? "^${root}(?:\..*)?$" : '',
+			':date' => date('Y-m-d', $date ? $date : time())
+		);
+
+		return new DatabaseQuery('dashboard', $sql, $args, function ($row) {
+			$project = Project::getProject ( $row ['id'] );
+			$name = $row ['name'];
+			$date = strtotime ( $row ['date'] );
+			$reviewDate = self::getProbableReviewDate ( $date );
+
+			return new Release ( $project, $name, $date );
+		} );
+	}
+
+	private function __construct($project, $name, $date) {
+		$this->project = $project;
+		$this->name = $name;
+		$this->date = $date;
+	}
+
+	function getId() {
+		return $this->project->getId ();
+	}
+
+	function getUrl() {
+		return $this->project->getUrl ();
+	}
+
+	function getName() {
+		return $this->project->getName () . ' ' . $this->name;
+	}
+
+	function getDate() {
+		return $this->date;
+	}
+
+	private static function getProbableReviewDate($date) {
+		$diff = date ( 'w', $date ) - 3; // '3' means Wednesday
+		if ($diff < 0)
+			$diff += 7;
+
+		$date = strtotime ( "-$diff days", $date );
+		$now = strtotime ( 'now' );
+		$dates = array ();
+		while ( $date > $now ) {
+			// There's probably a better way to do this, but
+			// the month boundaries are icky, this works, and is simple.
+			if (self::isFirstOrThirdWeek ( $date )) {
+				return $date;
+			}
+			$date = strtotime ( "-1 week", $date );
+		}
+		return 0;
+	}
+
+	/**
+	 * Answers whether the provided date represents either the
+	 * first or the third week of the month.
+	 *
+	 * @see Release::getProbableReviewDate()
+	 * @param int $date
+	 */
+	private static function isFirstOrThirdWeek($date) {
+		$day = date ( 'j', $date ) - 1; // 0-based day of the month
+		if (floor ( $day / 7 ) == 0)
+			return true; // first week
+		if (floor ( $day / 7 ) == 2)
+			return true; // third week
+		return false;
+	}
+}
+
+
+
+function dumpFutureReleases() {
+ foreach(getFutureReleases() as $date => $releases) {
+  echo "<h3>" . ($date == 0 ? "It's already too late" : ("For review on " . date('Y-m-d', $date))) . "</h3>";
+  echo "<ul>";
+  foreach($releases as $release) {
+   echo "<li><a href=\"{$release->getUrl()}\">{$release->getName()}</a> ";
+   echo date('Y-m-d', $release->getDate());
+   dumpOpenCQs($release);
+   echo "</li>";
+  }
+  echo "</ul>";
+ }
+}
+
+function dumpOpenCQs(Release $release) {
+ echo "<ul>";
+ $sql = '
+  select
+   b.bug_id as id, b.short_desc as title
+  from bugs as b
+   join components as c on b.component_id=c.id
+  where c.name=\'$id\'
+   and bug_status in (\'NEW\', \'REOPENED\')';
+
+ query ('ipzilla', $sql, array('$id' => $release->getId()), function ($row) {
+  $id = $row ['id'];
+  $title = $row ['title'];
+  echo "<li><a target=_blank href=\"https://dev.eclipse.org/ipzilla/show_bug.cgi?id=$id\">$id</a> $title</li>";
+ });
+ echo "</ul>";
+}
+
+
diff --git a/classes/common.php b/classes/common.php
index ffa51cf..8f2c060 100644
--- a/classes/common.php
+++ b/classes/common.php
@@ -325,8 +325,6 @@
 		return;
 	}
 
-	require_once ($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/classes/friends/friend.class.php");
-
 	$Session = $App->useSession("required");
 	$friend = $Session->getFriend();
 
diff --git a/classes/database.inc b/classes/database.inc
index b58991b..bc39976 100644
--- a/classes/database.inc
+++ b/classes/database.inc
@@ -88,4 +88,21 @@
 
   $mysqli->close();
 }
+
+class DatabaseQuery {
+	var $database, $query, $args, $callable;
+
+	function __construct($database, $query, $args, $callable) {
+		$this->database = $database;
+		$this->query = $query;
+		$this->args = $args;
+		$this->callable = $callable;
+	}
+
+	public function withEach($callback) {
+		query($this->database, $this->query, $this->args, function($row) use (&$callback) {
+			call_user_func($callback, call_user_func($this->callable, $row));
+		});
+	}
+}
 ?>
\ No newline at end of file
diff --git a/tools/pmc.php b/tools/pmc.php
new file mode 100644
index 0000000..d1ae379
--- /dev/null
+++ b/tools/pmc.php
@@ -0,0 +1,96 @@
+<?php
+/*******************************************************************************
+ * Copyright (c) 2018 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
+ *******************************************************************************/
+
+/**
+
+ */
+require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/nav.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/menu.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/Project.class.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/common.php");
+require_once($_SERVER['DOCUMENT_ROOT'] . "/projects/classes/database.inc");
+
+$App = new App();
+$Nav = new Nav();
+$Menu = new Menu();
+include($App->getProjectCommon());
+
+$pageTitle = "Eclipse Foundation PMC Tasks";
+$pageKeywords = "";
+$pageAuthor = "Wayne Beaton";
+
+$root = Project::getProject($_GET['id']);
+if ($root->isTopLevel()) {
+
+}
+
+function withCqsAwaitingPMC($id, $callable) {
+	$sql = "
+		select
+			b.bug_id as id,
+			c.name as project,
+			b.short_desc as description,
+			b.cf_type as type,
+			date(b.creation_ts) as created,
+			b.bug_status as status,
+			b.resolution as resolution,
+			b.bug_severity as state
+		from bugs as b
+			join components as c on b.component_id = c.id
+			join products as p on b.product_id = p.id
+		where p.name='{$id}'
+			and bug_status='NEW'
+			and bug_severity='awaiting_pmc'
+		order by c.name
+	";
+	query('ipzilla', $sql, array(), $callable);
+}
+
+
+ob_start();
+?>
+<div id="maincontent">
+<div id="midcolumn">
+<h1><?= $pageTitle ?></h1>
+
+<p><strong>Experimental</strong> Provide a list of links to tasks that require the PMC's attention.</p>
+
+<h3>Intellectual Property</h3>
+
+<?php
+$cqs = array();
+withCqsAwaitingPMC($root->getId(), function($cq) use (&$cqs) {
+	$cqs[$cq['project']][] = $cq;
+});
+
+if ($cqs) {
+	print "<p>The following CQs require some attention from the PMC.</p>";
+
+	foreach ($cqs as $id => $list) {
+		$project = Project::getProject($id);
+		$name = $project ? $project->getFormalName() : $id;
+		print "<h4>{$name}</h4>";
+		print "<ul>";
+		foreach($list as $cq) {
+			print "<li><a href=\"https://dev.eclipse.org/ipzilla/show_bug.cgi?id={$cq['id']}\">{$cq['id']}</a> {$cq['description']}</li>";
+		}
+		print "</ul>";
+	}
+}
+?>
+
+</div>
+</div>
+
+<?php
+$html = ob_get_contents();
+ob_end_clean();
+$App->generatePage($theme, $Menu, $Nav, $pageAuthor, $pageKeywords, $pageTitle, $html);
+?>