Merge remote-tracking branch 'upstream/master' into speedy

This commit is contained in:
user
2012-11-20 14:04:40 -04:00
13 changed files with 571 additions and 297 deletions

View File

@@ -997,9 +997,10 @@ class G
$calendarJs = '';
$calendarJsFile = PATH_GULLIVER_HOME . "js/widgets/js-calendar/lang/" . $locale .".js";
if (file_exists($calendarJsFile)) {
$calendarJs = file_get_contents($calendarJsFile) . "\n";
if (! file_exists($calendarJsFile)) {
$calendarJsFile = PATH_GULLIVER_HOME . "js/widgets/js-calendar/lang/en.js";
}
$calendarJs = file_get_contents($calendarJsFile) . "\n";
return $calendarJs . 'var TRANSLATIONS = ' . G::json_encode( $translation ) . ';' ;
}
@@ -3124,10 +3125,10 @@ class G
G::LoadClass("pmFunctions");
G::LoadThirdParty('phpmailer', 'class.phpmailer');
$setup = getEmailConfiguration();
if ($setup['MESS_RAUTH'] == false || (is_string($setup['MESS_RAUTH']) && $setup['MESS_RAUTH'] == 'false')) {
$setup['MESS_RAUTH'] = 0;
} else {
$setup['MESS_RAUTH'] = 1;
if ($setup['MESS_RAUTH'] == false || (is_string($setup['MESS_RAUTH']) && $setup['MESS_RAUTH'] == 'false')) {
$setup['MESS_RAUTH'] = 0;
} else {
$setup['MESS_RAUTH'] = 1;
}
if (count($setup) == 0 || !isset($setup['MESS_ENGINE']) || !isset($setup['MESS_SERVER'])

View File

@@ -3006,13 +3006,14 @@ class XmlForm_Field_Submit extends XmlForm_Field
// return '<input id="form['.$this->name.']" name="form['.$this->name.']" type=\'submit\' value=\''. $this->label .'\' disabled/>';
return "<input style=\"{$this->style}\" class='module_app_button___gray {$this->className}' id=\"form[{$this->name}]\" name=\"form[{$this->name}]\" type='submit' value=\"{$this->label}\" " . (($this->onclick) ? 'onclick="' . htmlentities( $onclick, ENT_COMPAT, 'utf-8' ) . '"' : '') . " />";
} elseif ($this->mode === 'view') {
// return "<input style=\"{$this->style};display:none\" disabled='disabled' class='module_app_button___gray module_app_buttonDisabled___gray {$this->className}' id=\"form[{$this->name}]\" name=\"form[{$this->name}]\" type='submit' value=\"{$this->label}\" " . (($this->onclick) ? 'onclick="' . htmlentities ( $onclick, ENT_COMPAT, 'utf-8' ) . '"' : '') . " />";
//return "<input style=\"{$this->style};display:none\" disabled='disabled' class='module_app_button___gray module_app_buttonDisabled___gray {$this->className}' id=\"form[{$this->name}]\" name=\"form[{$this->name}]\" type='submit' value=\"{$this->label}\" " . (($this->onclick) ? 'onclick="' . htmlentities ( $onclick, ENT_COMPAT, 'utf-8' ) . '"' : '') . " />";
//$sLinkNextStep = 'window.open("' . $owner->fields['__DYNAFORM_OPTIONS']->xmlMenu->values['NEXT_STEP'] . '", "_self");';
$html = '';
if (isset( $_SESSION['CURRENT_DYN_UID'] )) {
$sLinkNextStep = 'window.location=("casesSaveDataView?UID=' . $_SESSION['CURRENT_DYN_UID'] . '");';
$html = '<input style="' . $this->style . '" class="module_app_button___gray ' . $this->className . '" id="form[' . $this->name . ']" name="form[' . $this->name . ']" type="button" value="' . G::LoadTranslation( 'ID_CONTINUE' ) . '" onclick="' . htmlentities( $sLinkNextStep, ENT_COMPAT, 'utf-8' ) . '" />';
}
$html .= '<input ';
$html .= 'id="form[' . $this->name . ']" ';
$html .= 'name="form[' . $this->name . ']" ';

View File

@@ -1290,6 +1290,49 @@ function WSUnpauseCase ($caseUid, $delIndex, $userUid)
return $response;
}
/**
*
* @method Add case note.
*
* @name WSAddCaseNote
* @label WS Add case note
* @link http://wiki.processmaker.com/index.php/ProcessMaker_Functions#WSAddCaseNote.28.29
*
* @param string(32) | $caseUid | ID of the case | The unique ID of the case.
* @param string(32) | $processUid | ID of the process | The unique ID of the process.
* @param string(32) | $taskUid | ID of the task | The unique ID of the task.
* @param string(32) | $userUid | ID user | The unique ID of the user who will add note case.
* @param string | $note | Note of the case | Note of the case.
* @param int | $sendMail = 1 | Send mail | Optional parameter. If set to 1, will send an email to all participants in the case.
* @return array | $response | WS array | A WS Response associative array.
*
*/
function WSAddCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1)
{
$client = WSOpen();
$sessionId = $_SESSION["WS_SESSION_ID"];
$params = array(
"sessionId" => $sessionId,
"caseUid" => $caseUid,
"processUid" => $processUid,
"taskUid" => $taskUid,
"userUid" => $userUid,
"note" => $note,
"sendMail" => $sendMail
);
$result = $client->__soapCall("addCaseNote", array($params));
$response = array();
$response["status_code"] = $result->status_code;
$response["message"] = $result->message;
$response["time_stamp"] = $result->timestamp;
return $response;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -2421,3 +2464,33 @@ function PMFUnpauseCase ($caseUid, $delIndex, $userUid)
}
}
/**
*
* @method Add case note.
*
* @name PMFAddCaseNote
* @label PMF Add case note
*
* @param string(32) | $caseUid | ID of the case | The unique ID of the case.
* @param string(32) | $processUid | ID of the process | The unique ID of the process.
* @param string(32) | $taskUid | ID of the task | The unique ID of the task.
* @param string(32) | $userUid | ID user | The unique ID of the user who will add note case.
* @param string | $note | Note of the case | Note of the case.
* @param int | $sendMail = 1 | Send mail | Optional parameter. If set to 1, will send an email to all participants in the case.
* @return int | $result | Result of the add case note | Returns 1 if the note has been added to the case.; otherwise, returns 0 if an error occurred.
*
*/
function PMFAddCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1)
{
G::LoadClass("wsBase");
$ws = new wsBase();
$result = $ws->addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail);
if ($result->status_code == 0) {
return 1;
} else {
return 0;
}
}

