You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by jf...@apache.org on 2010/08/30 21:39:31 UTC

svn commit: r990918 - in /incubator/vcl/trunk/web: ./ .ht-inc/ .ht-inc/maintenance/ css/ js/

Author: jfthomps
Date: Mon Aug 30 19:39:30 2010
New Revision: 990918

URL: http://svn.apache.org/viewvc?rev=990918&view=rev
Log:
VCL-208
Ability to easily put the VCL site into a maintenance state

xmlrpcWrappers.php: modified XMLRPCextendRequest and XMLRPCsetRequestEnding - added check for isAvailable returning -2; if so, return errorcode 46: requested time is during a maintenance window

utils.php:
-modified initGlobals - added case statement for including sitemaintenance.php
-added maintenanceCheck - prints notice about site being down for maintenance if during maintenance window
-added maintenanceNotice - prints notice about upcoming maintenance window
-modified isAvailable - calls schCheckMaintenance to see if request conflicts with scheduled maintenance window; return -2 if it does
-added schCheckMaintenance - checks for time window conflicting with a maintenance window
-added numdatetimeToDatetime
-modified getTimeSlots - create entries for maintenance windows
-modified showTimeTable - print blocks for maintenance windows
-added checkInMaintenanceForTimeTable
-added getMaintItems
-added getMaintItemsForTimeTable
-modified printHTMLHeader - added call to maintenanceNotice
-modified getNavMenu - added entry for Site Maintenance to show up for ADMIN_DEVELOPER users
-modified getExtraCSS - return empty array if no matched modes (removes a php warning message)
-modified getDojoHTML - added includes and header stuff for siteMaintenance mode

states.php: added siteMaintenance, AJcreateSiteMaintenance, AJgetSiteMaintenanceData, AJgetDelSiteMaintenanceData, AJeditSiteMaintenance, and AJDeleteSiteMaintenance

conf-default.php: added DEFAULTTHEME - this is the theme that will be used when the site is placed in maintenance if $_COOKIE['VCLSKIN'] is not set

requests.php: modified AJupdateWaitTime and submitEditRequest - added check for isAvailable returning -2 and if so, display message about not being able to schedule due to scheduled downtime

sitemaintenance.php: initial add

maintenance: added directory for placing maintenance files

testsetup.php: added section to test write access to maintenance directory

index.php: added call to maintenanceCheck before calling dbConnect

css/vcl.css: added #maintenancenotice - this is the div that shows up notifying users of an upcoming maintenance window

js/sitemaintenance.js - initial add

Added:
    incubator/vcl/trunk/web/.ht-inc/maintenance/
    incubator/vcl/trunk/web/.ht-inc/sitemaintenance.php
    incubator/vcl/trunk/web/js/sitemaintenance.js
Modified:
    incubator/vcl/trunk/web/.ht-inc/conf-default.php
    incubator/vcl/trunk/web/.ht-inc/requests.php
    incubator/vcl/trunk/web/.ht-inc/states.php
    incubator/vcl/trunk/web/.ht-inc/utils.php
    incubator/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php
    incubator/vcl/trunk/web/css/vcl.css
    incubator/vcl/trunk/web/index.php
    incubator/vcl/trunk/web/testsetup.php

Modified: incubator/vcl/trunk/web/.ht-inc/conf-default.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/conf-default.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/conf-default.php (original)
+++ incubator/vcl/trunk/web/.ht-inc/conf-default.php Mon Aug 30 19:39:30 2010
@@ -81,6 +81,8 @@ define("USEFILTERINGSELECT", 1); // set 
                                  // the filteringselect can be a little slow for a large number of items
 define("FILTERINGSELECTTHRESHOLD", 300); // if USEFILTERINGSELECT = 1, only use them for selects up to this size
 
