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 2016/02/26 21:06:42 UTC

svn commit: r1732551 - in /vcl/trunk/web: .ht-inc/siteconfig.php .ht-inc/utils.php css/vcl.css js/code.js js/siteconfig.js

Author: jfthomps
Date: Fri Feb 26 20:06:42 2016
New Revision: 1732551

URL: http://svn.apache.org/viewvc?rev=1732551&view=rev
Log:
VCL-915 - Add ability for Linux images to mount NFS shares
VCL-919 - Allow customization of notification messages sent to users

siteconfig.php:
-modified generalOptions: added call to get html for NFSmounts section; added call to get html for messages section
-added GlobalMultiVariable class
-added NFSmounts class that inherits from GlobalMultiVariable class
-added Messages class

siteconfig.js:
-added GlobalMultiVariable class
-added nfsmount class that inherits from GlobalMultiVariable class
-instantiated nfs class
-added messages class
-instantiated messages class

utils.php:
-modified labeledFormItem: modified styling for label for textarea elements to vertical align the text to the top
-modified getDojoHTML: added dijit.Dialog and dojox.layout.FloatingPane to siteconfig mode; added import for FloatingPane.css file for stieconfig mode

code.js: modified checkValidateObj: added check for isValid method existing in dijit object for passed in id before calling isValid on it

vcl.css: added .highlightnotice

Modified:
    vcl/trunk/web/.ht-inc/siteconfig.php
    vcl/trunk/web/.ht-inc/utils.php
    vcl/trunk/web/css/vcl.css
    vcl/trunk/web/js/code.js
    vcl/trunk/web/js/siteconfig.js