View File

@@ -541,6 +541,13 @@ function handleErrors ($errno, $errstr, $errfile, $errline)
if (isset( $_SESSION['_CODE_'] )) {
$sCode = $_SESSION['_CODE_'];
unset( $_SESSION['_CODE_'] );
global $oPMScript;
if (isset($oPMScript) && isset($_SESSION['APPLICATION'])) {
G::LoadClass( 'case' );
$oCase = new Cases();
$oPMScript->aFields['__ERROR__'] = $errstr;
$oCase->updateCase($_SESSION['APPLICATION'], array('APP_DATA' => $oPMScript->aFields));
}
registerError( 1, $errstr, $errline - 1, $sCode );
}
}
@@ -554,6 +561,8 @@ function handleErrors ($errno, $errstr, $errfile, $errline)
function handleFatalErrors ($buffer)
{
G::LoadClass( 'case' );
$oCase = new Cases();
if (preg_match( '/(error<\/b>:)(.+)(<br)/', $buffer, $regs )) {
$err = preg_replace( '/<.*?>/', '', $regs[2] );
$aAux = explode( ' in ', $err );
@@ -562,23 +571,29 @@ function handleFatalErrors ($buffer)
registerError( 2, $aAux[0], 0, $sCode );
if (strpos( $_SERVER['REQUEST_URI'], '/cases/cases_Step' ) !== false) {
if (strpos( $_SERVER['REQUEST_URI'], '&ACTION=GENERATE' ) !== false) {
G::LoadClass( 'case' );
$oCase = new Cases();
$aNextStep = $oCase->getNextStep( $_SESSION['PROCESS'], $_SESSION['APPLICATION'], $_SESSION['INDEX'], $_SESSION['STEP_POSITION'] );
if ($_SESSION['TRIGGER_DEBUG']['ISSET']) {
$_SESSION['TRIGGER_DEBUG']['TIME'] = 'AFTER';
$_SESSION['TRIGGER_DEBUG']['BREAKPAGE'] = $aNextStep['PAGE'];
$aNextStep['PAGE'] = $aNextStep['PAGE'] . '&breakpoint=triggerdebug';
}
global $oPMScript;
if (isset($oPMScript) && isset($_SESSION['APPLICATION'])) {
$oPMScript->aFields['__ERROR__'] = $aAux[0];
$oCase->updateCase($_SESSION['APPLICATION'], array('APP_DATA' => $oPMScript->aFields));
}
G::header( 'Location: ' . $aNextStep['PAGE'] );
die();
}
$_SESSION['_NO_EXECUTE_TRIGGERS_'] = 1;
global $oPMScript;
if (isset($oPMScript) && isset($_SESSION['APPLICATION'])) {
$oPMScript->aFields['__ERROR__'] = $aAux[0];
$oCase->updateCase($_SESSION['APPLICATION'], array('APP_DATA' => $oPMScript->aFields));
}
G::header( 'Location: ' . $_SERVER['REQUEST_URI'] );
die();
} else {
G::LoadClass( 'case' );
$oCase = new Cases();
$aNextStep = $oCase->getNextStep( $_SESSION['PROCESS'], $_SESSION['APPLICATION'], $_SESSION['INDEX'], $_SESSION['STEP_POSITION'] );
if ($_SESSION['TRIGGER_DEBUG']['ISSET']) {
$_SESSION['TRIGGER_DEBUG']['TIME'] = 'AFTER';
@@ -588,6 +603,11 @@ function handleFatalErrors ($buffer)
if (strpos( $aNextStep['PAGE'], 'TYPE=ASSIGN_TASK&UID=-1' ) !== false) {
G::SendMessageText( 'Fatal error in trigger', 'error' );
}
global $oPMScript;
if (isset($oPMScript) && isset($_SESSION['APPLICATION'])) {
$oPMScript->aFields['__ERROR__'] = $aAux[0];
$oCase->updateCase($_SESSION['APPLICATION'], array('APP_DATA' => $oPMScript->aFields));
}
G::header( 'Location: ' . $aNextStep['PAGE'] );
die();
}

View File

@@ -30,6 +30,7 @@ require_once ("classes/model/AppCacheView.php");
require_once ("classes/model/AppDelegation.php");
require_once ("classes/model/AppDocument.php");
require_once ("classes/model/AppDelay.php");
require_once ("classes/model/AppNotes.php");
require_once ("classes/model/AppThread.php");
require_once ("classes/model/Department.php");
require_once ("classes/model/Dynaform.php");
@@ -3026,5 +3027,75 @@ class wsBase
return $result;
}
}
/**
* Add case note
*
* @param string caseUid : ID of the case.
* @param string processUid : ID of the process.
* @param string taskUid : ID of the task.
* @param string userUid : The unique ID of the user who will add note case.
* @param string note : Note of the case.
* @param int sendMail : Optional parameter. If set to 1, will send an email to all participants in the case.
* @return $result will return an object
*/
public function addCaseNote($caseUid, $processUid, $taskUid, $userUid, $note, $sendMail = 1)
{
try {
if (empty($caseUid)) {
$result = new wsResponse(100, G::LoadTranslation("ID_REQUIRED_FIELD") . " caseUid");
return $result;
}
if (empty($processUid)) {
$result = new wsResponse(100, G::LoadTranslation("ID_REQUIRED_FIELD") . " processUid");
return $result;
}
if (empty($taskUid)) {
$result = new wsResponse(100, G::LoadTranslation("ID_REQUIRED_FIELD") . " taskUid");
return $result;
}
if (empty($userUid)) {
$result = new wsResponse(100, G::LoadTranslation("ID_REQUIRED_FIELD") . " userUid");
return $result;
}
if (empty($note)) {
$result = new wsResponse(100, G::LoadTranslation("ID_REQUIRED_FIELD") . " note");
return $result;
}
$case = new Cases();
$respView = $case->getAllObjectsFrom($processUid, $caseUid, $taskUid, $userUid, "VIEW");
$respBlock = $case->getAllObjectsFrom($processUid, $caseUid, $taskUid, $userUid, "BLOCK");
if ($respView["CASES_NOTES"] == 0 && $respBlock["CASES_NOTES"] == 0) {
$result = new wsResponse(100, G::LoadTranslation("ID_CASES_NOTES_NO_PERMISSIONS"));
return $result;
}
//Add note case
$appNote = new AppNotes();
$response = $appNote->addCaseNote($caseUid, $userUid, $note, $sendMail);
//Response
$result = new wsResponse(0, G::LoadTranslation("ID_COMMAND_EXECUTED_SUCCESSFULLY"));
return $result;
} catch (Exception $e) {
$result = new wsResponse(100, $e->getMessage());
return $result;
}
}
}