+define("DEFAULTTHEME", 'default'); // this is the theme that will be used when the site is placed in maintenance if $_COOKIE['VCLSKIN'] is not set
+
 $ENABLE_ITECSAUTH = 0;     // use ITECS accounts (also called "Non-NCSU" accounts)
 
 $userlookupUsers = array(1, # admin

Modified: incubator/vcl/trunk/web/.ht-inc/requests.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/requests.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/requests.php (original)
+++ incubator/vcl/trunk/web/.ht-inc/requests.php Mon Aug 30 19:39:30 2010
@@ -251,6 +251,10 @@ function AJupdateWaitTime() {
 	$rc = isAvailable($images, $imageid, $start, $end, '');
 	semUnlock();
 	print "dojo.byId('waittime').innerHTML = ";
+	if($rc == -2) {
+		print "'<font color=red>Selection not currently available due to scheduled system downtime for maintenance</font>'; ";
+		print "if(dojo.byId('newsubmit')) dojo.byId('newsubmit').value = 'View Time Table';";
+	}
 	if($rc < 1) {
 		print "'<font color=red>Selection not currently available</font>'; ";
 		print "if(dojo.byId('newsubmit')) dojo.byId('newsubmit').value = 'View Time Table';";
@@ -1840,8 +1844,13 @@ function submitEditRequest() {
 		               'length' => $data['length'],
 		               'requestid' => $data['requestid']);
 		$cont = addContinuationsEntry('selectTimeTable', $cdata);
-		print "The time you have requested is not available. You may ";
-		print "<a href=\"" . BASEURL . SCRIPT . "?continuation=$cont\">";
+		if($rc == -2) {
+			print "The time you have requested is not available due to scheduled ";
+			print "system downtime for maintenance. ";
+		}
+		else
+			print "The time you have requested is not available. ";
+		print "You may <a href=\"" . BASEURL . SCRIPT . "?continuation=$cont\">";
 		print "view a timetable</a> of free and reserved times to find ";
 		print "a time that will work for you.<br>\n";
 		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),

Added: incubator/vcl/trunk/web/.ht-inc/sitemaintenance.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/sitemaintenance.php?rev=990918&view=auto
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/sitemaintenance.php (added)
+++ incubator/vcl/trunk/web/.ht-inc/sitemaintenance.php Mon Aug 30 19:39:30 2010
@@ -0,0 +1,582 @@
+<?php
+/*
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/**
+ * \file
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn siteMaintenance()
+///
+/// \brief prints a page for managing site maintenance
+///
+////////////////////////////////////////////////////////////////////////////////
+function siteMaintenance() {
+	$update = getContinuationVar('update', 0);
+	$items = getMaintItems();
+
+	$rt = '';
+	if(! $update) {
+		$rt .= "<h2>Site Maintenance</h2>\n";
+		$rt .= "<div id=\"maintenancediv\">\n";
+	}
+	$rt .= "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+	$rt .= "  Schedule Site Maintenance\n";
+	$rt .= "  <script type=\"dojo/method\" event=\"onClick\">\n";
+	$cont = addContinuationsEntry('AJcreateSiteMaintenance', array('update' => 1));
+	$rt .= "    showAddSiteMaintenance('$cont');\n";
+	$rt .= "  </script>\n";
+	$rt .= "</button><br>\n";
+	if(count($items)) {
+		$rt .= "<table>\n";
+		$rt .= "<tr>\n";
+		$rt .= "<td colspan=2></td>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Start</th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">End</th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Owner</th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Created</th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Reason <small>(hover for more)</small></th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">User Message <small>(hover for more)</small></th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Inform Hours Ahead</th>\n";
+		$rt .= "<th style=\"vertical-align: bottom;\">Allow Reservations</th>\n";
+		$rt .= "</tr>\n";
+		foreach($items as $item) {
+			$tmp = datetimeToUnix($item['start']);
+			$start = date('g:i&\n\b\s\p;A n/j/Y', $tmp);
+			$tmp = datetimeToUnix($item['end']);
+			$end = date('g:i&\n\b\s\p;A n/j/Y', $tmp);
+			$tmp = datetimeToUnix($item['created']);
+			$created = date('g:i&\n\b\s\p;A n/j/Y', $tmp);
+			$rt .= "<tr>\n";
+			$rt .= "<td>\n";
+			$rt .= "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+			$rt .= "  Edit\n";
+			$rt .= "  <script type=\"dojo/method\" event=\"onClick\">\n";
+			$data = array('id' => $item['id']);
+			$cont = addContinuationsEntry('AJgetSiteMaintenanceData', $data);
+			$rt .= "    showEditSiteMaintenance('$cont');\n";
+			$rt .= "  </script>\n";
+			$rt .= "</button>\n";
+			$rt .= "</td>\n";
+			$rt .= "<td>\n";
+			$rt .= "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+			$rt .= "  Delete\n";
+			$rt .= "  <script type=\"dojo/method\" event=\"onClick\">\n";
+			$cont = addContinuationsEntry('AJgetDelSiteMaintenanceData', $data);
+			$rt .= "    confirmDeleteSiteMaintenance('$cont');\n";
+			$rt .= "  </script>\n";
+			$rt .= "</button>\n";
+			$rt .= "</td>\n";
+			$rt .= "<td align=center>$start</td>\n";
+			$rt .= "<td align=center>$end</td>\n";
+			$rt .= "<td align=center>{$item['owner']}</td>\n";
+			$rt .= "<td align=center>$created</td>\n";
+			if(strlen($item['reason']) < 30)
+				$rt .= "<td align=center>{$item['reason']}</td>\n";
+			else {
+				$rt .= "<td align=center>\n";
+				$reason = substr($item['reason'], 0, 30) . '...';
+				$rt .= "  <span id=\"morereason\">$reason</span>\n";
+				$rt .= "  <div dojoType=\"dijit.Tooltip\" connectId=\"morereason\">\n";
+				$reason = preg_replace('/(.{1,50}[ \n])/', '\1<br>', $item['reason']);
+				$reason = preg_replace('/\n<br>\n/', "<br><br>\n", $reason);
+				$rt .= "$reason</div>\n";
+				$rt .= "</td>\n";
+			}
+			if(strlen($item['usermessage']) < 30)
+				$rt .= "<td align=center>{$item['usermessage']}</td>\n";
+			else {
+				$rt .= "<td align=center>\n";
+				$msg = substr($item['usermessage'], 0, 30) . '...';
+				$rt .= "  <span id=\"moreusermsg\">$msg</span>\n";
+				$rt .= "  <div dojoType=\"dijit.Tooltip\" connectId=\"moreusermsg\">\n";
+				$msg = preg_replace('/(.{1,50}[ \n])/', '\1<br>', $item['usermessage']);
+				$msg = preg_replace('/\n<br>\n/', "<br><br>\n", $msg);
+				$rt .=  "$msg</div>\n";
+				$rt .= "</td>\n";
+			}
+			$hours = $item['informhoursahead'] % 24;
+			if($hours == 1)
+				$hours = (int)($item['informhoursahead'] / 24) . " days, 1 hour";
+			else
+				$hours = (int)($item['informhoursahead'] / 24) . " days, $hours hours";
+			$rt .= "<td align=center>$hours</td>\n";
+			if($item['allowreservations'])
+				$rt .= "<td align=center>Yes</td>\n";
+			else
+				$rt .= "<td align=center>No</td>\n";
+			$rt .= "</tr>\n";
+		}
+		$rt .= "</table>\n";
+	}
+	if($update) {
+		$send = str_replace("\n", '', $rt);
+		$send = str_replace("'", "\'", $send);
+		$send = preg_replace("/>\s*</", "><", $send);
+		print setAttribute('maintenancediv', 'innerHTML', $send);
+		print "AJdojoCreate('maintenancediv');";
+		return;
+	}
+	print $rt;
+	print "</div>\n"; # end maintenancediv
+
+	print "<div id=\"editDialog\" dojoType=\"dijit.Dialog\" autofocus=\"false\">\n";
+	print "<h2><span id=\"editheader\"></span></h2>\n";
+	print "<span id=\"edittext\"></span><br><br>\n";
+	print "<table summary=\"\">\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">Start:</th>\n";
+	print "    <td>\n";
+	print "      <div type=\"text\" id=\"starttime\" dojoType=\"dijit.form.TimeTextBox\" ";
+	print "required=\"true\" style=\"width: 80px\"></div>\n";
+	print "      <div type=\"text\" id=\"startdate\" dojoType=\"dijit.form.DateTextBox\" ";
+	print "required=\"true\" style=\"width: 100px\"></div>\n";
+	print "    </td>\n";
+	print "  </tr>\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">End:</th>\n";
+	print "    <td>\n";
+	print "      <div type=\"text\" id=\"endtime\" dojoType=\"dijit.form.TimeTextBox\" ";
+	print "required=\"true\" style=\"width: 80px\"></div>\n";
+	print "      <div type=\"text\" id=\"enddate\" dojoType=\"dijit.form.DateTextBox\" ";
+	print "required=\"true\" style=\"width: 100px\"></div>\n";
+	print "    </td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=\"top\">\n";
+	print "    <th align=\"right\">Inform Hours Ahead:</th>\n";
+	print "    <td>\n";
+	print "      <input dojoType=\"dijit.form.NumberSpinner\"\n";
+	print "             constraints=\"{min:1,max:65535}\"\n";
+	print "             maxlength=\"3\"\n";
+	print "             id=\"hoursahead\"\n";
+	print "             format=\"updateDaysHours\"\n";
+	print "             required=\"true\"\n";
+	print "             style=\"width: 70px\">\n";
+	print "      <span id=dayshours></span>\n";
+	print "    </td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=\"top\">\n";
+	print "    <th align=\"right\">Allow Reservations:</th>\n";
+	print "    <td>\n";
+	print "      <select id=allowreservations dojoType=\"dijit.form.Select\">";
+	print "        <option value=\"1\">Yes</option>\n";
+	print "        <option value=\"0\">No</option>\n";
+	print "      </select>\n";
+	print "    </td>\n";
+	print "  </tr>\n";
+	print "</table>\n";
+	print "<b>Reason</b> (this is not displayed to any users and is just for your records):<br>\n";
+	print "<textarea id=\"reason\" dojoType=\"dijit.form.Textarea\" style=\"width: 400px;\">\n";
+	print "</textarea><br>\n";
+	print "<b>User Message</b> (this will be displayed on the site during the maintenance window):<br>\n";
+	print "<textarea id=\"usermessage\" dojoType=\"dijit.form.Textarea\" style=\"width: 400px;\">\n";
+	print "</textarea>\n";
+	print "<input type=\"hidden\" id=\"submitcont\">\n";
+	print "<div align=\"center\">\n";
+	print "<button dojoType=\"dijit.form.Button\" type=\"button\" id=\"editsubmitbtn\">\n";
+	print "  Save Changes\n";
+	print "  <script type=\"dojo/method\" event=\"onClick\">\n";
+	print "    editSubmit();\n";
+	print "  </script>\n";
+	print "</button>\n";
+	print "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+	print "  Cancel\n";
+	print "  <script type=\"dojo/method\" event=\"onClick\">\n";
+	print "    clearEdit();\n";
+	print "  </script>\n";
+	print "</button>\n";
+	print "</div>\n";
+	print "</div>\n"; # edit dialog
+
+	print "<div id=\"confirmDialog\" dojoType=\"dijit.Dialog\" title=\"Delete Site Maintenance\">\n";
+	print "<h2>Delete Site Maintenance</h2>\n";
+	print "Click <b>Delete Entry</b> to delete this site maintenance entry<br><br>\n";
+	print "<table summary=\"\">\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">Start:</th>\n";
+	print "    <td><span id=\"start\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">End:</th>\n";
+	print "    <td><span id=\"end\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">Owner:</th>\n";
+	print "    <td><span id=\"owner\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr>\n";
+	print "    <th align=\"right\">Created:</th>\n";
+	print "    <td><span id=\"created\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=\"top\">\n";
+	print "    <th align=\"right\">Inform Hours Ahead:</th>\n";
+	print "    <td><span id=\"informhoursahead\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=\"top\">\n";
+	print "    <th align=\"right\">Allow Reservations:</th>\n";
+	print "    <td><span id=\"delallowreservations\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=top>\n";
+	print "    <th align=\"right\">Reason:</th>\n";
+	print "    <td><span id=\"delreason\"></span></td>\n";
+	print "  </tr>\n";
+	print "  <tr valign=top>\n";
+	print "    <th align=\"right\">User Message:</th>\n";
+	print "    <td><span id=\"delusermessage\"></span></td>\n";
+	print "  </tr>\n";
+	print "</table>\n";
+	print "<input type=\"hidden\" id=\"delsubmitcont\">\n";
+	print "<div align=\"center\">\n";
+	print "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+	print "  Delete Entry\n";
+	print "  <script type=\"dojo/method\" event=\"onClick\">\n";
+	print "    deleteSiteMaintenance();\n";
+	print "  </script>\n";
+	print "</button>\n";
+	print "<button dojoType=\"dijit.form.Button\" type=\"button\">\n";
+	print "  Cancel\n";
+	print "  <script type=\"dojo/method\" event=\"onClick\">\n";
+	print "    dijit.byId('confirmDialog').hide();\n";
+	print "  </script>\n";
+	print "</button>\n";
+	print "</div>\n";
+	print "</div>\n"; # confirm dialog
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJcreateSiteMaintenance()
+///
+/// \brief creates an file in the maintenance directory, creates an entry in
+/// the sitemaintenance table and does a javascript page reload
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJcreateSiteMaintenance() {
+	global $user;
+	$data = processSiteMaintenanceInput();
+	if($data['err'])
+		return;
+
+	if(! writeMaintenanceFile($data['startts'], $data['endts'], $data['usermessage'])) {
+		print "alert('Failed to create maintenance file on web server.\\n";
+		print "Please have sysadmin check permissions on maintenance directory.');\n";
+		return;
+	}
+
+	$reason = mysql_real_escape_string($data['reason']);
+	$usermessage = mysql_real_escape_string($data['usermessage']);
+	$query = "INSERT INTO sitemaintenance "
+	       .        "(start, "
+	       .        "end, "
+	       .        "ownerid, "
+	       .        "created, "
+	       .        "reason, "
+	       .        "usermessage, "
+	       .        "informhoursahead, "
+	       .        "allowreservations) "
+	       . "VALUES "
+	       .        "('{$data['startdt']}', "
+	       .        "'{$data['enddt']}', "
+	       .        "{$user['id']}, "
+	       .        "NOW(), "
+	       .        "'$reason', "
+	       .        "'$usermessage', "
+	       .        "{$data['hoursahead']}, "
+	       .        "{$data['allowreservations']})";
+	doQuery($query, 101);
+	$_SESSION['usersessiondata'] = array();
+	print "window.location.href = '" . BASEURL . SCRIPT . "?mode=siteMaintenance';";
+	#print "clearEdit();";
+	#siteMaintenance();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJgetSiteMaintenanceData()
+///
+/// \brief gets info about a sitemaintenance entry and returns it in JSON format
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJgetSiteMaintenanceData() {
+	$id = getContinuationVar('id');
+	$tmp = getMaintItems($id);
+	$data = $tmp[$id];
+	$start = datetimeToUnix($data['start']) * 1000;
+	$end = datetimeToUnix($data['end']) * 1000;
+	$cdata = array('id' => $id,
+	               'update' => 1);
+	$cont = addContinuationsEntry('AJeditSiteMaintenance', $cdata,
+	                              SECINDAY, 1, 0);
+	$arr = array('start' => $start,
+	             'end' => $end,
+	             'hoursahead' => $data['informhoursahead'],
+	             'allowreservations' => $data['allowreservations'],
+	             'reason' => $data['reason'],
+	             'usermessage' => $data['usermessage'],
+	             'cont' => $cont);
+	sendJSON($arr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJgetDelSiteMaintenanceData()
+///
+/// \brief gets info about a sitemaintenance entry to be displayed on the
+/// confirm delete dialog and returns it in JSON format
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJgetDelSiteMaintenanceData() {
+	$id = getContinuationVar('id');
+	$tmp = getMaintItems($id);
+	$data = $tmp[$id];
+	$cdata = array('id' => $id,
+	               'update' => 1,
+	               'start' => datetimeToUnix($data['start']));
+	$cont = addContinuationsEntry('AJdeleteSiteMaintenance', $cdata,
+	                              SECINDAY, 1, 0);
+	$tmp = datetimeToUnix($data['start']);
+	$start = date('g:i A, n/j/Y', $tmp);
+	$tmp = datetimeToUnix($data['end']);
+	$end = date('g:i A, n/j/Y', $tmp);
+	$tmp = datetimeToUnix($data['created']);
+	$created = date('g:i A, n/j/Y', $tmp);
+	$hours = $data['informhoursahead'] % 24;
+	if($hours == 1)
+		$hours = (int)($data['informhoursahead'] / 24) . " days, 1 hour";
+	else
+		$hours = (int)($data['informhoursahead'] / 24) . " days, $hours hours";
+	$hours = "{$data['informhoursahead']} ($hours)";
+	if($data['allowreservations'])
+		$allowres = 'Yes';
+	else
+		$allowres = 'No';
+	$reason = preg_replace('/(.{1,50}[ \n])/', '\1<br>', $data['reason']);
+	$reason = preg_replace('/\n<br>\n/', "<br><br>\n", $reason);
+	$usermsg = preg_replace('/(.{1,50}[ \n])/', '\1<br>', $data['usermessage']);
+	$usermsg = preg_replace('/\n<br>\n/', "<br><br>\n", $usermsg);
+	$arr = array('start' => $start,
+	             'end' => $end,
+	             'owner' => $data['owner'],
+	             'created' => $created,
+	             'hoursahead' => $hours,
+	             'allowreservations' => $allowres,
+	             'reason' => $reason,
+	             'usermessage' => $usermsg,
+	             'cont' => $cont);
+	sendJSON($arr);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJeditSiteMaintenance()
+///
+/// \brief removes/adds file in maintenances directory and updates entry in
+/// sitemaintenance table
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJeditSiteMaintenance() {
+	global $user;
+	$id = getContinuationVar('id');
+	$data = processSiteMaintenanceInput();
+
+	$olddata = getMaintItems($id);
+	$start = datetimeToUnix($olddata[$id]['start']);
+	if(! deleteMaintenanceFile($start) ||
+	   ! writeMaintenanceFile($data['startts'], $data['endts'], $data['usermessage'])) {
+		print "alert('Failed to modify maintenance file on web server.\\n";
+		print "Please have sysadmin check permissions on maintenance directory.');\n";
+		print "dijit.byId('confirmDialog').hide();";
+		$data['err'] = 1;
+	}
+	if($data['err']) {
+		$data = array('id' => $id,
+		              'update' => 1);
+		$cont = addContinuationsEntry('AJeditSiteMaintenance', $data,
+		                              SECINDAY, 1, 0);
+		print "dojo.byId('submitcont').value = '$cont';";
+		return;
+	}
+
+	$reason = mysql_real_escape_string($data['reason']);
+	$usermessage = mysql_real_escape_string($data['usermessage']);
+	$query = "UPDATE sitemaintenance "
+	       . "SET start = '{$data['startdt']}', "
+	       .     "end = '{$data['enddt']}', "
+	       .     "ownerid = {$user['id']}, "
+	       .     "created = NOW(), "
+	       .     "reason = '$reason', "
+	       .     "usermessage = '$usermessage', "
+	       .     "informhoursahead = {$data['hoursahead']}, "
+	       .     "allowreservations = {$data['allowreservations']} "
+	       . "WHERE id = $id";
+	doQuery($query, 101);
+	$_SESSION['usersessiondata'] = array();
+	print "window.location.href = '" . BASEURL . SCRIPT . "?mode=siteMaintenance';";
+	#print "clearEdit();";
+	#siteMaintenance();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn processSiteMaintenanceInput()
+///
+/// \return array with these keys:\n
+/// \b hoursahead - corresponds to sitemaintenance table\n
+/// \b allowreservations - corresponds to sitemaintenance table\n
+/// \b reason - corresponds to sitemaintenance table\n
+/// \b usermessage - corresponds to sitemaintenance table\n
+/// \b startdt - datetime format for start of maintenance\n
+/// \b startts - unix timestamp format for start of maintenance\n
+/// \b enddt - datetime format for end of maintenance\n
+/// \b endts - unix timestamp format for end of maintenance\n
+/// \b err - whether or not an entry was found to be invalid
+///
+/// \brief validates form data for site maintenance items
+///
+////////////////////////////////////////////////////////////////////////////////
+function processSiteMaintenanceInput() {
+	$start = processInputVar('start', ARG_NUMERIC);
+	$end = processInputVar('end', ARG_NUMERIC);
+	$data['hoursahead'] = processInputVar('hoursahead', ARG_NUMERIC);
+	$data['allowreservations'] = processInputVar('allowreservations', ARG_NUMERIC);
+	$data['reason'] = processInputVar('reason', ARG_STRING);
+	$data['usermessage'] = processInputVar('usermessage', ARG_STRING);
+
+	$err = 0;
+
+	$now = time();
+	$data['startdt'] = numdatetimeToDatetime($start . '00');
+	$data['startts'] = datetimeToUnix($data['startdt']);
+	$data['enddt'] = numdatetimeToDatetime($end . '00');
+	$data['endts'] = datetimeToUnix($data['enddt']);
+
+	$reg = "/^[-0-9a-zA-Z\.,\?:;_@!#\(\)\n ]+$/";
+	if(! preg_match($reg, $data['reason'])) {
+		$errmsg = "Reason can only contain letters, numbers, spaces,\\nand these characters: . , ? : ; - _ @ ! # ( )";
+		$err = 1;
+	}
+	if(! preg_match($reg, $data['usermessage'])) {
+		$errmsg = "User Message can only contain letters, numbers, spaces,\\nand these characters: . , ? : ; - _ @ ! # ( )";
+		$err = 1;
+	}
+	if(! $err && $data['startts'] < $now) {
+		$errmsg = 'The start time and date must be later than the current time.';
+		$err = 1;
+	}
+	if(! $err && $data['endts'] <= $data['startts']) {
+		$errmsg = 'The end time and date must be later than the start time and date.';
+		$err = 1;
+	}
+	if(! $err && $data['hoursahead'] < 1) {
+		$errmsg = 'Inform Hours Ahead must be at least 1.';
+		$err = 1;
+	}
+	if(! $err && $data['hoursahead'] > 65535) {
+		$errmsg = 'Inform Hours Ahead must be less than 65536.';
+		$err = 1;
+	}
+	if(! $err && ($data['allowreservations'] != 0 && $data['allowreservations'] != 1))
+		$data['allowreservations'] = 0;
+	if(! $err && ! preg_match('/[A-Za-z]{2,}/', $data['usermessage'])) {
+		$errmsg = 'Something must be filled in for the User Message.';
+		$err = 1;
+	}
+	if($err)
+		print "alert('$errmsg');";
+	$data['err'] = $err;
+	return $data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn AJdeleteSiteMaintenance()
+///
+/// \brief removes file from maintenance directory and deletes entry from
+/// sitemaintenance table
+///
+////////////////////////////////////////////////////////////////////////////////
+function AJdeleteSiteMaintenance() {
+	$id = getContinuationVar('id');
+	$start = getContinuationVar('start');
+	if(! deleteMaintenanceFile($start)) {
+		print "alert('Failed to delete maintenance file on web server.\\n";
+		print "Please have sysadmin check permissions on maintenance directory.');\n";
+		print "dijit.byId('confirmDialog').hide();";
+		return;
+	}
+	$query = "DELETE FROM sitemaintenance WHERE id = $id";
+	doQuery($query, 101);
+	$_SESSION['usersessiondata'] = array();
+	print "window.location.href = '" . BASEURL . SCRIPT . "?mode=siteMaintenance';";
+	#print "dijit.byId('confirmDialog').hide();";
+	#siteMaintenance();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn writeMaintenanceFile($start, $end, $msg)
+///
+/// \param $start - start time of maintenance entry in unix timestamp format
+/// \param $end - end time of maintenance entry in unix timestamp format
+/// \param $msg - user message for maintenance entry
+///
+/// \return true if file created; false if not
+///
+/// \brief creates a file in the maintenance directory
+///
+////////////////////////////////////////////////////////////////////////////////
+function writeMaintenanceFile($start, $end, $msg) {
+	$name = date('YmdHi', $start);
+	$reg = "|" . SCRIPT . "$|";
+	$file = preg_replace($reg, '', $_SERVER['SCRIPT_FILENAME']);
+	$file .= "/.ht-inc/maintenance/$name";
+	if(! $fh = fopen($file, 'w')) {
+		return false;
+	}
+	$numend = date('YmdHi', $end);
+	fwrite($fh, "END=$numend\n");
+	fwrite($fh, "$msg\n");
+	fclose($fh);
+	return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn deleteMaintenanceFile($start)
+///
+/// \param $start - start time of maintenance entry in unix timestamp format
+///
+/// \return true if file deleted; false if not
+///
+/// \brief removes a file from the maintenance directory
+///
+////////////////////////////////////////////////////////////////////////////////
+function deleteMaintenanceFile($start) {
+	$name = date('YmdHi', $start);
+	$reg = "|" . SCRIPT . "$|";
+	$file = preg_replace($reg, '', $_SERVER['SCRIPT_FILENAME']);
+	$file .= "/.ht-inc/maintenance/$name";
+	if(! file_exists($file))
+		return true;
+	if(! @unlink($file))
+		return false;
+	return true;
+}
+
+?>

Modified: incubator/vcl/trunk/web/.ht-inc/states.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/states.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/states.php (original)
+++ incubator/vcl/trunk/web/.ht-inc/states.php Mon Aug 30 19:39:30 2010
@@ -54,6 +54,7 @@ $actions["entry"] = array('main',
                           'editVMInfo',
                           'continuationsError',
                           'requestBlockAllocation',
+                          'siteMaintenance',
 );
 
 $noHTMLwrappers = array('sendRDPfile',
@@ -133,6 +134,11 @@ $noHTMLwrappers = array('sendRDPfile',
                         'AJviewBlockAllocation',
                         'AJviewBlockAllocationTimes',
                         'AJtoggleBlockTime',
+                        'AJcreateSiteMaintenance',
+                        'AJgetSiteMaintenanceData',
+                        'AJgetDelSiteMaintenanceData',
+                        'AJeditSiteMaintenance',
+                        'AJdeleteSiteMaintenance',
 );
 
 # main
@@ -608,6 +614,20 @@ $actions['pages']['AJupdateVMprofileItem
 $actions['pages']['AJnewProfile'] = "vm";
 $actions['pages']['AJdelProfile'] = "vm";
 
+# site maintenance
+$actions['mode']['siteMaintenance'] = "siteMaintenance";
+$actions['mode']['AJcreateSiteMaintenance'] = "AJcreateSiteMaintenance";
+$actions['mode']['AJgetSiteMaintenanceData'] = "AJgetSiteMaintenanceData";
+$actions['mode']['AJgetDelSiteMaintenanceData'] = "AJgetDelSiteMaintenanceData";
+$actions['mode']['AJeditSiteMaintenance'] = "AJeditSiteMaintenance";
+$actions['mode']['AJdeleteSiteMaintenance'] = "AJdeleteSiteMaintenance";
+$actions['pages']['siteMaintenance'] = "sitemaintenance";
+$actions['pages']['AJcreateSiteMaintenance'] = "sitemaintenance";
+$actions['pages']['AJgetSiteMaintenanceData'] = "sitemaintenance";
+$actions['pages']['AJgetDelSiteMaintenanceData'] = "sitemaintenance";
+$actions['pages']['AJeditSiteMaintenance'] = "sitemaintenance";
+$actions['pages']['AJdeleteSiteMaintenance'] = "sitemaintenance";
+
 # RPC
 $actions['mode']['xmlrpccall'] = "xmlrpccall";
 $actions['mode']['xmlrpcaffiliations'] = "xmlrpcgetaffiliations";

Modified: incubator/vcl/trunk/web/.ht-inc/utils.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/utils.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/utils.php (original)
+++ incubator/vcl/trunk/web/.ht-inc/utils.php Mon Aug 30 19:39:30 2010
@@ -301,6 +301,9 @@ function initGlobals() {
 		case 'userLookup':
 			require_once(".ht-inc/privileges.php");
 			break;
+		case 'sitemaintenance':
+			require_once(".ht-inc/sitemaintenance.php");
+			break;
 		case 'vm':
 			require_once(".ht-inc/vm.php");
 			break;
@@ -545,6 +548,124 @@ function checkAccess() {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn maintenanceCheck()
+///
+/// \brief checks for site being in maintenance; if so, read user message from
+/// current maintenance file; print site header, maintenance message, and site
+/// foother; then exit; also removes any old maintenance files
+///
+////////////////////////////////////////////////////////////////////////////////
+function maintenanceCheck() {
+	global $authed, $mode, $user;
+	$now = time();
+	$reg = "|" . SCRIPT . "$|";
+	$search = preg_replace($reg, '', $_SERVER['SCRIPT_FILENAME']);
+	$search .= "/.ht-inc/maintenance/";
+	$files = glob("$search*");
+	$inmaintenance = 0;
+	foreach($files as $file) {
+		if(! preg_match("|^$search([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$|", $file, $matches))
+			continue;
+		#YYYYMMDDHHMM
+		$tmp = "{$matches[1]}-{$matches[2]}-{$matches[3]} {$matches[4]}:{$matches[5]}:00";
+		$start = datetimeToUnix($tmp);
+		if($start < $now) {
+			# check to see if end time has been reached
+			$fh = fopen($file, 'r');
+			$msg = '';
+			while($line = fgetss($fh)) {
+				if(preg_match("/^END=([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})$/", $line, $matches)) {
+					$tmp = "{$matches[1]}-{$matches[2]}-{$matches[3]} {$matches[4]}:{$matches[5]}:00";
+					$end = datetimeToUnix($tmp);
+					if($end < $now) {
+						fclose($fh);
+						unlink($file);
+						$_SESSION['usersessiondata'] = array();
+						return;
+					}
+					else
+						$inmaintenance = 1;
+				}
+				else
+					$msg .= $line;
+			}
+			fclose($fh);
+			if($inmaintenance)
+				break;
+		}
+	}
+	if($inmaintenance) {
+		$authed = 0;
+		$mode = 'inmaintenance';
+		$user = array();
+		if(array_key_exists('VCLSKIN', $_COOKIE))
+			$skin = strtolower($_COOKIE['VCLSKIN']);
+		else
+			$skin = DEFAULTTHEME;
+		require_once("themes/$skin/page.php");
+		printHTMLHeader();
+		print "<h2>Site Currently Under Maintenance</h2>\n";
+		if(! empty($msg)) {
+			$msg = htmlentities($msg);
+			$msg = preg_replace("/\n/", "<br>\n", $msg);
+			print "$msg<br>\n";
+		}
+		else
+			print "This site is currently in maintenance.<br>\n";
+		$niceend = date('l, F jS, Y \a\t g:i A', $end);
+		print "The maintenance is scheduled to end <b>$niceend</b>.<br><br><br>\n";
+		printHTMLFooter();
+		exit;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn maintenanceNotice()
+///
+/// \brief checks nformhoursahead for upcoming maintenance items and prints
+/// message about upcoming maintenance if currently within warning window
+///
+////////////////////////////////////////////////////////////////////////////////
+function maintenanceNotice() {
+	$items = getMaintItems();
+	foreach($items as $item) {
+		$start = datetimeToUnix($item['start']);
+		$file = date('YmdHi', $start);
+		$secahead = $item['informhoursahead'] * 3600;
+		if($start - $secahead < time()) {
+			$reg = "|" . SCRIPT . "$|";
+			$search = preg_replace($reg, '', $_SERVER['SCRIPT_FILENAME']);
+			$search .= "/.ht-inc/maintenance/$file";
+			$files = glob("$search");
+			if(empty($files)) {
+				$_SESSION['usersessiondata'] = array();
+				return;
+			}
+			$nicestart = date('g:i A \o\n l, F jS, Y', $start);
+			$niceend = date('g:i A \o\n l, F jS, Y', datetimeToUnix($item['end']));
+			print "<div id=\"maintenancenotice\">\n";
+			print "<b>NOTICE</b>: This site will be down for maintenance during ";
+			print "the following times:<br><br>\n";
+			print	"Start: $nicestart<br>\n";
+			print "End: $niceend.<br><br>\n";
+			if($item['allowreservations']) {
+				print "You will be able to access your reserved machines during ";
+				print "this maintenance. However, you will not be able to access ";
+				print "information on how to connect to them.<br>\n";
+			}
+			else {
+				print "You will not be able to access any of your reservations ";
+				print "during this maintenance.<br>\n";
+			}
+			print "</div>\n";
+			return;
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn clearPrivCache()
 ///
 /// \brief sets userresources, nodeprivileges, cascadenodeprivileges, and
@@ -3232,6 +3353,9 @@ function isAvailable($images, $imageid, 
 	$requestInfo["imageid"] = $imageid;
 	$allocatedcompids = array(0);
 
+	if(schCheckMaintenance($start, $end))
+		return -2;
+
 	if($requestInfo["start"] <= time()) {
 		$now = 1;
 		$nowfuture = 'now';
@@ -3677,6 +3801,33 @@ function RPCisAvailable($imageid, $start
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn schCheckMaintenance($start, $end)
+///
+/// \param $start - unix timestamp for start of reservation
+/// \param $end - unix timestamp for end of reservation
+///
+/// \return true if time window conflicts with maintenance window; false if not
+///
+/// \brief checks to see if the specified window conflicts with a maintenance
+/// window
+///
+////////////////////////////////////////////////////////////////////////////////
+function schCheckMaintenance($start, $end) {
+	$startdt = unixToDatetime($start);
+	$enddt = unixToDatetime($end);
+	$query = "SELECT id "
+	       . "FROM sitemaintenance "
+			 . "WHERE (allowreservations = 0 AND "
+			 .       "(('$enddt' > start) AND ('$startdt' < end))) OR "
+	       .       "(('$startdt' > (start - INTERVAL 30 MINUTE)) AND ('$startdt' < end))";
+	$qh = doQuery($query, 101);
+	if($row = mysql_fetch_row($qh))
+		return true;
+	return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn allocComputer($blockids, $currentids, $computerids, $start,
 ///                   $nowfuture)
 ///
@@ -4715,6 +4866,27 @@ function unixToDatetime($timestamp) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn numdatetimeToDatetime($numtime)
+///
+/// \param $numtime - date and time in YYYYMMDDHHMMSS format
+///
+/// \return a mysql datetime formatted string (YYYY-MM-DD HH:MM:SS)
+///
+/// \brief converts numeric date and time into datetime format
+///
+////////////////////////////////////////////////////////////////////////////////
+function numdatetimeToDatetime($numtime) {
+	$year = substr($numtime, 0, 4);
+	$month = substr($numtime, 4, 2);
+	$day = substr($numtime, 6, 2);
+	$hour = substr($numtime, 8, 2);
+	$min = substr($numtime, 10, 2);
+	$sec = substr($numtime, 12, 2);
+	return "$year-$month-$day $hour:$min:$sec";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn minuteOfDay($hour, $min)
 ///
 /// \param $hour - hour of the day (0 - 23)
@@ -5313,6 +5485,7 @@ function getTimeSlots($compids, $end=0, 
 	}
 
 	$blockData = getBlockTimeData($start, $endtime);
+	$maintItems = getMaintItemsForTimeTable($start, $endtime);
 	$reserveInfo = array();    // 0 = reserved, 1 = available
 	foreach($computerids as $id) {
 		$reserveInfo[$id] = array();
@@ -5330,11 +5503,17 @@ function getTimeSlots($compids, $end=0, 
 			}
 			print "-----------------------------------------------------<br>\n";*/
 			$reserveInfo[$id][$current]['blockAllocation'] = 0;
+			$reserveInfo[$id][$current]["inmaintenance"] = 0;
 			if(scheduleClosed($id, $current, $schedules[$scheduleids[$id]])) {
 				$reserveInfo[$id][$current]["available"] = 0;
 				$reserveInfo[$id][$current]["scheduleclosed"] = 1;
 				continue;
 			}
+			if(checkInMaintenanceForTimeTable($current, $current + 900, $maintItems)) {
+				$reserveInfo[$id][$current]["available"] = 0;
+				$reserveInfo[$id][$current]["inmaintenance"] = 1;
+				continue;
+			}
 			if($blockid = isBlockAllocationTime($id, $current, $blockData)) {
 				$reserveInfo[$id][$current]['blockAllocation'] = 1;
 				$reserveInfo[$id][$current]['blockInfo']['groupid'] = $blockData[$blockid]['groupid'];
@@ -5672,8 +5851,13 @@ function showTimeTable($links) {
 			   $computerData[$id]["stateid"] == 5)) {
 				continue;
 			}
+			# maintenance window
+			if($timeslots[$id][$stamp]["inmaintenance"] == 1) {
+				print "          <TD bgcolor=\"#a0a0a0\"><img src=images/gray.jpg ";
+				print "alt=sitemaintenance border=0></TD>\n";
+			}
 			# computer's schedule is currently closed
-			if($timeslots[$id][$stamp]["scheduleclosed"] == 1) {
+			elseif($timeslots[$id][$stamp]["scheduleclosed"] == 1) {
 				print "          <TD bgcolor=\"#a0a0a0\"><img src=images/gray.jpg ";
 				print "alt=scheduleclosed border=0></TD>\n";
 			}
@@ -6705,6 +6889,29 @@ function scheduleClosed($computerid, $ti
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn checkInMaintenanceForTimeTable($start, $end, $items)
+///
+/// \param $start - start time in unix timestamp format
+/// \param $end - end time in unix timestamp format
+/// \param $items - list of maintenance items as returned by
+///                 getMaintItemsForTimeTable
+///
+/// \return 1 if specified time period falls in an maintenance window, 0 if not
+///
+/// \brief checks if the specified time period overlaps with a scheduled
+/// maintenance window
+///
+////////////////////////////////////////////////////////////////////////////////
+function checkInMaintenanceForTimeTable($start, $end, $items) {
+	foreach($items as $item) {
+		if($item['start'] < $end && $item['end'] > $start)
+			return 1;
+	}
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn updateGroups($newusergroups, $userid)
 ///
 /// \param $newusergroups - array of $userid's current set of user groups
@@ -6824,6 +7031,97 @@ function getUserGroupName($id, $incAffil
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getMaintItems($id)
+///
+/// \param $id (optional) - if specified, id of maintenance item to get info
+///                         about
+///
+/// \return array of maintenance items where each id is a maintenance id and
+/// each element is an array with these keys:\n
+/// \b id - id of maintenance item\n
+/// \b start - start of maintenance item (datetime)\n
+/// \b end - end of maintenance item (datetime)\n
+/// \b ownerid - id from user table of owner of this maintenance item\n
+/// \b owner - unityid@affiliation of owner\n
+/// \b created - date/time entry was created (or last modified)\n
+/// \b reason - reason viewable by sysadmins for maintenance item\n
+/// \b usermessage - message viewable by all users for maintenance item\n
+/// \b informhoursahead - number of hours before start that a message will be
+///    displayed to all site users about the upcoming maintenance\n
+/// \b allowreservations - whether or not reservations can extend into this
+///    maintenance window (0 or 1)
+///
+/// \brief builds a list of current maintenance items and returns them
+///
+////////////////////////////////////////////////////////////////////////////////
+function getMaintItems($id=0) {
+	$key = getKey(array('getMaintItems', $id));
+	if(isset($_SESSION) && array_key_exists($key, $_SESSION['usersessiondata']))
+		return $_SESSION['usersessiondata'][$key];
+	$query = "SELECT m.id, "
+	       .        "m.start, "
+	       .        "m.end, "
+	       .        "m.ownerid, "
+	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
+	       .        "m.created, "
+	       .        "m.reason, "
+	       .        "m.usermessage, "
+	       .        "m.informhoursahead, "
+	       .        "m.allowreservations "
+	       . "FROM sitemaintenance m, "
+	       .      "user u, "
+	       .      "affiliation a "
+	       . "WHERE m.ownerid = u.id AND "
+	       .       "u.affiliationid = a.id AND "
+	       .       "m.end > NOW() ";
+	if($id)
+		$query .= "AND m.id = $id ";
+	$query .= "ORDER BY m.start";
+	$qh = doQuery($query, 101);
+	$data = array();
+	while($row = mysql_fetch_assoc($qh))
+		$data[$row['id']] = $row;
+	$_SESSION['usersessiondata'][$key] = $data;
+	return $data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \fn getMaintItemsForTimeTable($start, $end)
+///
+/// \param $start - start time in unix timestamp format
+/// \param $end - end time in unix timestamp format
+///
+/// \return array of maintenance items that overlap with $start and $end where
+/// each item has 2 keys:\n
+/// \b start - start time in unix timestamp format\n
+/// \b end - end time in unix timestamp format
+///
+/// \brief builds a simple list of maintenance items and returns them
+///
+////////////////////////////////////////////////////////////////////////////////
+function getMaintItemsForTimeTable($start, $end) {
+	$key = getKey(array('getMaintItemsForTimeTable', $start, $end));
+	if(array_key_exists($key, $_SESSION['usersessiondata']))
+		return $_SESSION['usersessiondata'][$key];
+	$startdt = unixToDatetime($start);
+	$enddt = unixToDatetime($end);
+	$query = "SELECT UNIX_TIMESTAMP(start - INTERVAL 30 MINUTE) AS start, "
+	       .        "UNIX_TIMESTAMP(end) AS end "
+	       . "FROM sitemaintenance "
+	       . "WHERE end > '$startdt' AND "
+	       .       "start < '$enddt' "
+	       . "ORDER BY start";
+	$qh = doQuery($query, 101);
+	$data = array();
+	while($row = mysql_fetch_assoc($qh))
+		$data[] = $row;
+	$_SESSION['usersessiondata'][$key] = $data;
+	return $data;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn unset_by_val($needle, &$haystack)
 ///
 /// \param $needle - value to remove from array
@@ -8527,6 +8825,8 @@ function printHTMLHeader() {
 
 	if(! in_array($mode, $noHTMLwrappers)) {
 		print $HTMLheader;
+		if($mode != 'inmaintenance')
+			print maintenanceNotice();
 		$printedHTMLheader = 1;
 	}
 }
@@ -8624,6 +8924,11 @@ function getNavMenu($inclogout, $inchome
 		$rt .= "<a href=\"" . BASEURL . SCRIPT . "?mode=editVMInfo\">";
 		$rt .= "Virtual Hosts</a></li>\n";
 	}
+	if($viewmode == ADMIN_DEVELOPER) {
+		$rt .= menulistLI('sitemaintenance');
+		$rt .= "<a href=\"" . BASEURL . SCRIPT . "?mode=siteMaintenance\">";
+		$rt .= "Site Maintenance</a></li>\n";
+	}
 	$rt .= menulistLI('statistics');
 	$rt .= "<a href=\"" . BASEURL . SCRIPT . "?mode=selectstats\">";
 	$rt .= "Statistics</a></li>\n";
@@ -8673,6 +8978,7 @@ function getExtraCSS() {
 		case 'viewdocs':
 			return array('doxygen.css');
 	}
+	return array();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -8818,6 +9124,19 @@ function getDojoHTML($refresh) {
 			                      'dojo.data.ItemFileReadStore',
 			                      'dijit.Dialog');
 			break;
+		case 'siteMaintenance':
+			$dojoRequires = array('dojo.parser',
+			                      'dijit.form.Button',
+			                      'dijit.form.NumberSpinner',
+			                      'dijit.form.DateTextBox',
+			                      'dijit.form.TimeTextBox',
+			                      'dijit.form.TextBox',
+			                      'dijit.form.Select',
+			                      'dijit.form.Textarea',
+			                      'dojox.string.sprintf',
+			                      'dijit.Tooltip',
+			                      'dijit.Dialog');
+			break;
 	}
 	if(empty($dojoRequires))
 		return '';
@@ -9099,6 +9418,22 @@ function getDojoHTML($refresh) {
 			$rt .= "   });\n";
 			$rt .= "</script>\n";
 			return $rt;
+		case "siteMaintenance":
+			$rt .= "<style type=\"text/css\">\n";
+			$rt .= "   @import \"themes/$skin/css/dojo/$skin.css\";\n";
+			$rt .= "</style>\n";
+			$rt .= "<script type=\"text/javascript\" src=\"js/sitemaintenance.js\"></script>\n";
+			$rt .= "<script type=\"text/javascript\" src=\"dojo/dojo/dojo.js\"\n";
+			$rt .= "   djConfig=\"parseOnLoad: true\">\n";
+			$rt .= "</script>\n";
+			$rt .= "<script type=\"text/javascript\">\n";
+			$rt .= "   dojo.addOnLoad(function() {\n";
+			foreach($dojoRequires as $req) {
+				$rt .= "   dojo.require(\"$req\");\n";
+			}
+			$rt .= "   });\n";
+			$rt .= "</script>\n";
+			return $rt;
 
 		default:
 			$rt .= "<style type=\"text/css\">\n";

Modified: incubator/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php (original)
+++ incubator/vcl/trunk/web/.ht-inc/xmlrpcWrappers.php Mon Aug 30 19:39:30 2010
@@ -593,8 +593,16 @@ function XMLRPCextendRequest($requestid,
 	}
 	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
 	                  $startts, $newendts, '', $requestid);
+	// conflicts with scheduled maintenance
+	if($rc == -2) {
+		addChangeLogEntry($request["logid"], NULL, unixToDatetime($newendts),
+		                  $request['start'], NULL, NULL, 0);
+		return array('status' => 'error',
+		             'errorcode' => 46,
+		             'errormsg' => 'requested time is during a maintenance window');
+	}
 	// concurrent license overlap
-	if($rc == -1) {
+	elseif($rc == -1) {
 		addChangeLogEntry($request["logid"], NULL, unixToDatetime($newendts),
 		                  $request['start'], NULL, NULL, 0);
 		return array('status' => 'error',
@@ -716,8 +724,16 @@ function XMLRPCsetRequestEnding($request
 	}
 	$rc = isAvailable(getImages(), $request['reservations'][0]["imageid"],
 	                  $startts, $end, '', $requestid);
+	// conflicts with scheduled maintenance
+	if($rc == -2) {
+		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
+		                  $request['start'], NULL, NULL, 0);
+		return array('status' => 'error',
+		             'errorcode' => 46,
+		             'errormsg' => 'requested time is during a maintenance window');
+	}
 	// concurrent license overlap
-	if($rc == -1) {
+	elseif($rc == -1) {
 		addChangeLogEntry($request["logid"], NULL, unixToDatetime($end),
 		                  $request['start'], NULL, NULL, 0);
 		return array('status' => 'error',

Modified: incubator/vcl/trunk/web/css/vcl.css
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/css/vcl.css?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/css/vcl.css (original)
+++ incubator/vcl/trunk/web/css/vcl.css Mon Aug 30 19:39:30 2010
@@ -248,3 +248,11 @@
 	background-color: #678db2;
 	color: white;
 }
+
+#maintenancenotice {
+	width: 450px;
+	padding: 3px;
+	margin: 5px;
+	border: 1px solid;
+	background-color: #f3f3f3;
+}

Modified: incubator/vcl/trunk/web/index.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/index.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/index.php (original)
+++ incubator/vcl/trunk/web/index.php Mon Aug 30 19:39:30 2010
@@ -48,6 +48,8 @@ require_once('.ht-inc/errors.php');
 
 require_once('.ht-inc/utils.php');
 
+maintenanceCheck();
+
 dbConnect();
 
 initGlobals();

Added: incubator/vcl/trunk/web/js/sitemaintenance.js
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/js/sitemaintenance.js?rev=990918&view=auto
==============================================================================
--- incubator/vcl/trunk/web/js/sitemaintenance.js (added)
+++ incubator/vcl/trunk/web/js/sitemaintenance.js Mon Aug 30 19:39:30 2010
@@ -0,0 +1,180 @@
+function RPCwrapper(data, CB, dojson) {
+	if(dojson) {
+		dojo.xhrPost({
+			url: 'index.php',
+			load: CB,
+			handleAs: "json",
+			error: errorHandler,
+			content: data,
+			timeout: 15000
+		});
+	}
+	else {
+		dojo.xhrPost({
+			url: 'index.php',
+			load: CB,
+			error: errorHandler,
+			content: data,
+			timeout: 15000
+		});
+	}
+}
+
+function generalReqCB(data, ioArgs) {
+	eval(data);
+	document.body.style.cursor = 'default';
+}
+
+function showAddSiteMaintenance(cont) {
+	dijit.byId('editDialog').attr('title', 'Add Site Maintenance');
+	dojo.byId('editheader').innerHTML = 'Add Site Maintenance';
+	dojo.byId('edittext').innerHTML = 'Fill in the following items and click <b>Create Site Maintenance</b>';
+	dijit.byId('editsubmitbtn').attr('label', 'Create Site Maintenance');
+	dijit.byId('editsubmitbtn').attr('disable', 'false');
+	dijit.byId('hoursahead').attr('value', 168);
+	dojo.byId('submitcont').value = cont;
+	dijit.byId('editDialog').show();
+}
+
+function showEditSiteMaintenance(cont) {
+	document.body.style.cursor = 'wait';
+	RPCwrapper({continuation: cont}, showEditSiteMaintenanceCB, 1);
+}
+
+function showEditSiteMaintenanceCB(data, ioArgs) {
+	dijit.byId('editDialog').attr('title', 'Edit Site Maintenance Entry');
+	dojo.byId('editheader').innerHTML = 'Edit Site Maintenance Entry';
+	dojo.byId('edittext').innerHTML = 'Adjust in the following items and click <b>Save Changes</b>';
+	dijit.byId('editsubmitbtn').attr('label', 'Save Changes');
+	dijit.byId('editsubmitbtn').attr('disable', 'false');
+
+	var start = new Date(data.items.start);
+	var end = new Date(data.items.end);
+	dijit.byId('starttime').attr('value', start);
+	dijit.byId('startdate').attr('value', start);
+	dijit.byId('endtime').attr('value', end);
+	dijit.byId('enddate').attr('value', end);
+	dijit.byId('hoursahead').attr('value', data.items.hoursahead);
+	updateDaysHours(data.items.hoursahead);
+	dijit.byId('allowreservations').attr('value', data.items.allowreservations);
+	dijit.byId('reason').attr('value', data.items.reason);
+	dijit.byId('usermessage').attr('value', data.items.usermessage);
+	dojo.byId('submitcont').value = data.items.cont;
+
+	dijit.byId('editDialog').show();
+	document.body.style.cursor = 'default';
+}
+
+function updateDaysHours(val) {
+	var days = Math.floor(val / 24);
+	var hours = val % 24;
+	var text = days + " days, " + hours;
+	if(hours == 1)
+		text += " hour";
+	else
+		text += " hours";
+	dojo.byId('dayshours').innerHTML = text;
+	return val;
+}
+
+function editSubmit() {
+	if(! dijit.byId('starttime').isValid() || ! dijit.byId('startdate')) {
+		dijit.byId('starttime')._hasBeenBlurred = true;
+		dijit.byId('starttime').validate();
+		dijit.byId('startdate')._hasBeenBlurred = true;
+		dijit.byId('startdate').validate();
+		alert('Please specify a valid start time and date');
+		return;
+	}
+	if(! dijit.byId('endtime').isValid() || ! dijit.byId('enddate')) {
+		dijit.byId('endtime')._hasBeenBlurred = true;
+		dijit.byId('endtime').validate();
+		dijit.byId('enddate')._hasBeenBlurred = true;
+		dijit.byId('enddate').validate();
+		alert('Please specify a valid end time and date');
+		return;
+	}
+	if(! dijit.byId('hoursahead').isValid()) {
+		alert('Please specify a valid number of hours ahead');
+		return;
+	}
+	if(dijit.byId('usermessage').attr('value').length == 0) {
+		alert('Please fill in something for the User Message');
+		return;
+	}
+	var start = dijit.byId('startdate').value;
+	var tmp = dijit.byId('starttime').value;
+	start.setHours(tmp.getHours());
+	start.setMinutes(tmp.getMinutes());
+	start.setSeconds(tmp.getSeconds());
+	start.setMilliseconds(0);
+	var now = new Date();
+	if(start < now) {
+		alert('The start time and date must be later than the current time.');
+		return;
+	}
+	var end = dijit.byId('enddate').value;
+	var tmp = dijit.byId('endtime').value;
+	end.setHours(tmp.getHours());
+	end.setMinutes(tmp.getMinutes());
+	end.setSeconds(tmp.getSeconds());
+	end.setMilliseconds(0);
+	if(end <= start) {
+		alert('The end time and date must be later than the start time and date.');
+		return;
+	}
+	var numstart = dojox.string.sprintf('%04d%02d%02d%02d%02d', start.getFullYear(),
+	                                    start.getMonth() + 1, start.getDate(),
+	                                    start.getHours(), start.getMinutes());
+	var numend = dojox.string.sprintf('%04d%02d%02d%02d%02d', end.getFullYear(),
+	                                  end.getMonth() + 1, end.getDate(),
+	                                  end.getHours(), end.getMinutes());
+	var data = {continuation: dojo.byId('submitcont').value,
+	            start: numstart,
+	            end: numend,
+	            hoursahead: dijit.byId('hoursahead').attr('value'),
+	            allowreservations: dijit.byId('allowreservations').value,
+	            reason: dijit.byId('reason').attr('value'),
+	            usermessage: dijit.byId('usermessage').attr('value')};
+	document.body.style.cursor = 'wait';
+	dijit.byId('editsubmitbtn').attr('disable', 'true');
+	RPCwrapper(data, generalReqCB);
+}
+
+function clearEdit() {
+	dijit.byId('editDialog').hide();
+	dijit.byId('editDialog').attr('title', '');
+	dojo.byId('editheader').innerHTML = '';
+	dojo.byId('edittext').innerHTML = '';
+	dijit.byId('editsubmitbtn').attr('label', '');
+	dijit.byId('editsubmitbtn').attr('disable', 'false');
+	dijit.byId('hoursahead').attr('value', 168);
+	dojo.byId('dayshours').innerHTML = '';
+	dojo.byId('submitcont').value = '';
+}
+
+function confirmDeleteSiteMaintenance(cont) {
+	document.body.style.cursor = 'wait';
+	RPCwrapper({continuation: cont}, confirmDeleteSiteMaintenanceCB, 1);
+}
+
+function confirmDeleteSiteMaintenanceCB(data, ioArgs) {
+	dojo.byId('start').innerHTML = data.items.start;
+	dojo.byId('end').innerHTML = data.items.end;
+	dojo.byId('owner').innerHTML = data.items.owner;
+	dojo.byId('created').innerHTML = data.items.created;
+	dojo.byId('informhoursahead').innerHTML = data.items.hoursahead;
+	dojo.byId('delallowreservations').innerHTML = data.items.allowreservations;
+	dojo.byId('delreason').innerHTML = data.items.reason;
+	dojo.byId('delusermessage').innerHTML = data.items.usermessage;
+	dojo.byId('delsubmitcont').value = data.items.cont;
+
+	dijit.byId('confirmDialog').show();
+	document.body.style.cursor = 'default';
+}
+
+function deleteSiteMaintenance() {
+	var cont = dojo.byId('delsubmitcont').value;
+	document.body.style.cursor = 'wait';
+	RPCwrapper({continuation: cont}, generalReqCB);
+}

Modified: incubator/vcl/trunk/web/testsetup.php
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/web/testsetup.php?rev=990918&r1=990917&r2=990918&view=diff
==============================================================================
--- incubator/vcl/trunk/web/testsetup.php (original)
+++ incubator/vcl/trunk/web/testsetup.php Mon Aug 30 19:39:30 2010
@@ -230,6 +230,39 @@ if($includeconf && include('.ht-inc/conf
 			fail("SCRIPT does not appear to be set correctly");
 	}
 	print "</ul>\n";
+
+	# check for existance of maintenance directory
+	title("Checking that .ht-inc/maintenance directory exists");
+	print "<ul>\n";
+	$file = preg_replace('|/testsetup.php|', '', $_SERVER['SCRIPT_FILENAME']);
+	$file .= "/.ht-inc/maintenance";
+	if(! is_dir($file))
+		fail("/.ht-inc/maintenance directory does not exist. Please create it.");
+	else {
+		pass("/.ht-inc/maintenance directory exists");
+		print "</ul>\n";
+		# check that we can write files to maintenance directory
+		title("Checking that .ht-inc/maintenance directory is writable");
+		print "<ul>\n";
+		if(! is_writable("$file"))
+			fail("Maintenance directory is not writable");
+		else {
+			if(! $fh = @fopen("$file/testfile", 'w'))
+				fail("Failed to open file in maintenance directory");
+			else {
+				if(! fwrite($fh, 'test') || ! fclose($fh))
+					fail("Failed to write to file in maintenance directory");
+				else {
+					# check that we can remove files from maintenance directory
+					if(! unlink("$file/testfile"))
+						fail("Failed to remove file from maintenance directory");
+					else
+						pass("Maintenance directory is writable");
+				}
+			}
+		}
+	}
+	print "</ul>\n";
 }
 
 # required extentions