Modified: vcl/trunk/web/.ht-inc/siteconfig.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/siteconfig.php?rev=1732551&r1=1732550&r2=1732551&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/siteconfig.php (original)
+++ vcl/trunk/web/.ht-inc/siteconfig.php Fri Feb 26 20:06:42 2016
@@ -60,6 +60,12 @@ function generalOptions($globalopts) {
 
 	# -------- full width -----------
 	$h .= timeSourceHTML($globalopts);
+	if($globalopts) {
+		$obj = new NFSmounts();
+		$h .= $obj->getHTML();
+	}
+	$obj = new Messages($globalopts);
+	$h .= $obj->getHTML();
 	# ------ end full width ---------
 
 	$h .= "<table summary=\"\" id=siteconfig>\n";
@@ -1080,4 +1086,720 @@ class NATportRange extends GlobalSingleV
 	}
 }
 
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \class GlobalMultiVariable
+///
+/// \brief base class for global single value variables
+///
+////////////////////////////////////////////////////////////////////////////////
+class GlobalMultiVariable {
+	var $name;
+	var $units; #array('id' => array('name' => 'razr-mgt', ...))
+	var $values; #array('id' => '<IP>:/foo/bar,/mydata')
+	var $desc;
+	var $domidbase;
+	var $basecdata;
+	var $jsname;
+	var $defaultval;
+	var $updatemsg;
+	var $type;
+	var $width;
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn __construct()
+	///
+	/// \brief class construstor
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function __construct() {
+		$this->basecdata = array('obj' => $this);
+		$this->updatemsg = _("Values updated");
+		$type = 'text';
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn getHTML()
+	///
+	/// \return string of HTML
+	///
+	/// \brief generates HTML for setting variables
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function getHTML() {
+		global $user;
+		$h  = "<div class=\"configwidget\" style=\"width: 100%;\">\n";
+		$h .= "<h3>{$this->name}</h3>\n";
+		$h .= "<span class=\"siteconfigdesc\">\n";
+		$h .= $this->desc;
+		$h .= "<br><br></span>\n";
+		$this->savekeys = array();
+		$this->setkeys = array();
+
+		$h .= $this->existingValuesHTML();
+
+		$unitskeys = array_keys($this->units);
+		$remainingkeys = array_diff($unitskeys, $this->setkeys);
+		$remaining = array();
+		foreach($remainingkeys as $key) {
+			$remaining[$key] = $this->units[$key]['name'];
+		}
+		if(count($remaining) == 0)
+			$h .= "<span id=\"{$this->domidbase}multivalnewspan\" class=\"hidden\">\n";
+		else
+			$h .= "<span id=\"{$this->domidbase}multivalnewspan\">\n";
+
+		$h .= $this->newValueHTML($remaining);
+
+		$h .= "</span>\n"; # multivalnewspan
+
+		$h .= "<div id=\"{$this->domidbase}msg\"></div>\n";
+		$h .= dijitButton("{$this->domidbase}btn", _('Submit Changes'), "{$this->jsname}.saveSettings();", 1);
+		$cdata = $this->basecdata;
+		$cont = addContinuationsEntry('AJupdateAllSettings', $cdata);
+		$h .= "<input type=\"hidden\" id=\"{$this->domidbase}cont\" value=\"$cont\">\n";
+		$this->savekeys = implode(',', $this->savekeys);
+		$h .= "<input type=\"hidden\" id=\"{$this->domidbase}savekeys\" value=\"{$this->savekeys}\">\n";
+		$cont = addContinuationsEntry('AJdeleteMultiSetting', $cdata);
+		$h .= "<input type=\"hidden\" id=\"delete{$this->domidbase}cont\" value=\"$cont\">\n";
+		$h .= "</div>\n";
+		return $h;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn existingValuesHTML()
+	///
+	/// \return string of HTML
+	///
+	/// \brief generates HTML for existing value forms
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function existingValuesHTML() {
+		$h = "<span id=\"{$this->domidbase}multivalspan\">\n";
+		foreach($this->values as $key => $val) {
+			$this->savekeys[] = "{$this->domidbase}|$key";
+			$this->setkeys[] = $key;
+			$h .= "<span id=\"{$this->domidbase}|{$key}wrapspan\">\n";
+			switch($this->type) {
+				/*case 'numeric':
+					$extra = array('smallDelta' => 1, 'largeDelta' => 10);
+					$h .= labeledFormItem($this->domidbase, $this->label, 'spinner', "{min:{$this->minval}, max:{$this->maxval}}", 1, $val, '', '', $extra);
+					break;
+				case 'boolean':
+					$extra = array();
+					if($val == 1)
+						$extra = array('checked' => 'checked');
+					$h .= labeledFormItem($this->domidbase, $this->label, 'check', '', 1, 1, '', '', $extra);
+					break;*/
+				case 'textarea':
+					$h .= labeledFormItem("{$this->domidbase}|$key", $this->units[$key]['name'], 'textarea', '', 1, $val, '', '', '', $this->width, '', 0);
+					break;
+				default:
+					$h .= labeledFormItem("{$this->domidbase}|$key", $this->units[$key]['name'], 'text', "{$this->constraint}", 1, $val, "{$this->invalidmsg}", '', '', $this->width, '', 0);
+					break;
+			}
+			$h .= dijitButton("{$this->domidbase}|{$key}delbtn", _('Delete'), "{$this->jsname}.deleteMultiVal('$key', '{$this->domidbase}');") . "<br></span>\n";
+		}
+		$h .= "</span>\n"; # multivalspan
+		return $h;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn newValueHTML($remaining)
+	///
+	/// \param $remaining - array of remaining units for which values can be added
+	///
+	/// \return string of HTML
+	///
+	/// \brief generates HTML for new value form
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function newValueHTML($remaining) {
+		$h = selectInputHTML('', $remaining, "{$this->domidbase}newmultivalid", "dojoType=\"dijit.form.Select\"");
+
+		switch($this->type) {
+			case 'textarea':
+				$h .= "<textarea ";
+				$h .=   "dojoType=\"dijit.form.Textarea\" ";
+				$h .=   "style=\"width: {$this->width}; text-align: left;\" ";
+				$h .=   "id=\"{$this->domidbase}newmultival\">";
+				$h .= "</textarea>\n";
+				break;
+			default:
+				$h .= "<input type=\"text\" ";
+				$h .=   "dojoType=\"dijit.form.ValidationTextBox\" ";
+				$h .=   "regExp=\"{$this->constraint}\" ";
+				$h .=   "invalidMessage=\"{$this->invalidmsg}\" ";
+				$h .=   "style=\"width: {$this->width}\" ";
+				$h .=   "id=\"{$this->domidbase}newmultival\">";
+				break;
+		}
+		$h .= dijitButton("{$this->domidbase}addbtn", _('Add'), "{$this->jsname}.addNewMultiVal();");
+		$cont = addContinuationsEntry('AJaddConfigMultiVal', $this->basecdata);
+		$h .= "<input type=\"hidden\" id=\"{$this->domidbase}addcont\" value=\"$cont\">\n";
+		return $h;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJaddConfigMultiVal()
+	///
+	/// \brief adds a multi value setting
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJaddConfigMultiVal() {
+		$newkey = processInputVar('multivalid', ARG_NUMERIC);
+		$newval = processInputVar('multival', ARG_STRING);
+		if(! array_key_exists($newkey, $this->units)) {
+			$arr = array('status' => 'failed',
+			             'msgid' => "{$this->domidbase}msg",
+			             'btn' => "{$this->domidbase}addbtn",
+			             'errmsg' => _("Invalid item selected for new value"));
+			sendJSON($arr);
+			return;
+		}
+		if(! $this->validateValue($newval)) {
+			$arr = array('status' => 'failed',
+			             'msgid' => "{$this->domidbase}msg",
+			             'btn' => "{$this->domidbase}addbtn",
+			             'errmsg' => _("Invalid value submitted"));
+			if(isset($this->invalidvaluemsg))
+				$arr['errmsg'] = $this->invalidvaluemsg;
+			sendJSON($arr);
+			return;
+		}
+		setVariable("{$this->domidbase}|$newkey", $newval, 'none');
+		$arr = array('status' => 'success',
+		             'msgid' => "{$this->domidbase}msg",
+		             'addid' => "{$this->domidbase}|$newkey",
+		             'addname' => $this->units[$newkey]['name'],
+		             'addval' => $newval,
+		             'delkey' => $newkey,
+		             'extrafunc' => "{$this->jsname}.addNewMultiValCBextra",
+		             'msg' => $this->addmsg,
+		             'regexp' => $this->constraint,
+		             'invalidmsg' => str_replace('&amp;', '&', $this->invalidmsg));
+		sendJSON($arr);
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJdeleteMultiSetting()
+	///
+	/// \brief deletes a multi value setting
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJdeleteMultiSetting() {
+		$key = processInputVar('key', ARG_NUMERIC);
+		if(! array_key_exists($key, $this->values)) {
+			$arr = array('status' => 'failed',
+			             'msgid' => "{$this->domidbase}msg",
+			             'btn' => "{$this->domidbase}|{$key}delbtn",
+			             'errmsg' => _("Invalid item submitted for deletion"));
+			sendJSON($arr);
+			return;
+		}
+		deleteVariable("{$this->domidbase}|$key");
+		$arr = array('status' => 'success',
+		             'msgid' => "{$this->domidbase}msg",
+		             'delid' => "{$this->domidbase}|$key",
+		             'extrafunc' => "{$this->jsname}.deleteMultiValCBextra",
+		             'addid' => "$key",
+		             'addname' => $this->units[$key]['name'],
+		             'msg' => $this->delmsg);
+		sendJSON($arr);
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJupdateAllSettings()
+	///
+	/// \brief updates all values for implemented type of timevariable
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJupdateAllSettings() {
+		if(! checkUserHasPerm('Site Configuration (global)')) {
+			$arr = array('status' => 'noaccess',
+			             'msg' => _('You do not have access to modify the submitted settings.'));
+			sendJSON($arr);
+			return;
+		}
+		$origvals = $this->values;
+		$newvals = array();
+		foreach($origvals as $key => $val) {
+			switch($this->type) {
+				/*case 'numeric':
+					$newval = processInputVar('newval', ARG_NUMERIC); 
+					if($newval < $this->minval || $newval > $this->maxval) {
+						$arr = array('status' => 'failed',
+						             'msgid' => "{$this->domidbase}msg",
+						             'btn' => "{$this->domidbase}btn",
+						             'errmsg' => _("Invalid value submitted"));
+						sendJSON($arr);
+						return;
+					}
+					break;
+				case 'boolean':
+					$newval = processInputVar('newval', ARG_NUMERIC); 
+					if($newval !== '0' && $newval !== '1') {
+						$arr = array('status' => 'failed',
+						             'msgid' => "{$this->domidbase}msg",
+						             'btn' => "{$this->domidbase}btn",
+						             'errmsg' => _("Invalid value submitted"));
+						sendJSON($arr);
+						return;
+					}
+					break;*/
+				case 'text':
+				case 'textarea':
+					$newval = processInputVar("{$this->domidbase}|$key", ARG_STRING); 
+					if(! $this->validateValue($newval)) {
+						$arr = array('status' => 'failed',
+						             'msgid' => "{$this->domidbase}msg",
+						             'btn' => "{$this->domidbase}btn",
+						             'errmsg' => _("Invalid value submitted for ") . $this->units[$key]['name']);
+						sendJSON($arr);
+						return;
+					}
+					if($newval != $origvals[$key])
+						$newvals["{$this->jsname}|$key"] = $newval;
+					break;
+				/*case 'textarea':
+					$newval = processInputVar('newval', ARG_STRING); 
+					if(! $this->validateValue($newval)) {
+						$arr = array('status' => 'failed',
+						             'msgid' => "{$this->domidbase}msg",
+						             'btn' => "{$this->domidbase}btn",
+						             'errmsg' => _("Invalid value submitted"));
+						if(isset($this->invalidvaluemsg))
+							$arr['errmsg'] = $this->invalidvaluemsg;
+						sendJSON($arr);
+						return;
+					}
+					break;*/
+				default:
+					$arr = array('status' => 'failed',
+					             'msgid' => "{$this->domidbase}msg",
+					             'btn' => "{$this->domidbase}btn",
+					             'errmsg' => _("Invalid value submitted"));
+					sendJSON($arr);
+					return;
+			}
+		}
+		foreach($newvals as $key => $val)
+			setVariable($key, $val, 'none');
+		$arr = array('status' => 'success',
+		             'msgid' => "{$this->domidbase}msg",
+		             'btn' => "{$this->domidbase}btn",
+		             'msg' => $this->updatemsg);
+		sendJSON($arr);
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn validateValue($val)
+	///
+	/// \brief validates that a new value is okay; should be implemented in 
+	/// inheriting class
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function validateValue($val) {
+		return 1;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \class NFSmounts
+///
+/// \brief extends GlobalMultiVariable class to implement NFSmounts
+///
+////////////////////////////////////////////////////////////////////////////////
+class NFSmounts extends GlobalMultiVariable {
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn __construct()
+	///
+	/// \brief class construstor
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function __construct() {
+		parent::__construct();
+		$this->name = _('NFS Mounts');
+		$this->units = getManagementNodes();
+		foreach($this->units as $key => $val) {
+			$this->units[$key]['name'] = $val['hostname'];
+		}
+		$vals = getVariablesRegex('^nfsmount\\\|[0-9]+$');
+		$this->values = array();
+		foreach($vals as $key => $val) {
+			$tmp = explode('|', $key);
+			$id = $tmp[1];
+			$this->values[$id] = $val;
+		}
+		$formbase = ' &lt;hostname or IP&gt;:&lt;export path&gt;,&lt;mount path&gt;';
+		$this->desc = _("NFS Mounts are NFS exports that are to be mounted within each reservation deployed by a given management node.<br>Values must be like") . $formbase;
+		$this->domidbase = 'nfsmount';
+		$this->basecdata['obj'] = $this;
+		$this->jsname = 'nfsmount';
+		$this->defaultval = '';
+		$this->type = 'textarea';
+		$this->addmsg = "NFS mount sucessfully added";
+		$this->delmsg = "NFS mount sucessfully deleted";
+		$this->constraint = '((((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|([A-Za-z0-9\-\.]+)):\/[a-zA-Z0-9\.@#%\(\)-_=\+\/]+,\/[a-zA-Z0-9\.@#%\(\)-_=\+\/]+';
+		$this->invalidmsg = _("Invalid value - must be in the form") . str_replace('&', '&amp;', $formbase);
+		$this->invalidvaluemsg = html_entity_decode($this->invalidmsg);
+		$this->width = '400px';
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn validateValue($val)
+	///
+	/// \brief validates that a new value is okay
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function validateValue($val) {
+		$vals = explode(";", $val);
+		foreach($vals as $testval) {
+			$tmp = explode(':', $testval);
+			if(count($tmp) != 2)
+				return 0;
+			$iphost = $tmp[0];
+			if(preg_match('/^[0-9\.]+$/', $iphost) && ! validateIPv4addr($iphost))
+				return 0;
+			elseif(! preg_match('/^[A-Za-z0-9\-\.]+$/', $iphost))
+				return 0;
+			$tmp = explode(',', $tmp[1]);
+			if(count($tmp) != 2)
+				return 0;
+			$exportpath = $tmp[0];
+			$mntpath = $tmp[1];
+			if(! preg_match(':^/[a-zA-Z0-9\.@#%\(\)-_=\+/]+:', $exportpath))
+				return 0;
+			if(! preg_match(':^/[a-zA-Z0-9\.@#%\(\)-_=\+/]+:', $mntpath))
+				return 0;
+		}
+		return 1;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \class Messages
+///
+/// \brief class to handle configuration of Messages
+///
+////////////////////////////////////////////////////////////////////////////////
+class Messages {
+	var $basecdata;
+	var $name;
+	var $desc;
+	var $affils;
+	var $units;
+	var $basekeys;
+	var $globalopts;
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn __construct()
+	///
+	/// \brief class construstor
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function __construct($globalopts) {
+		$this->basecdata['obj'] = $this;
+		$this->name = _('Messages');
+		$this->desc = sprintf(_("This section allows for configuration of messages that are sent to users and administrators about things such as reservations and image management. Every message has a default. Additionally, separate messages can be configured for each affiliation. Most of the messages will have parts that are in square brackets. These parts will have data substituted for them before the message is sent. A list of what can be used in squeare brackets can be found at the <a href=\"%s\">Apache VCL web site</a>. Some messages also have a short form that may be sent such as in the form of a popup within a reservation when the reservation is about to end."), "http://vcl.apache.org/docs/message_substitutions.html");
+		$this->affils = getAffiliations();
+		$this->units = array();
+		$this->basekeys = array();
+		$this->globalopts = $globalopts;
+
+		if($this->globalopts)
+			$data = getVariablesRegex('^(usermessage\\\||adminmessage\\\|)');
+		else
+			$data = getVariablesRegex('^usermessage\\\|');
+		foreach($data as $key => $item) {
+			# 0 - category, 1 - type, 2 - affil
+			$keyparts = explode('|', $key);
+			$k = "{$keyparts[0]}|{$keyparts[1]}";
+			$kname = "{$keyparts[0]} -&gt; {$keyparts[1]}";
+			$this->basekeys[$k] = $kname;
+
+			if($keyparts[0] == 'adminmessage')
+				$keyparts[2] = 'Global';
+			$this->units[$k][$keyparts[2]] = $item;
+		}
+		uasort($this->basekeys, "sortKeepIndex");
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn getHTML()
+	///
+	/// \return string of HTML
+	///
+	/// \brief generates HTML for setting variables
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function getHTML() {
+		global $user;
+		$h  = "<div class=\"configwidget\" style=\"width: 100%;\">\n";
+		$h .= "<h3>{$this->name}</h3>\n";
+		$h .= "<div style=\"text-align: left; padding: 4px;\">\n";
+		$h .= $this->desc;
+		$h .= "<br><br></div>\n";
+
+		$h .= "<script type=\"application/json\" id=\"messagesdata\">\n";
+		$h .= json_encode($this->units); 
+		$h .= "</script>\n";
+
+		$h .= "<strong>Select Message</strong>:";
+		$extra = "dojoType=\"dijit.form.FilteringSelect\" "
+		       . "style=\"width: 300px\" "
+		       . "onChange=\"messages.setContents(1);\"";
+		$h .= selectInputHTML('', $this->basekeys, "messagesselid", $extra);
+		$h .= "<br>\n";
+		if($this->globalopts) {
+			$h .= "<strong>Select Affiliation</strong>:";
+			$h .= selectInputHTML('', $this->affils, "messagesaffilid", $extra);
+		}
+		else {
+			$opts = array($user['affiliationid'] => $this->affils[$user['affiliationid']]);
+			$h .= "<strong>Affiliation: {$this->affils[$user['affiliationid']]}</strong>";
+			$h .= "<div class=\"hidden\">\n";
+			$h .= selectInputHTML('', $opts, "messagesaffilid", $extra);
+			$h .= "</div>\n";
+		}
+
+		$h .= "<br>\n";
+		$h .= "<div id=\"defaultmessagesdiv\" class=\"hidden highlightnotice\"><br><strong>";
+		$h .= i('There is no message set specifically for this affiliation. The default message is being used and is displayed below.');
+		$h .= "</strong><br><br></div>\n";
+		$h .= labeledFormItem("messagessubject", 'Subject', 'text', '', 1, '', '', '', '', '80ch');
+		$h .= labeledFormItem("messagesbody", 'Message', 'textarea', '', 1, '', '', '', '', '80ch');
+		$h .= labeledFormItem("messagesshortmsg", 'Short Message', 'textarea', '', 1, '', '', '', '', '80ch');
+
+		$h .= "<span id=\"messagesaffil\" style=\"font-weight: bold;\"";
+		if(! $this->globalopts)
+			$h .= " class=\"hidden\"";
+		$h .= ">Default message for any affiliation</span><br>\n";
+
+		$h .= dijitButton("messagessavebtn", _('Save Message'), "messages.savemsg();");
+		$h .= dijitButton("messagesdelbtn", _('Delete Message and Use Default'), "messages.confirmdeletemsg();");
+
+		$h .= "<div dojoType=dijit.Dialog\n";
+		$h .= "      id=\"deleteMessageDlg\"\n";
+		$h .= "      title=\"" . i("Delete Message") . "\"\n";
+		$h .= "      duration=250\n";
+		$h .= "      draggable=true\n";
+		$h .= "      style=\"width: 315px;\">\n";
+		$h .= "Are you sure you want to delete the selected message for this affiliaton ";
+		$h .= "and use the default message instead?<br><br>\n";
+		$h .= "<span style=\"font-weight: bold\">Affiliation:</span> <span id=\"deleteMsgAffil\"></span><br>\n";
+		$h .= "<span style=\"font-weight: bold\">Category:</span> <span id=\"deleteMsgCategory\"></span><br>\n";
+		$h .= "<span style=\"font-weight: bold\">Type:</span> <span id=\"deleteMsgType\"></span><br><br>\n";
+		$h .= "   <div align=\"center\">\n";
+		$h .= "   <button id=\"deleteMessageDelBtn\" dojoType=\"dijit.form.Button\">\n";
+		$h .= "    " . i("Delete Message") . "\n";
+		$h .= "     <script type=\"dojo/method\" event=\"onClick\">\n";
+		$h .= "       messages.deletemsg();\n";
+		$h .= "     </script>\n";
+		$h .= "   </button>\n";
+		$h .= "   <button dojoType=\"dijit.form.Button\">\n";
+		$h .= "     " . i("Cancel") . "\n";
+		$h .= "     <script type=\"dojo/method\" event=\"onClick\">\n";
+		$h .= "       dijit.byId('deleteMessageDlg').hide();\n";
+		$h .= "     </script>\n";
+		$h .= "   </button>\n";
+		$h .= "   </div>\n";
+		$h .= "</div>\n";
+
+		$h .= "<div dojoType=dojox.layout.FloatingPane\n";
+		$h .= "      id=\"invalidmsgfieldspane\"\n";
+		$h .= "      resizable=\"true\"\n";
+		$h .= "      closable=\"true\"\n";
+		$h .= "      title=\"" . i("Invalid Message Fields") . "\"\n";
+		$h .= "      style=\"width: 400px; ";
+		$h .=               "height: 200px; ";
+		$h .=               "visibility: hidden; ";
+		$h .=               "text-align: left; ";
+		$h .=               "border: solid 2px red;\"\n";
+		$h .= ">\n";
+		$h .= "<script type=\"dojo/method\" event=\"minimize\">\n";
+		$h .= "  this.hide();\n";
+		$h .= "</script>\n";
+		$h .= "<script type=\"dojo/method\" event=\"close\">\n";
+		$h .= "  this.hide();\n";
+		$h .= "  return false;\n";
+		$h .= "</script>\n";
+		$h .= "<div style=\"padding: 4px;\">\n";
+		$h .= _("The following messages have invalid items included for substitution. Please correct the message contents and save them again for the backend to validate.") . "<br><br>\n";
+		$h .= "   <div id=\"invalidmsgfieldcontent\"></div>\n";
+		$h .= "</div>\n";
+		$h .= "</div>\n";
+
+		$cdata = $this->basecdata;
+		$h .= "<div id=\"messagesmsg\"></div>\n";
+		$cont = addContinuationsEntry('AJsaveMessages', $cdata);
+		$h .= "<input type=\"hidden\" id=\"savemessagescont\" value=\"$cont\">\n";
+		$cont = addContinuationsEntry('AJdeleteMessages', $cdata);
+		$h .= "<input type=\"hidden\" id=\"deletemessagescont\" value=\"$cont\">\n";
+
+		$cont = addContinuationsEntry('AJvalidateMessagesPoll', $cdata);
+		$h .= "<input type=\"hidden\" id=\"validatemessagespollcont\" value=\"$cont\">\n";
+
+		$h .= "</div>\n";
+
+		return $h;
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJdeleteMessages()
+	///
+	/// \brief deletes an affiliation specific message
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJdeleteMessages() {
+		global $user;
+		$key = processInputVar('key', ARG_STRING);
+		$affilid = processInputVar('affilid', ARG_NUMERIC);
+		$keyparts = explode('|', $key);
+		if(! array_key_exists($key, $this->basekeys) ||
+		   ! array_key_exists($affilid, $this->affils) ||
+		   count($keyparts) != 2 ||
+		   $this->affils[$affilid] == 'Global' ||
+		   ($this->globalopts == 0 && $affilid != $user['affiliationid'])) {
+			$arr = array('status' => 'failed',
+			             'msgid' => "messagesmsg",
+			             'btn' => "messagesdelbtn",
+			             'errmsg' => _("Invalid item submitted for deletion"));
+			sendJSON($arr);
+			return;
+		}
+		$affil = $this->affils[$affilid];
+		$delkey = "$key|$affil";
+		deleteVariable($delkey);
+		$arr = array('status' => 'success',
+		             'msgid' => "messagesmsg",
+		             'key' => $key,
+		             'affil' => $affil,
+		             'extrafunc' => "messages.deleteMessagesCBextra",
+		             'btn' => "messagesdelbtn",
+		             'msg' => sprintf(_('Message type %s for affiliation %s successfully deleted'), $keyparts[1], $affil));
+		sendJSON($arr);
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJsaveMessages()
+	///
+	/// \brief saves an affiliation specific message
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJsaveMessages() {
+		global $user;
+		$key = processInputVar('key', ARG_STRING);
+		$affilid = processInputVar('affilid', ARG_NUMERIC);
+		$subject = processInputVar('subject', ARG_STRING);
+		$body = processInputVar('body', ARG_STRING);
+		$shortmsg = processInputVar('shortmsg', ARG_STRING);
+
+		$keyparts = explode('|', $key);
+
+		if(! array_key_exists($key, $this->basekeys) ||
+		   ! array_key_exists($affilid, $this->affils) ||
+			count($keyparts) != 2 ||
+		   ($this->globalopts == 0 && $keyparts[0] == 'adminmessage') ||
+		   ($this->globalopts == 0 && $affilid != $user['affiliationid'])) {
+			$arr = array('status' => 'failed',
+			             'msgid' => "messagesmsg",
+			             'btn' => "messagessavebtn",
+			             'errmsg' => _("Invalid item submitted to save"));
+			sendJSON($arr);
+			return;
+		}
+		$affil = $this->affils[$affilid];
+		$savekey = $key;
+		if($keyparts[0] == 'usermessage')
+			$savekey = "{$keyparts[0]}|{$keyparts[1]}|$affil";
+		$data = getVariable($savekey);
+		if(is_null($data))
+			$data = array();
+		$changed = 0;
+		if(! array_key_exists('subject', $data) || $data['subject'] != $subject) {
+			$data['subject'] = $subject;
+			$changed = 1;
+		}
+		if(! array_key_exists('message', $data) || $data['message'] != $body) {
+			$data['message'] = $body;
+			$changed = 1;
+		}
+		if($keyparts[0] == 'usermessage' &&
+			(! array_key_exists('usermessage', $data) ||
+			$data['short_message'] != $shortmsg)) {
+			$data['short_message'] = $shortmsg;
+			$changed = 1;
+		}
+		if($changed) {
+			if(preg_match('/\[.*\]/', $body) ||
+			   preg_match('/\[.*\]/', $shortmsg))
+				setVariable('usermessage_needs_validating', 1, 'none');
+			unset($data['invalidfields']);
+			setVariable($savekey, $data, 'yaml');
+			$usermsg = _('Message successfully saved');
+		}
+		else
+			$usermsg = _('No changes to submitted message. Nothing saved.');
+		$arr = array('status' => 'success',
+		             'msgid' => "messagesmsg",
+		             'key' => $key,
+		             'affil' => $affil,
+		             'subject' => $subject,
+		             'body' => $body,
+		             'shortmsg' => $shortmsg,
+		             'extrafunc' => "messages.saveMessagesCBextra",
+		             'btn' => "messagessavebtn",
+		             'msg' => $usermsg);
+		sendJSON($arr);
+	}
+
+	////////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJvalidateMessagesPoll()
+	///
+	/// \brief checks for errors found by vcld in any recently updated messages
+	///
+	////////////////////////////////////////////////////////////////////////////////
+	function AJvalidateMessagesPoll() {
+		$query = "SELECT v1.name, "
+		       .        "v1.value "
+		       . "FROM variable v1, "
+		       .      "variable v2 "
+		       . "WHERE v1.setby != 'webcode' AND "
+		       .       "v1.setby IS NOT NULL AND "
+		       .       "v2.name = 'usermessage_needs_validating' AND "
+		       .       "v1.timestamp > DATE_SUB(v2.timestamp, INTERVAL 5 MINUTE)";
+		$qh = doQuery($query);
+		$invalids = array();
+		while($row = mysql_fetch_assoc($qh)) {
+			$data = Spyc::YAMLLoad($row['value']);
+			if(array_key_exists('invalidfields', $data)) {
+				$invalids[$row['name']] = $data['invalidfields'];
+			}
+		}
+		if(count($invalids)) {
+			sendJSON(array('status' => 'invalid', 'values' => $invalids));
+			return;
+		}
+		sendJSON(array('status' => 'valid'));
+	}
+}
+
 ?>

Modified: vcl/trunk/web/.ht-inc/utils.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/utils.php?rev=1732551&r1=1732550&r2=1732551&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/utils.php (original)
+++ vcl/trunk/web/.ht-inc/utils.php Fri Feb 26 20:06:42 2016
@@ -9110,7 +9110,7 @@ function labeledFormItem($id, $label, $t
 		case 'textarea':
 			if($width == '')
 				$width = '300px';
-			$h .= "<label for=\"$id\">$label:</label>\n";
+			$h .= "<label style=\"vertical-align: top;\" for=\"$id\">$label:</label>\n";
 			$h .= "<span class=\"labeledform\">\n";
 			$h .= "<textarea ";
 			$h .=        "dojoType=\"dijit.form.Textarea\" ";
@@ -12962,6 +12962,7 @@ function getDojoHTML($refresh) {
 		case 'siteconfig':
 			$filename = 'siteconfig.js';
 			$dojoRequires = array('dojo.parser',
+			                      'dijit.Dialog',
 			                      'dijit.form.Button',
 			                      'dijit.form.Textarea',
 			                      'dijit.form.FilteringSelect',
@@ -12970,6 +12971,7 @@ function getDojoHTML($refresh) {
 			                      'dijit.form.CheckBox',
 			                      'dijit.form.ValidationTextBox',
 			                      'dijit.layout.ContentPane',
+			                      'dojox.layout.FloatingPane',
 			                      'dijit.layout.TabContainer');
 			break;
 		# TODO clean up
@@ -13366,6 +13368,7 @@ function getDojoHTML($refresh) {
 		case "siteconfig":
 			$rt .= "<style type=\"text/css\">\n";
 			$rt .= "   @import \"themes/$skin/css/dojo/$skin.css\";\n";
+			$rt .= "   @import \"dojo/dojox/layout/resources/FloatingPane.css\";\n";
 			$rt .= "   @import \"css/siteconfig.css\";\n";
 			$rt .= "</style>\n";
 			$rt .= "<script type=\"text/javascript\" src=\"js/siteconfig.js?v=$v\"></script>\n";

Modified: vcl/trunk/web/css/vcl.css
URL: http://svn.apache.org/viewvc/vcl/trunk/web/css/vcl.css?rev=1732551&r1=1732550&r2=1732551&view=diff
==============================================================================
--- vcl/trunk/web/css/vcl.css (original)
+++ vcl/trunk/web/css/vcl.css Fri Feb 26 20:06:42 2016
@@ -305,6 +305,13 @@ body {
 	text-align: left;
 }
 
+.highlightnotice {
+	background-color: #fffa9a;
+	border: 2px solid #eaea00;
+	padding: 3px;
+	margin: 10px;
+}
+
 .highlightnoticewarn {
 	background-color: #ffcccc;
 	border: 2px solid #ff0000;

Modified: vcl/trunk/web/js/code.js
URL: http://svn.apache.org/viewvc/vcl/trunk/web/js/code.js?rev=1732551&r1=1732550&r2=1732551&view=diff
==============================================================================
--- vcl/trunk/web/js/code.js (original)
+++ vcl/trunk/web/js/code.js Fri Feb 26 20:06:42 2016
@@ -346,7 +346,7 @@ function resizeRecenterDijitDialog(id) {
 
 function checkValidatedObj(objid, errobj) {
 	if(dijit.byId(objid) && ! dijit.byId(objid).get('disabled') &&
-	   ! dijit.byId(objid).isValid()) {
+	   'isValid' in dijit.byId(objid) && ! dijit.byId(objid).isValid()) {
 		dijit.byId(objid)._hasBeenBlurred = true;
 		dijit.byId(objid).validate();
 		//dijit.byId(objid).focus();

Modified: vcl/trunk/web/js/siteconfig.js
URL: http://svn.apache.org/viewvc/vcl/trunk/web/js/siteconfig.js?rev=1732551&r1=1732550&r2=1732551&view=diff
==============================================================================
--- vcl/trunk/web/js/siteconfig.js (original)
+++ vcl/trunk/web/js/siteconfig.js Fri Feb 26 20:06:42 2016
@@ -249,3 +249,339 @@ function natPortRange() {
 }
 natPortRange.prototype = new GlobalSingleVariable();
 var natPortRange = new natPortRange();
+
+function GlobalMultiVariable() {}
+GlobalMultiVariable.prototype.saveSettings = function() {
+	var data = {continuation: dojo.byId(this.domidbase + 'cont').value};
+
+	var keys = dojo.byId(this.domidbase + 'savekeys').value.split(',');
+	for(var i = 0; i < keys.length; i++) {
+		if('checked' in dijit.byId(keys[i])) {
+			if(dijit.byId(keys[i]).checked)
+				data[keys[i]] = dijit.byId(keys[i]).value;
+			else
+				data[keys[i]] = 0;
+		}
+		else {
+			if(! checkValidatedObj(keys[i])) {
+				dijit.byId(keys[i]).focus();
+				return;
+			}
+			data[keys[i]] = dijit.byId(keys[i]).get('value');
+		}
+	}
+	dijit.byId(this.domidbase + 'btn').set('disabled', true);
+	RPCwrapper(data, generalSiteConfigCB, 1);
+}
+GlobalMultiVariable.prototype.addNewMultiVal = function() {
+	var data = {continuation: dojo.byId(this.domidbase + 'addcont').value,
+	            multivalid: dijit.byId(this.domidbase + 'newmultivalid').get('value'),
+	            multival: dijit.byId(this.domidbase + 'newmultival').get('value')};
+	dijit.byId(this.domidbase + 'addbtn').set('disabled', true);
+	RPCwrapper(data, generalSiteConfigCB, 1);
+}
+GlobalMultiVariable.prototype.addNewMultiValCBextra = function(data) {
+	var span = document.createElement('span');
+	span.setAttribute('id', data.items.addid + 'wrapspan');
+	var label = document.createElement('label');
+	label.setAttribute('for', data.items.addid);
+	label.innerHTML = data.items.addname + ': ';
+	span.appendChild(label);
+	var span2 = document.createElement('span');
+	span2.setAttribute('class', 'labeledform');
+	var text = new dijit.form.ValidationTextBox({
+		id: data.items.addid,
+		required: 'true',
+		style: 'width: 400px;',
+		value: data.items.addval,
+		regExp: data.items.regexp,
+		invalidMessage: data.items.invalidmsg
+	}, document.createElement('div'));
+	span2.appendChild(text.domNode);
+	span.appendChild(span2);
+	var func = this.deleteMultiVal
+	var domidbase = this.domidbase;
+	var btn = new dijit.form.Button({
+		id: data.items.addid + 'delbtn',
+		label: _('Delete'),
+		onClick: function() {
+			func(data.items.delkey, domidbase);
+		}
+	}, document.createElement('div'));
+	span.appendChild(btn.domNode);
+	span.appendChild(document.createElement('br'));
+	dojo.byId(this.domidbase + 'multivalspan').appendChild(span);
+	dijit.byId(this.domidbase + 'newmultival').set('value', '');
+	dijit.byId(this.domidbase + 'newmultivalid').removeOption({value: data.items.delkey});
+	if(dijit.byId(this.domidbase + 'newmultivalid').options.length == 0)
+		dojo.addClass(this.domidbase + 'multivalnewspan', 'hidden');
+	var keys = dojo.byId(this.domidbase + 'savekeys').value.split(',');
+	keys.push(data.items.addid);
+	dojo.byId(this.domidbase + 'savekeys').value = keys.join(',');
+	dijit.byId(this.domidbase + 'addbtn').set('disabled', false);
+}
+GlobalMultiVariable.prototype.deleteMultiVal = function(key, domidbase) {
+	var data = {key: key,
+	            continuation: dojo.byId('delete' + domidbase + 'cont').value};
+	RPCwrapper(data, generalSiteConfigCB, 1);
+}
+GlobalMultiVariable.prototype.deleteMultiValCBextra = function(data) {
+	dijit.byId(data.items.delid).destroy();
+	dijit.byId(data.items.delid + 'delbtn').destroy();
+	dojo.destroy(data.items.delid + 'wrapspan');
+	dijit.byId(this.domidbase + 'newmultivalid').addOption({value: data.items.addid, label: data.items.addname});
+	//dojo.removeClass(this.domidbase + 'adddiv', 'hidden');
+	var keys = dojo.byId(this.domidbase + 'savekeys').value.split(',');
+	var newkeys = new Array();
+	for(var i = 0; i < keys.length; i++) {
+		if(keys[i] != data.items.delid)
+			newkeys.push(keys[i]);
+	}
+	dojo.byId(this.domidbase + 'savekeys').value = newkeys.join(',');
+	//dojo.byId(this.domidbase + 'cont').value = data.items.savecont;
+	dojo.removeClass(this.domidbase + 'multivalnewspan', 'hidden');
+}
+
+function nfsmount() {
+	GlobalMultiVariable.apply(this, Array.prototype.slice.call(arguments));
+	this.domidbase = 'nfsmount';
+}
+nfsmount.prototype = new GlobalMultiVariable();
+var nfsmount = new nfsmount();
+
+function messages() {
+	var items;
+	var timer;
+	var validatecnt;
+	var invalids;
+	this.init();
+}
+messages.prototype.init = function() {
+	if(typeof dijit !== 'object' || dijit.byId('messagesselid') === undefined) {
+		setTimeout(this.init, 500);
+		return;
+	}
+	messages.setContents(1);
+	messages.invalids = new Object();
+	setTimeout(function() {
+		messages.validatecnt = 1;
+		messages.validatePoll();
+	}, 1000);
+}
+messages.prototype.validateContent = function() {
+	var subj = dijit.byId('messagessubject').get('value');
+	if(! this.checkBalancedBrackets(subj)) {
+		dojo.addClass('messagesmsg', 'cfgerror');
+		dojo.removeClass('messagesmsg', 'cfgsuccess');
+		dojo.byId('messagesmsg').innerHTML = _('Unmatched or empty brackets ( [ and ] ) in subject');
+		return false;
+	}
+	var body = dijit.byId('messagesbody').get('value');
+	if(! this.checkBalancedBrackets(body)) {
+		dojo.addClass('messagesmsg', 'cfgerror');
+		dojo.removeClass('messagesmsg', 'cfgsuccess');
+		dojo.byId('messagesmsg').innerHTML = _('Unmatched or empty brackets ( [ and ] ) in message');
+		return false;
+	}
+	var shortmsg = dijit.byId('messagesshortmsg').get('value');
+	if(! this.checkBalancedBrackets(shortmsg)) {
+		dojo.addClass('messagesmsg', 'cfgerror');
+		dojo.removeClass('messagesmsg', 'cfgsuccess');
+		dojo.byId('messagesmsg').innerHTML = _('Unmatched or empty brackets ( [ and ] ) in short message');
+		return false;
+	}
+	return true;
+}
+messages.prototype.checkBalancedBrackets = function(string) {
+	var len = string.length;
+	var inBracket = 0;
+	var hasContent = 0;
+	for(var i = 0; i < len; i++) {
+		var ch = string.charAt(i);
+		switch(ch) {
+			case '[':
+				if(inBracket)
+					return false;
+				inBracket = 1;
+				hasContent = 0;
+				break;
+			case ']':
+				if(! hasContent)
+					return false;
+				inBracket = 0;
+				hasContent = 0;
+				break;
+			default:
+				if(inBracket)
+					hasContent = 1;
+		}
+	}
+	if(inBracket)
+		return false;
+	return true;
+}
+messages.prototype.setContents = function(clearmsg) {
+	if(messages.items === undefined) {
+		messages.items = JSON.parse(document.getElementById('messagesdata').innerHTML);
+	}
+	var msgkey = dijit.byId('messagesselid').get('value');
+	var msgkeyparts = msgkey.split('|');
+	if(msgkeyparts[0] == 'adminmessage') {
+		dijit.byId('messagesaffilid').set('displayedValue', 'Global');
+		dijit.byId('messagesaffilid').set('disabled', true);
+	}
+	else {
+		dijit.byId('messagesaffilid').set('disabled', false);
+	}
+	var affil = dijit.byId('messagesaffilid').get('displayedValue');
+	var key = msgkey + '|' + affil;
+	if(affil == 'Global' || ! (affil in messages.items[msgkey])) {
+		// use default
+		dijit.byId('messagesdelbtn').set('disabled', true);
+		var item = messages.items[msgkey]['Global'];
+		if(affil == 'Global') {
+			dojo.addClass('defaultmessagesdiv', 'hidden');
+		}
+		else {
+			dojo.removeClass('defaultmessagesdiv', 'hidden');
+		}
+	}
+	else {
+		// use affil specific msg
+		dijit.byId('messagesdelbtn').set('disabled', false);
+		var item = messages.items[msgkey][affil];
+		dojo.addClass('defaultmessagesdiv', 'hidden');
+	}
+	var affiltype;
+	if(affil == 'Global') {
+		affiltype = 'Default message for any affiliation';
+	}
+	else {
+		affiltype = 'Message for ' + affil + ' affiliation';
+	}
+	dojo.byId('messagesaffil').innerHTML = affiltype;
+	dijit.byId('messagessubject').set('value', item['subject']);
+	dijit.byId('messagesbody').set('value', item['message']);
+	if('short_message' in item)
+		dijit.byId('messagesshortmsg').set('value', item['short_message']);
+	else
+		dijit.byId('messagesshortmsg').set('value', '');
+	if(clearmsg) {
+		dojo.removeClass('messagesmsg', 'cfgerror');
+		dojo.removeClass('messagesmsg', 'cfgsuccess');
+		dojo.byId('messagesmsg').innerHTML = '';
+	}
+}
+messages.prototype.confirmdeletemsg = function() {
+	var affil = dijit.byId('messagesaffilid').get('displayedValue');
+	if(affil == 'Global')
+		return;
+	var key = dijit.byId('messagesselid').get('value');
+	var keyparts = key.split('|');
+	dojo.byId('deleteMsgAffil').innerHTML = affil;
+	dojo.byId('deleteMsgCategory').innerHTML = keyparts[0];
+	dojo.byId('deleteMsgType').innerHTML = keyparts[1];
+	dijit.byId('deleteMessageDlg').show();
+}
+messages.prototype.deletemsg = function() {
+	dijit.byId('messagesdelbtn').set('disabled', true);
+	dijit.byId('messagesselid').set('disabled', true);
+	dijit.byId('messagesaffilid').set('disabled', true);
+	var data = {key: dijit.byId('messagesselid').get('value'),
+	            affilid: dijit.byId('messagesaffilid').get('value'),
+	            continuation: dojo.byId('deletemessagescont').value};
+	RPCwrapper(data, generalSiteConfigCB, 1);
+}
+messages.prototype.deleteMessagesCBextra = function(data) {
+	dijit.byId('messagesselid').set('disabled', false);
+	dijit.byId('messagesaffilid').set('disabled', false);
+	delete messages.items[data.items.key][data.items.affil];
+	dijit.byId('deleteMessageDlg').hide();
+	messages.setContents(0);
+}
+messages.prototype.savemsg = function() {
+	if(! this.validateContent()) {
+		return;
+	}
+	var invalidkey = dijit.byId('messagesselid').get('value') + '|' + dijit.byId('messagesaffilid').get('displayedValue');
+	if('invalidkey' in this.invalids) {
+		this.invalids.splice(invalidkey, 1);
+		this.updateInvalidContent();
+	}
+	dijit.byId('messagessavebtn').set('disabled', true);
+	dijit.byId('messagesselid').set('disabled', true);
+	dijit.byId('messagesaffilid').set('disabled', true);
+	var data = {key: dijit.byId('messagesselid').get('value'),
+	            affilid: dijit.byId('messagesaffilid').get('value'), 
+	            subject: dijit.byId('messagessubject').get('value'), 
+	            body:  dijit.byId('messagesbody').get('value'),
+	            shortmsg:  dijit.byId('messagesshortmsg').get('value'),
+	            continuation: dojo.byId('savemessagescont').value};
+	RPCwrapper(data, generalSiteConfigCB, 1);
+}
+messages.prototype.saveMessagesCBextra = function(data) {
+	dijit.byId('messagesselid').set('disabled', false);
+	dijit.byId('messagesaffilid').set('disabled', false);
+	messages.items[data.items.key][data.items.affil] = new Object();
+	messages.items[data.items.key][data.items.affil]['name'] = data.items.name;
+	messages.items[data.items.key][data.items.affil]['subject'] = data.items.subject;
+	messages.items[data.items.key][data.items.affil]['message'] = data.items.body;
+	messages.items[data.items.key][data.items.affil]['short_message'] = data.items.shortmsg;
+	dijit.byId('deleteMessageDlg').hide();
+	messages.setContents(0);
+	messages.startValidatePoll();
+}
+messages.prototype.validatePoll = function() {
+	var data = {continuation: dojo.byId('validatemessagespollcont').value};
+	RPCwrapper(data, this.validatePollCB, 1);
+	this.validatecnt--;
+}
+messages.prototype.validatePollCB = function(data, ioArgs) {
+	if(data.items.status == 'invalid') {
+		messages.invalids = data.items.values;
+		messages.updateInvalidContent();
+	}
+	else {
+		messages.invalids = new Object();
+		dijit.byId('invalidmsgfieldspane').hide();
+	}
+	clearTimeout(this.timer);
+	if(messages.validatecnt <= 0)
+		return;
+	messages.timer = setTimeout(function() {
+		messages.validatePoll();
+	}, 1000);
+}
+messages.prototype.startValidatePoll = function() {
+	this.validatecnt = 60;
+	this.validatePoll();
+}
+messages.prototype.stopValidatePoll = function() {
+	clearTimeout(messages.timer);
+}
+messages.prototype.updateInvalidContent = function() {
+	var msg = '';
+	for(key in this.invalids) {
+		var parts = key.split('|');
+		var item = this.invalids[key];
+		if(parts.length == 2) {
+			msg += 'Message: ' + parts[0] + ' -&gt; ' + parts[1] + '<br>';
+			for(var i = 0; i < item.length; i++) {
+				msg += item[i] + '<br>';
+			}
+			msg += '<br>';
+		}
+		else {
+			msg += 'Affiliation: ' + parts[2] + '<br>';
+			msg += 'Message: ' + parts[0] + ' -&gt; ' + parts[1] + '<br>';
+			for(var i = 0; i < item.length; i++) {
+				msg += item[i] + '<br>';
+			}
+			msg += '<br>';
+		}
+	}
+	dojo.byId('invalidmsgfieldcontent').innerHTML = msg;
+	if(dijit.byId('invalidmsgfieldspane').domNode.style.visibility != 'visible')
+		dijit.byId('invalidmsgfieldspane').show();
+}
+var messages = new messages();