View File

@@ -275,6 +275,15 @@ class AppFolder extends BaseAppFolder
$oCriteria->add( AppDocumentPeer::APP_DOC_STATUS, 'ACTIVE' );
}
$numRecTotal = AppDocumentPeer::doCount($oCriteria);
$auxCriteria = clone $oCriteria;
$auxCriteria->addJoin(AppDocumentPeer::DOC_UID, OutputDocumentPeer::OUT_DOC_UID);
$auxCriteria->add(AppDocumentPeer::APP_DOC_TYPE, 'OUTPUT');
$auxCriteria->add(OutputDocumentPeer::OUT_DOC_UID, '-1', Criteria::NOT_EQUAL);
$auxCriteria->add(OutputDocumentPeer::OUT_DOC_GENERATE, 'BOTH');
$numRecTotal += AppDocumentPeer::doCount($auxCriteria);
$oCase->verifyTable();
$oCriteria->addAscendingOrderByColumn( AppDocumentPeer::APP_DOC_INDEX );
@@ -321,8 +330,10 @@ class AppFolder extends BaseAppFolder
}
$rs->next();
}
$response['totalDocumentsCount'] = count($response['documents']);
return ($response);
$response["totalDocumentsCount"] = $numRecTotal;
return $response;
}
public function getCompleteDocumentInfo ($appUid, $appDocUid, $docVersion, $docUid, $usrId)

View File

@@ -217,10 +217,10 @@ class AppNotes extends BaseAppNotes
$sTo = ((($aUser['USR_FIRSTNAME'] != '') || ($aUser['USR_LASTNAME'] != '')) ? $aUser['USR_FIRSTNAME'] . ' ' . $aUser['USR_LASTNAME'] . ' ' : '') . '<' . $aUser['USR_EMAIL'] . '>';
$oSpool = new spoolRun();
if ($aConfiguration['MESS_RAUTH'] == false || (is_string($aConfiguration['MESS_RAUTH']) && $aConfiguration['MESS_RAUTH'] == 'false')) {
$aConfiguration['MESS_RAUTH'] = 0;
} else {
$aConfiguration['MESS_RAUTH'] = 1;
if ($aConfiguration['MESS_RAUTH'] == false || (is_string($aConfiguration['MESS_RAUTH']) && $aConfiguration['MESS_RAUTH'] == 'false')) {
$aConfiguration['MESS_RAUTH'] = 0;
} else {
$aConfiguration['MESS_RAUTH'] = 1;
}
$oSpool->setConfig( array ('MESS_ENGINE' => $aConfiguration['MESS_ENGINE'],'MESS_SERVER' => $aConfiguration['MESS_SERVER'],'MESS_PORT' => $aConfiguration['MESS_PORT'],'MESS_ACCOUNT' => $aConfiguration['MESS_ACCOUNT'],'MESS_PASSWORD' => $aConfiguration['MESS_PASSWORD'],'SMTPAuth' => $aConfiguration['MESS_RAUTH'] == '1' ? true : false,'SMTPSecure' => isset( $aConfiguration['SMTPSecure'] ) ? $aConfiguration['SMTPSecure'] : '') );
@@ -235,5 +235,30 @@ class AppNotes extends BaseAppNotes
throw $oException;
}
}
public function addCaseNote($applicationUid, $userUid, $note, $sendMail)
{
$response = $this->postNewNote($applicationUid, $userUid, $note, false);
if ($sendMail == 1) {
G::LoadClass("case");
$case = new Cases();
$p = $case->getUsersParticipatedInCase($applicationUid);
$noteRecipientsList = array();
foreach ($p["array"] as $key => $userParticipated) {
$noteRecipientsList[] = $key;
}
$noteRecipients = implode(",", $noteRecipientsList);
$note = stripslashes($note);
$this->sendNoteNotification($applicationUid, $userUid, $note, $noteRecipients);
}
return $response;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -121,30 +121,40 @@ function expandNode()
$totalDocuments=0;
if (($_POST['sendWhat'] == "dirs") || ($_POST['sendWhat'] == "both")) {
$folderListObj = $oPMFolder->getFolderList ($_POST ['node'] != 'root' ?
$_POST ['node'] == 'NA' ? "" : $_POST ['node'] : $rootFolder, $limit, $start);
//G::pr($folderListObj);
$folderListObj = $oPMFolder->getFolderList(
($_POST["node"] != "root")? (($_POST["node"] == "NA")? "" : $_POST["node"]) : $rootFolder,
$limit,
$start
);
$folderList=$folderListObj['folders'];
$totalFolders=$folderListObj['totalFoldersCount'];
$totalItems+=count($folderList);
//G::pr($folderListObj);
}
if (($_POST['sendWhat'] == "files") || ($_POST['sendWhat'] == "both")) {
global $RBAC;
$user = ($RBAC->userCanAccess('PM_ALLCASES') == 1)? '' : $_SESSION['USER_LOGGED'];
$folderContentObj = $oPMFolder->getFolderContent ($_POST ['node'] != 'root' ?
$_POST ['node'] == 'NA' ? "" : $_POST ['node'] : $rootFolder, array(), null, null, $limit, $start, $user, true);
$folderContentObj = $oPMFolder->getFolderContent(
($_POST["node"] != "root")? (($_POST["node"] == "NA")? "" : $_POST["node"]) : $rootFolder,
array(),
null,
null,
$limit,
$start,
$user,
true
);
$folderContent=$folderContentObj['documents'];
$totalDocuments=$folderContentObj['totalDocumentsCount'];
$totalItems+=count($folderContent);
//G::pr($folderContent);
}
// G::pr($folderList);
//var_dump(isset($folderList));
$processListTree=array();
$tempTree=array();
if (isset($folderList) && sizeof($folderList)>0) {
//print'krlos';
//$tempTree=array();
foreach ($folderList as $key => $obj) {
//$tempTree ['all-obj'] = $obj;
@@ -339,15 +349,15 @@ function expandNode()
$tempTree=array();
}
}
//G::pr($processListTree);
if ((isset($_POST['option'])) && ($_POST['option'] == "gridDocuments")) {
$processListTreeTemp['totalCount']=$totalFolders+count($processListTree);
$processListTreeTemp["totalCount"] = $totalFolders + $totalDocuments;
$processListTreeTemp['msg']='correct reload';
$processListTreeTemp['items']=$processListTree;
$processListTree = $processListTreeTemp;
}
//G::pr ($processListTree);die;
print G::json_encode ($processListTree);
echo G::json_encode($processListTree);
}
function openPMFolder()

View File

@@ -434,12 +434,27 @@ if ($actionAjax == 'historyDynaformGridPreview') {
$Fields['APP_DATA']['__DYNAFORM_OPTIONS']['NEXT_STEP'] = '#';
$Fields['APP_DATA']['__DYNAFORM_OPTIONS']['NEXT_ACTION'] = 'return false;';
$Fields['APP_DATA']['__DYNAFORM_OPTIONS']['DYNUIDPRINT'] = $_POST['DYN_UID'];
?>
<script language="javascript">
window.onload = function () {
var inputs = document.getElementsByTagName('input');
for(var i= 0; i<inputs.length; i++) {
if(inputs[i].type == 'button' || inputs[i].type == 'submit')
{
inputs[i].disabled = true;
}
}
};
</script>
<?php
$_SESSION['DYN_UID_PRINT'] = $_POST['DYN_UID'];
$G_PUBLISH->AddContent( 'dynaform', 'xmlform', $_SESSION['PROCESS'] . '/' . $_POST['DYN_UID'], '', $Fields['APP_DATA'], '', '', 'view' );
?>
<script language="javascript">
function popUp(URL, width, height, left, top, resizable) {
window.open(URL, '', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=1,resizable='+resizable+',width='+width+',height='+height+',left = '+left+',top = '+top+'');
}

View File

@@ -871,6 +871,28 @@
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="addCaseNoteRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="sessionId" type="xs:string"/>
<xs:element name="caseUid" type="xs:string"/>
<xs:element name="processUid" type="xs:string"/>
<xs:element name="taskUid" type="xs:string"/>
<xs:element name="userUid" type="xs:string"/>
<xs:element name="note" type="xs:string"/>
<xs:element name="sendMail" minOccurs="0" maxOccurs="unbounded" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="addCaseNoteResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="status_code" type="xs:integer"/>
<xs:element name="message" type="xs:string"/>
<xs:element name="timestamp" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</types>
@@ -1099,6 +1121,12 @@
<message name="unpauseCaseResponse">
<part name="parameters" element="xs0:unpauseCaseResponse"/>
</message>
<message name="addCaseNoteRequest">
<part name="parameters" element="xs0:addCaseNoteRequest"/>
</message>
<message name="addCaseNoteResponse">
<part name="parameters" element="xs0:addCaseNoteResponse"/>
</message>
<portType name="ProcessMakerServiceSoap">
<operation name="login">
@@ -1261,6 +1289,10 @@
<input message="xs0:unpauseCaseRequest"/>
<output message="xs0:unpauseCaseResponse"/>
</operation>
<operation name="addCaseNote">
<input message="xs0:addCaseNoteRequest"/>
<output message="xs0:addCaseNoteResponse"/>
</operation>
</portType>
<binding name="ProcessMakerServiceSoap" type="xs0:ProcessMakerServiceSoap">
@@ -1625,6 +1657,15 @@
<soap12:body use="literal"/>
</output>
</operation>
<operation name="addCaseNote">
<soap12:operation soapAction="urn:addCaseNote" soapActionRequired="true" style="document"/>
<input>
<soap12:body use="literal"/>
</input>
<output>
<soap12:body use="literal"/>
</output>
</operation>
</binding>
<service name="ProcessMakerService">

View File

@@ -1190,6 +1190,33 @@ function unpauseCase ($params)
return $result;
}
function addCaseNote($params)
{
$result = isValidSession($params->sessionId);
if ($result->status_code != 0) {
return $result;
}
if (ifPermission($params->sessionId, "PM_CASES") == 0) {
$result = new wsResponse(2, "You do not have privileges");
return $result;
}
$ws = new wsBase();
$result = $ws->addCaseNote(
$params->caseUid,
$params->processUid,
$params->taskUid,
$params->userUid,
$params->note,
(isset($params->sendMail))? $params->sendMail : 1
);
return $result;
}
$server = new SoapServer($wsdl);
$server->addFunction("Login");
@@ -1233,5 +1260,6 @@ $server->addFunction("deleteCase");
$server->addFunction("cancelCase");
$server->addFunction("pauseCase");
$server->addFunction("unpauseCase");
$server->addFunction("addCaseNote");
$server->handle();

View File

@@ -1093,11 +1093,7 @@ var gridbb = new Ext.PagingToolbar({
lastText : TRANSLATIONS.ID_LAST,
nextText : TRANSLATIONS.ID_NEXT,
prevText : TRANSLATIONS.ID_PREVIOUS,
refreshText : TRANSLATIONS.ID_RELOAD,
items : [ '-', ' ', ' ', ' ', ' ', ' ', new Ext.ux.StatusBar({
defaultText : TRANSLATIONS.ID_DONE,
id : 'statusPanel'
}) ]
refreshText: TRANSLATIONS.ID_RELOAD
});
var grid;