1<?php 2// Copyright (C) 2010-2018 Combodo SARL 3// 4// This file is part of iTop. 5// 6// iTop is free software; you can redistribute it and/or modify 7// it under the terms of the GNU Affero General Public License as published by 8// the Free Software Foundation, either version 3 of the License, or 9// (at your option) any later version. 10// 11// iTop is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU Affero General Public License for more details. 15// 16// You should have received a copy of the GNU Affero General Public License 17// along with iTop. If not, see <http://www.gnu.org/licenses/> 18 19 20/** 21 * Abstract class that implements some common and useful methods for displaying 22 * the objects 23 * 24 * @copyright Copyright (C) 2010-2018 Combodo SARL 25 * @license http://opensource.org/licenses/AGPL-3.0 26 */ 27 28define('OBJECT_PROPERTIES_TAB', 'ObjectProperties'); 29 30define('HILIGHT_CLASS_CRITICAL', 'red'); 31define('HILIGHT_CLASS_WARNING', 'orange'); 32define('HILIGHT_CLASS_OK', 'green'); 33define('HILIGHT_CLASS_NONE', ''); 34 35define('MIN_WATCHDOG_INTERVAL', 15); // Minimum interval for the watchdog: 15s 36 37require_once(APPROOT.'core/cmdbobject.class.inc.php'); 38require_once(APPROOT.'application/applicationextension.inc.php'); 39require_once(APPROOT.'application/utils.inc.php'); 40require_once(APPROOT.'application/applicationcontext.class.inc.php'); 41require_once(APPROOT.'application/ui.linkswidget.class.inc.php'); 42require_once(APPROOT.'application/ui.linksdirectwidget.class.inc.php'); 43require_once(APPROOT.'application/ui.passwordwidget.class.inc.php'); 44require_once(APPROOT.'application/ui.extkeywidget.class.inc.php'); 45require_once(APPROOT.'application/ui.htmleditorwidget.class.inc.php'); 46require_once(APPROOT.'application/datatable.class.inc.php'); 47require_once(APPROOT.'sources/renderer/console/consoleformrenderer.class.inc.php'); 48require_once(APPROOT.'sources/application/search/searchform.class.inc.php'); 49require_once(APPROOT.'sources/application/search/criterionparser.class.inc.php'); 50require_once(APPROOT.'sources/application/search/criterionconversionabstract.class.inc.php'); 51require_once(APPROOT.'sources/application/search/criterionconversion/criteriontooql.class.inc.php'); 52require_once(APPROOT.'sources/application/search/criterionconversion/criteriontosearchform.class.inc.php'); 53 54abstract class cmdbAbstractObject extends CMDBObject implements iDisplay 55{ 56 protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !) 57 static $iGlobalFormId = 1; 58 protected $aFieldsMap; 59 60 /** 61 * If true, bypass IsActionAllowedOnAttribute when writing this object 62 * 63 * @var bool 64 */ 65 protected $bAllowWrite; 66 67 /** 68 * Constructor from a row of data (as a hash 'attcode' => value) 69 * 70 * @param array $aRow 71 * @param string $sClassAlias 72 * @param array $aAttToLoad 73 * @param array $aExtendedDataSpec 74 */ 75 public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null) 76 { 77 parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec); 78 $this->bAllowWrite = false; 79 } 80 81 /** 82 * returns what will be the next ID for the forms 83 */ 84 public static function GetNextFormId() 85 { 86 return 1 + self::$iGlobalFormId; 87 } 88 89 public static function GetUIPage() 90 { 91 return 'UI.php'; 92 } 93 94 public static function ReloadAndDisplay($oPage, $oObj, $aParams) 95 { 96 $oAppContext = new ApplicationContext(); 97 // Reload the page to let the "calling" page execute its 'onunload' method. 98 // Note 1: The redirection MUST NOT be made via an HTTP "header" since onunload is only called when the actual content of the DOM 99 // is replaced by some other content. So the "bouncing" page must provide some content (in our case a script making the redirection). 100 // Note 2: make sure that the URL below is different from the one of the "Modify" button, otherwise the button will have no effect. This is why we add "&a=1" at the end !!! 101 // Note 3: we use the toggle of a flag in the sessionStorage object to prevent an infinite loop of reloads in case the object is actually locked by another window 102 $sSessionStorageKey = get_class($oObj).'_'.$oObj->GetKey(); 103 $sParams = ''; 104 foreach($aParams as $sName => $value) 105 { 106 $sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing & 107 } 108 $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1'; 109 $oPage->add_script( 110 <<<EOF 111 if (!sessionStorage.getItem('$sSessionStorageKey')) 112 { 113 sessionStorage.setItem('$sSessionStorageKey', 1); 114 window.location.href= "$sUrl"; 115 } 116 else 117 { 118 sessionStorage.removeItem('$sSessionStorageKey'); 119 } 120EOF 121 ); 122 123 $oObj->Reload(); 124 $oObj->DisplayDetails($oPage, false); 125 } 126 127 /** 128 * @param $sMessageId 129 * @param $sMessage 130 * @param $sSeverity 131 * @param $fRank 132 * @param bool $bMustNotExist 133 * 134 * @see SetSessionMessage() 135 * @since 2.6 136 */ 137 protected function SetSessionMessageFromInstance($sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false) 138 { 139 $sObjectClass = get_class($this); 140 $iObjectId = $this->GetKey(); 141 142 self::SetSessionMessage($sObjectClass, $iObjectId, $sMessageId, $sMessage, $sSeverity, $fRank); 143 } 144 145 /** 146 * Set a message diplayed to the end-user next time this object will be displayed 147 * Messages are uniquely identified so that plugins can override standard messages (the final work is given to the 148 * last plugin to set the message for a given message id) In practice, standard messages are recorded at the end 149 * but they will not overwrite existing messages 150 * 151 * @param string $sClass The class of the object (must be the final class) 152 * @param int $iKey The identifier of the object 153 * @param string $sMessageId Your id or one of the well-known ids: 'create', 'update' and 'apply_stimulus' 154 * @param string $sMessage The HTML message (must be correctly escaped) 155 * @param string $sSeverity Any of the following: ok, info, error. 156 * @param float $fRank Ordering of the message: smallest displayed first (can be negative) 157 * @param bool $bMustNotExist Do not alter any existing message (considering the id) 158 * 159 * @see SetSessionMessageFromInstance() to call from within an instance 160 */ 161 public static function SetSessionMessage( 162 $sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false 163 ) { 164 $sMessageKey = $sClass.'::'.$iKey; 165 if (!isset($_SESSION['obj_messages'][$sMessageKey])) 166 { 167 $_SESSION['obj_messages'][$sMessageKey] = array(); 168 } 169 if (!$bMustNotExist || !array_key_exists($sMessageId, $_SESSION['obj_messages'][$sMessageKey])) 170 { 171 $_SESSION['obj_messages'][$sMessageKey][$sMessageId] = array( 172 'rank' => $fRank, 173 'severity' => $sSeverity, 174 'message' => $sMessage, 175 ); 176 } 177 } 178 179 function DisplayBareHeader(WebPage $oPage, $bEditMode = false) 180 { 181 // Standard Header with name, actions menu and history block 182 // 183 184 if (!$oPage->IsPrintableVersion()) 185 { 186 // Is there a message for this object ?? 187 $aMessages = array(); 188 $aRanks = array(); 189 if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) 190 { 191 $aLockInfo = iTopOwnershipLock::IsLocked(get_class($this), $this->GetKey()); 192 if ($aLockInfo['locked']) 193 { 194 $aRanks[] = 0; 195 $sName = $aLockInfo['owner']->GetName(); 196 if ($aLockInfo['owner']->Get('contactid') != 0) 197 { 198 $sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')'; 199 } 200 $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); 201 $aMessages[] = "<div class=\"header_message message_error\">".Dict::Format('UI:CurrentObjectIsLockedBy_User', 202 $sName)."</div>"; 203 } 204 } 205 $sMessageKey = get_class($this).'::'.$this->GetKey(); 206 if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, 207 $_SESSION['obj_messages'])) 208 { 209 foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) 210 { 211 $sMsgClass = 'message_'.$aMessageData['severity']; 212 $aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>"; 213 $aRanks[] = $aMessageData['rank']; 214 } 215 unset($_SESSION['obj_messages'][$sMessageKey]); 216 } 217 array_multisort($aRanks, $aMessages); 218 foreach($aMessages as $sMessage) 219 { 220 $oPage->add($sMessage); 221 } 222 } 223 224 if (!$oPage->IsPrintableVersion()) 225 { 226 // action menu 227 $oSingletonFilter = new DBObjectSearch(get_class($this)); 228 $oSingletonFilter->AddCondition('id', $this->GetKey(), '='); 229 $oBlock = new MenuBlock($oSingletonFilter, 'details', false); 230 $oBlock->Display($oPage, -1); 231 } 232 233 // Master data sources 234 $aIcons = array(); 235 if (!$oPage->IsPrintableVersion()) 236 { 237 $oCreatorTask = null; 238 $bCanBeDeletedByTask = false; 239 $bCanBeDeletedByUser = true; 240 $aMasterSources = array(); 241 $aSyncData = $this->GetSynchroData(); 242 if (count($aSyncData) > 0) 243 { 244 foreach($aSyncData as $iSourceId => $aSourceData) 245 { 246 $oDataSource = $aSourceData['source']; 247 $oReplica = reset($aSourceData['replica']); // Take the first one! 248 249 $sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica); 250 $sLink = $oDataSource->GetName(); 251 if (!empty($sApplicationURL)) 252 { 253 $sLink = "<a href=\"$sApplicationURL\" target=\"_blank\">".$oDataSource->GetName()."</a>"; 254 } 255 if ($oReplica->Get('status_dest_creator') == 1) 256 { 257 $oCreatorTask = $oDataSource; 258 $bCreatedByTask = true; 259 } 260 else 261 { 262 $bCreatedByTask = false; 263 } 264 if ($bCreatedByTask) 265 { 266 $sDeletePolicy = $oDataSource->Get('delete_policy'); 267 if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete')) 268 { 269 $bCanBeDeletedByTask = true; 270 } 271 $sUserDeletePolicy = $oDataSource->Get('user_delete_policy'); 272 if ($sUserDeletePolicy == 'nobody') 273 { 274 $bCanBeDeletedByUser = false; 275 } 276 elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator()) 277 { 278 $bCanBeDeletedByUser = false; 279 } 280 } 281 $aMasterSources[$iSourceId]['datasource'] = $oDataSource; 282 $aMasterSources[$iSourceId]['url'] = $sLink; 283 $aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen'); 284 } 285 286 if (is_object($oCreatorTask)) 287 { 288 $sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url']; 289 if (!$bCanBeDeletedByUser) 290 { 291 $sTip = "<p>".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', 292 $sTaskUrl)."</p>"; 293 } 294 else 295 { 296 $sTip = "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."</p>"; 297 } 298 if ($bCanBeDeletedByTask) 299 { 300 $sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."</p>"; 301 } 302 } 303 else 304 { 305 $sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>"; 306 } 307 308 $sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>"; 309 foreach($aMasterSources as $aStruct) 310 { 311 // Formatting last synchro date 312 $oDateTime = DateTime::createFromFormat('Y-m-d H:i:s', $aStruct['last_synchro']); 313 $oDateTimeFormat = AttributeDateTime::GetFormat(); 314 $sLastSynchro = $oDateTimeFormat->Format($oDateTime); 315 316 $oDataSource = $aStruct['datasource']; 317 $sLink = $aStruct['url']; 318 $sTip .= "<p style=\"white-space:nowrap\">".$oDataSource->GetIcon(true, 319 'style="vertical-align:middle"')." $sLink<br/>"; 320 $sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$sLastSynchro."</p>"; 321 } 322 $sLabel = htmlentities(Dict::S('Tag:Synchronized'), ENT_QUOTES, 'UTF-8'); 323 $sSynchroTagId = 'synchro_icon-'.$this->GetKey(); 324 $aIcons[] = "<div class=\"tag\" id=\"$sSynchroTagId\"><span class=\"object-synchronized fa fa-lock fa-1x\"> </span> $sLabel</div>"; 325 $sTip = addslashes($sTip); 326 $oPage->add_ready_script("$('#$sSynchroTagId').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'topLeft' }, position: { corner: { target: 'bottomMiddle', tooltip: 'topLeft' }} } );"); 327 } 328 } 329 330 if ($this->IsArchived()) 331 { 332 $sLabel = htmlentities(Dict::S('Tag:Archived'), ENT_QUOTES, 'UTF-8'); 333 $sTitle = htmlentities(Dict::S('Tag:Archived+'), ENT_QUOTES, 'UTF-8'); 334 $aIcons[] = "<div class=\"tag\" title=\"$sTitle\"><span class=\"object-archived fa fa-archive fa-1x\"> </span> $sLabel</div>"; 335 } 336 elseif ($this->IsObsolete()) 337 { 338 $sLabel = htmlentities(Dict::S('Tag:Obsolete'), ENT_QUOTES, 'UTF-8'); 339 $sTitle = htmlentities(Dict::S('Tag:Obsolete+'), ENT_QUOTES, 'UTF-8'); 340 $aIcons[] = "<div class=\"tag\" title=\"$sTitle\"><span class=\"object-obsolete fa fa-eye-slash fa-1x\"> </span> $sLabel</div>"; 341 } 342 343 $sObjectIcon = $this->GetIcon(); 344 $sClassName = MetaModel::GetName(get_class($this)); 345 $sObjectName = $this->GetName(); 346 if (count($aIcons) > 0) 347 { 348 $sTags = '<div class="tags">'.implode(' ', $aIcons).'</div>'; 349 } 350 else 351 { 352 $sTags = ''; 353 } 354 355 $oPage->add( 356 <<<EOF 357<div class="page_header"> 358 <div class="object-details-header"> 359 <div class ="object-icon">$sObjectIcon</div> 360 <div class ="object-infos"> 361 <h1 class="object-name">$sClassName: <span class="hilite">$sObjectName</span></h1> 362 $sTags 363 </div> 364 </div> 365</div> 366EOF 367 ); 368 } 369 370 function DisplayBareHistory(WebPage $oPage, $bEditMode = false, $iLimitCount = 0, $iLimitStart = 0) 371 { 372 // history block (with as a tab) 373 $oHistoryFilter = new DBObjectSearch('CMDBChangeOp'); 374 $oHistoryFilter->AddCondition('objkey', $this->GetKey(), '='); 375 $oHistoryFilter->AddCondition('objclass', get_class($this), '='); 376 $oBlock = new HistoryBlock($oHistoryFilter, 'table', false); 377 $oBlock->SetLimit($iLimitCount, $iLimitStart); 378 $oBlock->Display($oPage, 'history'); 379 } 380 381 function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array()) 382 { 383 $aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); 384 385 386 if (!isset($aExtraParams['disable_plugins']) || !$aExtraParams['disable_plugins']) 387 { 388 foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) 389 { 390 $oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode); 391 } 392 } 393 394 // Special case to display the case log, if any... 395 // WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayModifyForm 396 foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) 397 { 398 if ($oAttDef instanceof AttributeCaseLog) 399 { 400 $sComment = (isset($aExtraParams['fieldsComments'][$sAttCode])) ? $aExtraParams['fieldsComments'][$sAttCode] : ''; 401 $this->DisplayCaseLog($oPage, $sAttCode, $sComment, $sPrefix, $bEditMode); 402 $aFieldsMap[$sAttCode] = $this->m_iFormId.'_'.$sAttCode; 403 } 404 } 405 406 return $aFieldsMap; 407 } 408 409 /** 410 * Add a field to the map: attcode => id used when building a form 411 * 412 * @param string $sAttCode The attribute code of the field being edited 413 * @param string $sInputId The unique ID of the control/widget in the page 414 */ 415 protected function AddToFieldsMap($sAttCode, $sInputId) 416 { 417 $this->aFieldsMap[$sAttCode] = $sInputId; 418 } 419 420 /** 421 * @param \iTopWebPage $oPage 422 * @param $sAttCode 423 * 424 * @throws \Exception 425 */ 426 public function DisplayDashboard($oPage, $sAttCode) 427 { 428 $sClass = get_class($this); 429 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 430 431 if (!$oAttDef instanceof AttributeDashboard) 432 { 433 throw new CoreException(Dict::S('UI:Error:InvalidDashboard')); 434 } 435 436 // Load the dashboard 437 $oDashboard = $oAttDef->GetDashboard(); 438 if (is_null($oDashboard)) 439 { 440 throw new CoreException(Dict::S('UI:Error:InvalidDashboard')); 441 } 442 443 $bCanEdit = UserRights::IsAdministrator() || $oAttDef->IsUserEditable(); 444 $sDivId = $oDashboard->GetId(); 445 $oPage->add('<div class="dashboard_contents" id="'.$sDivId.'">'); 446 $aExtraParams = array('query_params' => $this->ToArgsForQuery()); 447 $oDashboard->Render($oPage, false, $aExtraParams, $bCanEdit); 448 $oPage->add('</div>'); 449 } 450 451 /** 452 * @param \WebPage $oPage 453 * @param bool $bEditMode 454 * 455 * @throws \CoreException 456 * @throws \CoreUnexpectedValue 457 * @throws \DictExceptionMissingString 458 * @throws \MissingQueryArgument 459 * @throws \MySQLException 460 * @throws \MySQLHasGoneAwayException 461 * @throws \OQLException 462 */ 463 function DisplayBareRelations(WebPage $oPage, $bEditMode = false) 464 { 465 $aRedundancySettings = $this->FindVisibleRedundancySettings(); 466 467 // Related objects: display all the linkset attributes, each as a separate tab 468 // In the order described by the 'display' ZList 469 $aList = $this->FlattenZList(MetaModel::GetZListItems(get_class($this), 'details')); 470 if (count($aList) == 0) 471 { 472 // Empty ZList defined, display all the linkedset attributes defined 473 $aList = array_keys(MetaModel::ListAttributeDefs(get_class($this))); 474 } 475 $sClass = get_class($this); 476 foreach($aList as $sAttCode) 477 { 478 $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); 479 if ($oAttDef instanceof AttributeDashboard) 480 { 481 if ($bEditMode) 482 { 483 continue; 484 } 485 $oPage->AddAjaxTab($oAttDef->GetLabel(), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=dashboard&class='.get_class($this).'&id='.$this->GetKey().'&attcode='.$oAttDef->GetCode()); 486 continue; 487 } 488 489 // Display mode 490 if (!$oAttDef->IsLinkset()) 491 { 492 continue; 493 } // Process only linkset attributes... 494 495 $sLinkedClass = $oAttDef->GetLinkedClass(); 496 497 // Filter out links pointing to obsolete objects (if relevant) 498 $oOrmLinkSet = $this->Get($sAttCode); 499 $oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData()); 500 501 $iCount = $oLinkSet->Count(); 502 $sCount = ''; 503 if ($iCount != 0) 504 { 505 $sCount = " ($iCount)"; 506 } 507 $oPage->SetCurrentTab($oAttDef->GetLabel().$sCount); 508 if ($this->IsNew()) 509 { 510 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 511 } 512 else 513 { 514 $iFlags = $this->GetAttributeFlags($sAttCode); 515 } 516 // Adjust the flags according to user rights 517 if ($oAttDef->IsIndirect()) 518 { 519 $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); 520 $sTargetClass = $oLinkingAttDef->GetTargetClass(); 521 // n:n links => must be allowed to modify the linking class AND read the target class in order to edit the linkedset 522 if (!UserRights::IsActionAllowed($sLinkedClass, 523 UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) 524 { 525 $iFlags |= OPT_ATT_READONLY; 526 } 527 // n:n links => must be allowed to read the linking class AND the target class in order to display the linkedset 528 if (!UserRights::IsActionAllowed($sLinkedClass, 529 UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) 530 { 531 $iFlags |= OPT_ATT_HIDDEN; 532 } 533 } 534 else 535 { 536 // 1:n links => must be allowed to modify the linked class in order to edit the linkedset 537 if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_MODIFY)) 538 { 539 $iFlags |= OPT_ATT_READONLY; 540 } 541 // 1:n links => must be allowed to read the linked class in order to display the linkedset 542 if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_READ)) 543 { 544 $iFlags |= OPT_ATT_HIDDEN; 545 } 546 } 547 // Non-readable/hidden linkedset... don't display anything 548 if ($iFlags & OPT_ATT_HIDDEN) 549 { 550 continue; 551 } 552 553 $aArgs = array('this' => $this); 554 $bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); 555 if ($bEditMode && (!$bReadOnly)) 556 { 557 $sInputId = $this->m_iFormId.'_'.$sAttCode; 558 559 if ($oAttDef->IsIndirect()) 560 { 561 $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); 562 $sTargetClass = $oLinkingAttDef->GetTargetClass(); 563 } 564 else 565 { 566 $sTargetClass = $sLinkedClass; 567 } 568 $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription().'<span id="busy_'.$sInputId.'"></span>'); 569 570 $sDisplayValue = ''; // not used 571 $sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, 572 $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>'; 573 $this->AddToFieldsMap($sAttCode, $sInputId); 574 $oPage->add($sHTMLValue); 575 } 576 else 577 { 578 // Display mode 579 if (!$oAttDef->IsIndirect()) 580 { 581 // 1:n links 582 $sTargetClass = $sLinkedClass; 583 584 $aDefaults = array($oAttDef->GetExtKeyToMe() => $this->GetKey()); 585 $oAppContext = new ApplicationContext(); 586 foreach($oAppContext->GetNames() as $sKey) 587 { 588 // The linked object inherits the parent's value for the context 589 if (MetaModel::IsValidAttCode($sClass, $sKey)) 590 { 591 $aDefaults[$sKey] = $this->Get($sKey); 592 } 593 } 594 $aParams = array( 595 'target_attr' => $oAttDef->GetExtKeyToMe(), 596 'object_id' => $this->GetKey(), 597 'menu' => MetaModel::GetConfig()->Get('allow_menu_on_linkset'), 598 //'menu_actions_target' => '_blank', 599 'default' => $aDefaults, 600 'table_id' => $sClass.'_'.$sAttCode, 601 ); 602 } 603 else 604 { 605 // n:n links 606 $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); 607 $sTargetClass = $oLinkingAttDef->GetTargetClass(); 608 $aParams = array( 609 'link_attr' => $oAttDef->GetExtKeyToMe(), 610 'object_id' => $this->GetKey(), 611 'target_attr' => $oAttDef->GetExtKeyToRemote(), 612 'view_link' => false, 613 'menu' => false, 614 //'menu_actions_target' => '_blank', 615 'display_limit' => true, // By default limit the list to speed up the initial load & display 616 'table_id' => $sClass.'_'.$sAttCode, 617 ); 618 } 619 $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); 620 $oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false); 621 $oBlock->Display($oPage, 'rel_'.$sAttCode, $aParams); 622 } 623 if (array_key_exists($sAttCode, $aRedundancySettings)) 624 { 625 foreach($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) 626 { 627 $sRedundancyAttCode = $oRedundancyAttDef->GetCode(); 628 $sValue = $this->Get($sRedundancyAttCode); 629 $iRedundancyFlags = $this->GetFormAttributeFlags($sRedundancyAttCode); 630 $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); 631 632 $oPage->add('<fieldset>'); 633 $oPage->add('<legend>'.$oRedundancyAttDef->GetLabel().'</legend>'); 634 if ($bEditMode && (!$bRedundancyReadOnly)) 635 { 636 $sInputId = $this->m_iFormId.'_'.$sRedundancyAttCode; 637 $oPage->add("<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, 638 $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, 639 $aArgs).'</span>'); 640 } 641 else 642 { 643 $oPage->add($oRedundancyAttDef->GetDisplayForm($sValue, $oPage, false, $this->m_iFormId)); 644 } 645 $oPage->add('</fieldset>'); 646 } 647 } 648 } 649 $oPage->SetCurrentTab(''); 650 651 foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) 652 { 653 $oExtensionInstance->OnDisplayRelations($this, $oPage, $bEditMode); 654 } 655 656 // Display Notifications after the other tabs since this tab disappears in edition 657 if (!$bEditMode) 658 { 659 // Look for any trigger that considers this object as "In Scope" 660 // If any trigger has been found then display a tab with notifications 661 // 662 $oTriggerSet = new CMDBObjectSet(new DBObjectSearch('Trigger')); 663 $aTriggers = array(); 664 while ($oTrigger = $oTriggerSet->Fetch()) 665 { 666 if ($oTrigger->IsInScope($this)) 667 { 668 $aTriggers[] = $oTrigger->GetKey(); 669 } 670 } 671 if (count($aTriggers) > 0) 672 { 673 $iId = $this->GetKey(); 674 $sTriggersList = implode(',', $aTriggers); 675 $aNotifSearches = array(); 676 $iNotifsCount = 0; 677 $aNotificationClasses = MetaModel::EnumChildClasses('EventNotification', ENUM_CHILD_CLASSES_EXCLUDETOP); 678 foreach($aNotificationClasses as $sNotifClass) 679 { 680 $aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN ($sTriggersList) AND Ev.object_id = $iId"); 681 $oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass]); 682 $iNotifsCount += $oNotifSet->Count(); 683 } 684 // Display notifications regarding the object: on block per subclass to have the intersting columns 685 $sCount = ($iNotifsCount > 0) ? ' ('.$iNotifsCount.')' : ''; 686 $oPage->SetCurrentTab(Dict::S('UI:NotificationsTab').$sCount); 687 688 foreach($aNotificationClasses as $sNotifClass) 689 { 690 $oPage->p(MetaModel::GetClassIcon($sNotifClass, true).' '.MetaModel::GetName($sNotifClass)); 691 $oBlock = new DisplayBlock($aNotifSearches[$sNotifClass], 'list', false); 692 $oBlock->Display($oPage, 'notifications_'.$sNotifClass, array('menu' => false)); 693 } 694 } 695 } 696 } 697 698 function GetBareProperties(WebPage $oPage, $bEditMode, $sPrefix, $aExtraParams = array()) 699 { 700 $sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this)); 701 $sClass = get_class($this); 702 $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); 703 $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', 'col1', ''); 704 // Compute the list of properties to display, first the attributes in the 'details' list, then 705 // all the remaining attributes that are not external fields 706 $sEditMode = ($bEditMode) ? 'edit' : 'view'; 707 $aDetails = array(); 708 $iInputId = 0; 709 $aFieldsMap = array(); 710 $aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array(); 711 $aExtraFlags = (isset($aExtraParams['fieldsFlags'])) ? $aExtraParams['fieldsFlags'] : array(); 712 713 foreach($aDetailsStruct as $sTab => $aCols) 714 { 715 $aDetails[$sTab] = array(); 716 $aTableStyles[] = 'vertical-align:top'; 717 $aTableClasses = array(); 718 $aColStyles[] = 'vertical-align:top'; 719 $aColClasses = array(); 720 721 ksort($aCols); 722 $iColCount = count($aCols); 723 if ($iColCount > 1) 724 { 725 $aTableClasses[] = 'n-cols-details'; 726 $aTableClasses[] = $iColCount.'-cols-details'; 727 728 $aColStyles[] = 'width:'.floor(100 / $iColCount).'%'; 729 } 730 else 731 { 732 $aTableClasses[] = 'one-col-details'; 733 } 734 735 $oPage->SetCurrentTab(Dict::S($sTab)); 736 $oPage->add('<table style="'.implode('; ', $aTableStyles).'" class="'.implode(' ', 737 $aTableClasses).'" data-mode="'.$sEditMode.'"><tr>'); 738 foreach($aCols as $sColIndex => $aFieldsets) 739 { 740 $oPage->add('<td style="'.implode('; ', $aColStyles).'" class="'.implode(' ', $aColClasses).'">'); 741 $sPreviousLabel = ''; 742 $aDetails[$sTab][$sColIndex] = array(); 743 foreach($aFieldsets as $sFieldsetName => $aFields) 744 { 745 if (!empty($sFieldsetName) && ($sFieldsetName[0] != '_')) 746 { 747 $sLabel = $sFieldsetName; 748 } 749 else 750 { 751 $sLabel = ''; 752 } 753 if ($sLabel != $sPreviousLabel) 754 { 755 if (!empty($sPreviousLabel)) 756 { 757 $oPage->add('<fieldset>'); 758 $oPage->add('<legend>'.Dict::S($sPreviousLabel).'</legend>'); 759 } 760 $oPage->Details($aDetails[$sTab][$sColIndex]); 761 if (!empty($sPreviousLabel)) 762 { 763 $oPage->add('</fieldset>'); 764 } 765 $aDetails[$sTab][$sColIndex] = array(); 766 $sPreviousLabel = $sLabel; 767 } 768 foreach($aFields as $sAttCode) 769 { 770 if ($bEditMode) 771 { 772 $sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ''; 773 $sInfos = ''; 774 $iFlags = $this->GetFormAttributeFlags($sAttCode); 775 if (array_key_exists($sAttCode, $aExtraFlags)) 776 { 777 // the caller may override some flags if needed 778 $iFlags = $iFlags | $aExtraFlags[$sAttCode]; 779 } 780 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 781 if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) 782 { 783 $sInputId = $this->m_iFormId.'_'.$sAttCode; 784 if ($oAttDef->IsWritable()) 785 { 786 if ($sStateAttCode == $sAttCode) 787 { 788 // State attribute is always read-only from the UI 789 $sHTMLValue = $this->GetStateLabel(); 790 $val = array( 791 'label' => '<label>'.$oAttDef->GetLabel().'</label>', 792 'value' => $sHTMLValue, 793 'comments' => $sComments, 794 'infos' => $sInfos, 795 'attcode' => $sAttCode, 796 ); 797 } 798 else 799 { 800 if ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) 801 { 802 // Check if the attribute is not read-only because of a synchro... 803 if ($iFlags & OPT_ATT_SLAVE) 804 { 805 $aReasons = array(); 806 $this->GetSynchroReplicaFlags($sAttCode, $aReasons); 807 $sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>"; 808 $sTip = ''; 809 foreach($aReasons as $aRow) 810 { 811 $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 812 'UTF-8'); 813 $sDescription = str_replace(array("\r\n", "\n"), "<br/>", 814 $sDescription); 815 $sTip .= "<div class='synchro-source'>"; 816 $sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>"; 817 $sTip .= "<div class='synchro-source-description'>$sDescription</div>"; 818 } 819 $sTip = addslashes($sTip); 820 $oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"); 821 $sComments = $sSynchroIcon; 822 } 823 824 // Attribute is read-only 825 $sHTMLValue = "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode).'</span>'; 826 } 827 else 828 { 829 $sValue = $this->Get($sAttCode); 830 $sDisplayValue = $this->GetEditValue($sAttCode); 831 $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); 832 $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, 833 $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, 834 $aArgs).''; 835 } 836 $aFieldsMap[$sAttCode] = $sInputId; 837 $val = array( 838 'label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 839 'value' => $sHTMLValue, 840 'comments' => $sComments, 841 'infos' => $sInfos, 842 'attcode' => $sAttCode, 843 ); 844 } 845 } 846 else 847 { 848 $val = array( 849 'label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 850 'value' => "<span id=\"field_{$sInputId}\">".$this->GetAsHTML($sAttCode)."</span>", 851 'comments' => $sComments, 852 'infos' => $sInfos, 853 'attcode' => $sAttCode, 854 ); 855 $aFieldsMap[$sAttCode] = $sInputId; 856 } 857 858 // Checking how the field should be rendered 859 // Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml() 860 // Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain? 861 if (in_array($oAttDef->GetEditClass(), 862 array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) 863 { 864 $val['layout'] = 'large'; 865 } 866 else 867 { 868 $val['layout'] = 'small'; 869 } 870 } 871 else 872 { 873 $val = null; // Skip this field 874 } 875 876 } 877 else 878 { 879 // !bEditMode 880 $val = $this->GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode); 881 } 882 883 if ($val != null) 884 { 885 // The field is visible, add it to the current column 886 $aDetails[$sTab][$sColIndex][] = $val; 887 $iInputId++; 888 } 889 } 890 } 891 if (!empty($sPreviousLabel)) 892 { 893 $oPage->add('<fieldset>'); 894 $oPage->add('<legend>'.Dict::S($sFieldsetName).'</legend>'); 895 } 896 $oPage->Details($aDetails[$sTab][$sColIndex]); 897 if (!empty($sPreviousLabel)) 898 { 899 $oPage->add('</fieldset>'); 900 } 901 $oPage->add('</td>'); 902 } 903 $oPage->add('</tr></table>'); 904 } 905 906 return $aFieldsMap; 907 } 908 909 910 /** 911 * @param \iTopWebPage $oPage 912 * @param bool $bEditMode 913 * 914 * @throws \CoreException 915 * @throws \CoreUnexpectedValue 916 * @throws \DictExceptionMissingString 917 * @throws \MissingQueryArgument 918 * @throws \MySQLException 919 * @throws \MySQLHasGoneAwayException 920 * @throws \OQLException 921 */ 922 function DisplayDetails(WebPage $oPage, $bEditMode = false) 923 { 924 $sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate(get_class($this))); 925 if (!empty($sTemplate)) 926 { 927 $oTemplate = new DisplayTemplate($sTemplate); 928 // Note: to preserve backward compatibility with home-made templates, the placeholder '$pkey$' has been preserved 929 // but the preferred method is to use '$id$' 930 $oTemplate->Render($oPage, array( 931 'class_name' => MetaModel::GetName(get_class($this)), 932 'class' => get_class($this), 933 'pkey' => $this->GetKey(), 934 'id' => $this->GetKey(), 935 'name' => $this->GetName(), 936 )); 937 } 938 else 939 { 940 // Object's details 941 // template not found display the object using the *old style* 942 $oPage->add('<div id="search-widget-results-outer">'); 943 $this->DisplayBareHeader($oPage, $bEditMode); 944 $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB); 945 $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); 946 $oPage->SetCurrentTab(Dict::S('UI:PropertiesTab')); 947 $this->DisplayBareProperties($oPage, $bEditMode); 948 $this->DisplayBareRelations($oPage, $bEditMode); 949 //$oPage->SetCurrentTab(Dict::S('UI:HistoryTab')); 950 //$this->DisplayBareHistory($oPage, $bEditMode); 951 $oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), 952 utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey()); 953 $oPage->add('</div>'); 954 } 955 } 956 957 function DisplayPreview(WebPage $oPage) 958 { 959 $aDetails = array(); 960 $sClass = get_class($this); 961 $aList = MetaModel::GetZListItems($sClass, 'preview'); 962 foreach($aList as $sAttCode) 963 { 964 $aDetails[] = array( 965 'label' => MetaModel::GetLabel($sClass, $sAttCode), 966 'value' => $this->GetAsHTML($sAttCode), 967 ); 968 } 969 $oPage->details($aDetails); 970 } 971 972 public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) 973 { 974 $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); 975 } 976 977 /** 978 * Simplified version of GetDisplaySet() with less "decoration" around the table (and no paging) 979 * that fits better into a printed document (like a PDF or a printable view) 980 * 981 * @param WebPage $oPage 982 * @param DBObjectSet $oSet 983 * @param array $aExtraParams 984 * 985 * @return string The HTML representation of the table 986 * @throws \CoreException 987 */ 988 public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) 989 { 990 $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; 991 992 $bViewLink = true; 993 $sSelectMode = 'none'; 994 $iListId = $sTableId; 995 $sClassAlias = $oSet->GetClassAlias(); 996 $sClassName = $oSet->GetClass(); 997 $sZListName = 'list'; 998 $aClassAliases = array($sClassAlias => $sClassName); 999 $aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); 1000 1001 $oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId); 1002 $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); 1003 $oSettings->iDefaultPageSize = 0; 1004 $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); 1005 1006 return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, 1007 $aExtraParams); 1008 1009 } 1010 1011 /** 1012 * Get the HTML fragment corresponding to the display of a table representing a set of objects 1013 * 1014 * @param WebPage $oPage The page object is used for out-of-band information (mostly scripts) output 1015 * @param CMDBObjectSet The set of objects to display 1016 * @param array $aExtraParams Some extra configuration parameters to tweak the behavior of the display 1017 * 1018 * @return String The HTML fragment representing the table of objects. <b>Warning</b> : no JS added to handled 1019 * pagination or table sorting ! 1020 * 1021 * @throws \ApplicationException 1022 * @throws \CoreException 1023 * @see DisplayBlock to get a similar table but with the JS for pagination & sorting 1024 */ 1025 public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) 1026 { 1027 if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) 1028 { 1029 return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams); 1030 } 1031 1032 if (empty($aExtraParams['currentId'])) 1033 { 1034 $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! 1035 } 1036 else 1037 { 1038 $iListId = $aExtraParams['currentId']; 1039 } 1040 1041 // Initialize and check the parameters 1042 $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; 1043 $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; 1044 $iLinkedObjectId = isset($aExtraParams['object_id']) ? $aExtraParams['object_id'] : 0; 1045 $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; 1046 if (!empty($sLinkageAttribute)) 1047 { 1048 if ($iLinkedObjectId == 0) 1049 { 1050 // if 'links' mode is requested the id of the object to link to must be specified 1051 throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); 1052 } 1053 if ($sTargetAttr == '') 1054 { 1055 // if 'links' mode is requested the d of the object to link to must be specified 1056 throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); 1057 } 1058 } 1059 $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; 1060 $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; 1061 $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; 1062 1063 $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', 1064 trim($aExtraParams['extra_fields'])) : array(); 1065 $aExtraFields = array(); 1066 foreach($aExtraFieldsRaw as $sFieldName) 1067 { 1068 // Ignore attributes not of the main queried class 1069 if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) 1070 { 1071 $sClassAlias = $aMatches[1]; 1072 $sAttCode = $aMatches[2]; 1073 if ($sClassAlias == $oSet->GetFilter()->GetClassAlias()) 1074 { 1075 $aExtraFields[] = $sAttCode; 1076 } 1077 } 1078 else 1079 { 1080 $aExtraFields[] = $sFieldName; 1081 } 1082 } 1083 $sClassName = $oSet->GetFilter()->GetClass(); 1084 $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; 1085 if ($sZListName !== false) 1086 { 1087 $aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); 1088 $aList = array_merge($aList, $aExtraFields); 1089 } 1090 else 1091 { 1092 $aList = $aExtraFields; 1093 } 1094 1095 // Filter the list to removed linked set since we are not able to display them here 1096 foreach($aList as $index => $sAttCode) 1097 { 1098 $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); 1099 if ($oAttDef instanceof AttributeLinkedSet) 1100 { 1101 // Removed from the display list 1102 unset($aList[$index]); 1103 } 1104 } 1105 1106 1107 if (!empty($sLinkageAttribute)) 1108 { 1109 // The set to display is in fact a set of links between the object specified in the $sLinkageAttribute 1110 // and other objects... 1111 // The display will then group all the attributes related to the link itself: 1112 // | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n | 1113 $aDisplayList = array(); 1114 $aAttDefs = MetaModel::ListAttributeDefs($sClassName); 1115 assert(isset($aAttDefs[$sLinkageAttribute])); 1116 $oAttDef = $aAttDefs[$sLinkageAttribute]; 1117 assert($oAttDef->IsExternalKey()); 1118 // First display all the attributes specific to the link record 1119 foreach($aList as $sLinkAttCode) 1120 { 1121 $oLinkAttDef = $aAttDefs[$sLinkAttCode]; 1122 if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) 1123 { 1124 $aDisplayList[] = $sLinkAttCode; 1125 } 1126 } 1127 // Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant) 1128 foreach($aList as $sLinkAttCode) 1129 { 1130 $oLinkAttDef = $aAttDefs[$sLinkAttCode]; 1131 if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) 1132 || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) 1133 { 1134 $aDisplayList[] = $sLinkAttCode; 1135 } 1136 } 1137 // First display all the attributes specific to the link 1138 // Then display all the attributes linked to the other end of the relationship 1139 $aList = $aDisplayList; 1140 } 1141 1142 $sSelectMode = 'none'; 1143 if ($bSelectMode) 1144 { 1145 $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; 1146 } 1147 1148 $sClassAlias = $oSet->GetClassAlias(); 1149 $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; 1150 1151 $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; 1152 $aClassAliases = array($sClassAlias => $sClassName); 1153 $oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId); 1154 $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); 1155 1156 if ($bDisplayLimit) 1157 { 1158 $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', 1159 MetaModel::GetConfig()->GetMinDisplayLimit()); 1160 $oSettings->iDefaultPageSize = $iDefaultPageSize; 1161 } 1162 else 1163 { 1164 $oSettings->iDefaultPageSize = 0; 1165 } 1166 $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); 1167 1168 return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); 1169 } 1170 1171 public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) 1172 { 1173 if (empty($aExtraParams['currentId'])) 1174 { 1175 $iListId = $oPage->GetUniqueId(); // Works only if not in an Ajax page !! 1176 } 1177 else 1178 { 1179 $iListId = $aExtraParams['currentId']; 1180 } 1181 $aList = array(); 1182 1183 // Initialize and check the parameters 1184 $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; 1185 $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; 1186 // Check if there is a list of aliases to limit the display to... 1187 $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', 1188 $aExtraParams['display_aliases']) : array(); 1189 $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; 1190 1191 $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', 1192 trim($aExtraParams['extra_fields'])) : array(); 1193 $aExtraFields = array(); 1194 $sAttCode = ''; 1195 foreach($aExtraFieldsRaw as $sFieldName) 1196 { 1197 // Ignore attributes not of the main queried class 1198 if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) 1199 { 1200 $sClassAlias = $aMatches[1]; 1201 $sAttCode = $aMatches[2]; 1202 if (array_key_exists($sClassAlias, $oSet->GetSelectedClasses())) 1203 { 1204 $aExtraFields[$sClassAlias][] = $sAttCode; 1205 } 1206 } 1207 else 1208 { 1209 $aExtraFields['*'] = $sAttCode; 1210 } 1211 } 1212 1213 $aClasses = $oSet->GetFilter()->GetSelectedClasses(); 1214 $aAuthorizedClasses = array(); 1215 foreach($aClasses as $sAlias => $sClassName) 1216 { 1217 if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && 1218 ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) 1219 { 1220 $aAuthorizedClasses[$sAlias] = $sClassName; 1221 } 1222 } 1223 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1224 { 1225 if (array_key_exists($sAlias, $aExtraFields)) 1226 { 1227 $aList[$sAlias] = $aExtraFields[$sAlias]; 1228 } 1229 else 1230 { 1231 $aList[$sAlias] = array(); 1232 } 1233 if ($sZListName !== false) 1234 { 1235 $aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); 1236 1237 $aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]); 1238 } 1239 1240 // Filter the list to removed linked set since we are not able to display them here 1241 foreach($aList[$sAlias] as $index => $sAttCode) 1242 { 1243 $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode); 1244 if ($oAttDef instanceof AttributeLinkedSet) 1245 { 1246 // Removed from the display list 1247 unset($aList[$sAlias][$index]); 1248 } 1249 } 1250 } 1251 1252 $sSelectMode = 'none'; 1253 1254 $oDataTable = new DataTable($iListId, $oSet, $aAuthorizedClasses); 1255 1256 $oSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList); 1257 1258 $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; 1259 if ($bDisplayLimit) 1260 { 1261 $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', 1262 MetaModel::GetConfig()->GetMinDisplayLimit()); 1263 $oSettings->iDefaultPageSize = $iDefaultPageSize; 1264 } 1265 1266 $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); 1267 1268 return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); 1269 } 1270 1271 static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') 1272 { 1273 $oPage->add(self::GetSetAsCSV($oSet, $aParams, $sCharset)); 1274 } 1275 1276 static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') 1277 { 1278 $sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma 1279 $sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote 1280 $aFields = null; 1281 if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) 1282 { 1283 $aFields = explode(',', $aParams['fields']); 1284 } 1285 1286 $bFieldsAdvanced = false; 1287 if (isset($aParams['fields_advanced'])) 1288 { 1289 $bFieldsAdvanced = (bool)$aParams['fields_advanced']; 1290 } 1291 1292 $bLocalize = true; 1293 if (isset($aParams['localize_values'])) 1294 { 1295 $bLocalize = (bool)$aParams['localize_values']; 1296 } 1297 1298 $aList = array(); 1299 1300 $aClasses = $oSet->GetFilter()->GetSelectedClasses(); 1301 $aAuthorizedClasses = array(); 1302 foreach($aClasses as $sAlias => $sClassName) 1303 { 1304 if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) 1305 { 1306 $aAuthorizedClasses[$sAlias] = $sClassName; 1307 } 1308 } 1309 $aHeader = array(); 1310 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1311 { 1312 $aList[$sAlias] = array(); 1313 1314 foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) 1315 { 1316 if (is_null($aFields) || (count($aFields) == 0)) 1317 { 1318 // Standard list of attributes (no link sets) 1319 if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) 1320 { 1321 $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; 1322 1323 if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) 1324 { 1325 if ($bFieldsAdvanced) 1326 { 1327 $aList[$sAlias][$sAttCodeEx] = $oAttDef; 1328 1329 if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) 1330 { 1331 $sRemoteClass = $oAttDef->GetTargetClass(); 1332 foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) 1333 { 1334 $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, 1335 $sRemoteAttCode); 1336 } 1337 } 1338 } 1339 } 1340 else 1341 { 1342 // Any other attribute 1343 $aList[$sAlias][$sAttCodeEx] = $oAttDef; 1344 } 1345 } 1346 } 1347 else 1348 { 1349 // User defined list of attributes 1350 if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) 1351 { 1352 $aList[$sAlias][$sAttCode] = $oAttDef; 1353 } 1354 } 1355 } 1356 if ($bFieldsAdvanced) 1357 { 1358 $aHeader[] = 'id'; 1359 } 1360 foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) 1361 { 1362 $aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, 1363 isset($aParams['showMandatoryFields'])) : $sAttCodeEx; 1364 } 1365 } 1366 $sHtml = implode($sSeparator, $aHeader)."\n"; 1367 $oSet->Seek(0); 1368 while ($aObjects = $oSet->FetchAssoc()) 1369 { 1370 $aRow = array(); 1371 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1372 { 1373 $oObj = $aObjects[$sAlias]; 1374 if ($bFieldsAdvanced) 1375 { 1376 if (is_null($oObj)) 1377 { 1378 $aRow[] = ''; 1379 } 1380 else 1381 { 1382 $aRow[] = $oObj->GetKey(); 1383 } 1384 } 1385 foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) 1386 { 1387 if (is_null($oObj)) 1388 { 1389 $aRow[] = ''; 1390 } 1391 else 1392 { 1393 $value = $oObj->Get($sAttCodeEx); 1394 $sCSVValue = $oAttDef->GetAsCSV($value, $sSeparator, $sTextQualifier, $oObj, $bLocalize); 1395 $aRow[] = iconv('UTF-8', $sCharset.'//IGNORE//TRANSLIT', $sCSVValue); 1396 } 1397 } 1398 } 1399 $sHtml .= implode($sSeparator, $aRow)."\n"; 1400 } 1401 1402 return $sHtml; 1403 } 1404 1405 static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) 1406 { 1407 $oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams)); 1408 } 1409 1410 /** 1411 * Spreadsheet output: designed for end users doing some reporting 1412 * Then the ids are excluded and replaced by the corresponding friendlyname 1413 */ 1414 static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array()) 1415 { 1416 $aFields = null; 1417 if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) 1418 { 1419 $aFields = explode(',', $aParams['fields']); 1420 } 1421 1422 $bFieldsAdvanced = false; 1423 if (isset($aParams['fields_advanced'])) 1424 { 1425 $bFieldsAdvanced = (bool)$aParams['fields_advanced']; 1426 } 1427 1428 $bLocalize = true; 1429 if (isset($aParams['localize_values'])) 1430 { 1431 $bLocalize = (bool)$aParams['localize_values']; 1432 } 1433 1434 $aList = array(); 1435 1436 $aClasses = $oSet->GetFilter()->GetSelectedClasses(); 1437 $aAuthorizedClasses = array(); 1438 foreach($aClasses as $sAlias => $sClassName) 1439 { 1440 if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) 1441 { 1442 $aAuthorizedClasses[$sAlias] = $sClassName; 1443 } 1444 } 1445 $aHeader = array(); 1446 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1447 { 1448 $aList[$sAlias] = array(); 1449 1450 foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) 1451 { 1452 if (is_null($aFields) || (count($aFields) == 0)) 1453 { 1454 // Standard list of attributes (no link sets) 1455 if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) 1456 { 1457 $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; 1458 1459 $aList[$sAlias][$sAttCodeEx] = $oAttDef; 1460 1461 if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) 1462 { 1463 $sRemoteClass = $oAttDef->GetTargetClass(); 1464 foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) 1465 { 1466 $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, 1467 $sRemoteAttCode); 1468 } 1469 } 1470 } 1471 } 1472 else 1473 { 1474 // User defined list of attributes 1475 if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) 1476 { 1477 $aList[$sAlias][$sAttCode] = $oAttDef; 1478 } 1479 } 1480 } 1481 // Replace external key by the corresponding friendly name (if not already in the list) 1482 foreach($aList[$sAlias] as $sAttCode => $oAttDef) 1483 { 1484 if ($oAttDef->IsExternalKey()) 1485 { 1486 unset($aList[$sAlias][$sAttCode]); 1487 $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; 1488 if (!array_key_exists($sFriendlyNameAttCode, 1489 $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) 1490 { 1491 $oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode); 1492 $aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt; 1493 } 1494 } 1495 } 1496 1497 foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) 1498 { 1499 $sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx; 1500 1501 $oFinalAttDef = $oAttDef->GetFinalAttDef(); 1502 if (get_class($oFinalAttDef) == 'AttributeDateTime') 1503 { 1504 $aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')'; 1505 $aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')'; 1506 } 1507 else 1508 { 1509 $aHeader[] = $sColLabel; 1510 } 1511 } 1512 } 1513 1514 1515 $sHtml = "<table border=\"1\">\n"; 1516 $sHtml .= "<tr>\n"; 1517 $sHtml .= "<td>".implode("</td><td>", $aHeader)."</td>\n"; 1518 $sHtml .= "</tr>\n"; 1519 $oSet->Seek(0); 1520 while ($aObjects = $oSet->FetchAssoc()) 1521 { 1522 $aRow = array(); 1523 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1524 { 1525 $oObj = $aObjects[$sAlias]; 1526 foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) 1527 { 1528 if (is_null($oObj)) 1529 { 1530 $aRow[] = '<td></td>'; 1531 } 1532 else 1533 { 1534 $oFinalAttDef = $oAttDef->GetFinalAttDef(); 1535 if (get_class($oFinalAttDef) == 'AttributeDateTime') 1536 { 1537 $sDate = $oObj->Get($sAttCodeEx); 1538 if ($sDate === null) 1539 { 1540 $aRow[] = '<td></td>'; 1541 $aRow[] = '<td></td>'; 1542 } 1543 else 1544 { 1545 $iDate = AttributeDateTime::GetAsUnixSeconds($sDate); 1546 $aRow[] = '<td>'.date('Y-m-d', 1547 $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports 1548 $aRow[] = '<td>'.date('H:i:s', 1549 $iDate).'</td>'; // Format kept as-is for 100% backward compatibility of the exports 1550 } 1551 } 1552 else 1553 { 1554 if ($oAttDef instanceof AttributeCaseLog) 1555 { 1556 $rawValue = $oObj->Get($sAttCodeEx); 1557 $outputValue = str_replace("\n", "<br/>", 1558 htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); 1559 // Trick for Excel: treat the content as text even if it begins with an equal sign 1560 $aRow[] = '<td x:str>'.$outputValue.'</td>'; 1561 } 1562 else 1563 { 1564 $rawValue = $oObj->Get($sAttCodeEx); 1565 // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings 1566 // let's fix this and make sure we render an empty string if the key == 0 1567 if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) 1568 { 1569 $sKeyAttCode = $oAttDef->GetKeyAttCode(); 1570 if ($oObj->Get($sKeyAttCode) == 0) 1571 { 1572 $rawValue = ''; 1573 } 1574 } 1575 if ($bLocalize) 1576 { 1577 $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 1578 'UTF-8'); 1579 } 1580 else 1581 { 1582 $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); 1583 } 1584 $aRow[] = '<td>'.$outputValue.'</td>'; 1585 } 1586 } 1587 } 1588 } 1589 } 1590 $sHtml .= implode("\n", $aRow); 1591 $sHtml .= "</tr>\n"; 1592 } 1593 $sHtml .= "</table>\n"; 1594 1595 return $sHtml; 1596 } 1597 1598 static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) 1599 { 1600 $bLocalize = true; 1601 if (isset($aParams['localize_values'])) 1602 { 1603 $bLocalize = (bool)$aParams['localize_values']; 1604 } 1605 1606 $aClasses = $oSet->GetFilter()->GetSelectedClasses(); 1607 $aAuthorizedClasses = array(); 1608 foreach($aClasses as $sAlias => $sClassName) 1609 { 1610 if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) 1611 { 1612 $aAuthorizedClasses[$sAlias] = $sClassName; 1613 } 1614 } 1615 $aList = array(); 1616 $aList[$sAlias] = MetaModel::GetZListItems($sClassName, 'details'); 1617 $oPage->add("<Set>\n"); 1618 $oSet->Seek(0); 1619 while ($aObjects = $oSet->FetchAssoc()) 1620 { 1621 if (count($aAuthorizedClasses) > 1) 1622 { 1623 $oPage->add("<Row>\n"); 1624 } 1625 foreach($aAuthorizedClasses as $sAlias => $sClassName) 1626 { 1627 $oObj = $aObjects[$sAlias]; 1628 if (is_null($oObj)) 1629 { 1630 $oPage->add("<$sClassName alias=\"$sAlias\" id=\"null\">\n"); 1631 } 1632 else 1633 { 1634 $sClassName = get_class($oObj); 1635 $oPage->add("<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n"); 1636 } 1637 foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) 1638 { 1639 if (is_null($oObj)) 1640 { 1641 $oPage->add("<$sAttCode>null</$sAttCode>\n"); 1642 } 1643 else 1644 { 1645 if ($oAttDef->IsWritable()) 1646 { 1647 if (!$oAttDef->IsLinkSet()) 1648 { 1649 $sValue = $oObj->GetAsXML($sAttCode, $bLocalize); 1650 $oPage->add("<$sAttCode>$sValue</$sAttCode>\n"); 1651 } 1652 } 1653 } 1654 } 1655 $oPage->add("</$sClassName>\n"); 1656 } 1657 if (count($aAuthorizedClasses) > 1) 1658 { 1659 $oPage->add("</Row>\n"); 1660 } 1661 } 1662 $oPage->add("</Set>\n"); 1663 } 1664 1665 public static function DisplaySearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) 1666 { 1667 1668 $oPage->add(self::GetSearchForm($oPage, $oSet, $aExtraParams)); 1669 } 1670 1671 /** 1672 * @param WebPage $oPage 1673 * @param CMDBObjectSet $oSet 1674 * @param array $aExtraParams 1675 * 1676 * @return string 1677 * @throws CoreException 1678 * @throws DictExceptionMissingString 1679 */ 1680 public static function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) 1681 { 1682 $oSearchForm = new \Combodo\iTop\Application\Search\SearchForm(); 1683 1684 return $oSearchForm->GetSearchForm($oPage, $oSet, $aExtraParams); 1685 } 1686 1687 /** 1688 * @param \iTopWebPage $oPage 1689 * @param string $sClass 1690 * @param string $sAttCode 1691 * @param \AttributeDefinition $oAttDef 1692 * @param string $value 1693 * @param string $sDisplayValue 1694 * @param string $iId 1695 * @param string $sNameSuffix 1696 * @param int $iFlags 1697 * @param array $aArgs 1698 * @param bool $bPreserveCurrentValue Preserve the current value even if not allowed 1699 * 1700 * @return string 1701 * @throws \ArchivedObjectException 1702 * @throws \CoreException 1703 * @throws \DictExceptionMissingString 1704 */ 1705 public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) 1706 { 1707 $sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : ''; 1708 $sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix; 1709 if ($sDisplayValue == '') 1710 { 1711 $sDisplayValue = $value; 1712 } 1713 1714 if (isset($aArgs[$sAttCode]) && empty($value)) 1715 { 1716 // default value passed by the context (either the app context of the operation) 1717 $value = $aArgs[$sAttCode]; 1718 } 1719 1720 if (!empty($iId)) 1721 { 1722 $iInputId = $iId; 1723 } 1724 else 1725 { 1726 $iInputId = $oPage->GetUniqueId(); 1727 } 1728 1729 $sHTMLValue = ''; 1730 if (!$oAttDef->IsExternalField()) 1731 { 1732 $bMandatory = 'false'; 1733 if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) 1734 { 1735 $bMandatory = 'true'; 1736 } 1737 $sValidationSpan = "<span class=\"form_validation\" id=\"v_{$iId}\"></span>"; 1738 $sReloadSpan = "<span class=\"field_status\" id=\"fstatus_{$iId}\"></span>"; 1739 $sHelpText = htmlentities($oAttDef->GetHelpOnEdition(), ENT_QUOTES, 'UTF-8'); 1740 1741 // mandatory field control vars 1742 $aEventsList = array(); // contains any native event (like change), plus 'validate' for the form submission 1743 $sNullValue = $oAttDef->GetNullValue(); // used for the ValidateField() call in js/forms-json-utils.js 1744 $sFieldToValidateId = $iId; // can be different than the displayed field (for example in TagSet) 1745 1746 switch ($oAttDef->GetEditClass()) 1747 { 1748 case 'Date': 1749 $aEventsList[] = 'validate'; 1750 $aEventsList[] = 'keyup'; 1751 $aEventsList[] = 'change'; 1752 $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), 1753 ENT_QUOTES, 'UTF-8').'"'; 1754 1755 $sHTMLValue = "<div class=\"field_input_zone field_input_date\"><input title=\"$sHelpText\" class=\"date-pick\" type=\"text\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, 1756 ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/></div>{$sValidationSpan}{$sReloadSpan}"; 1757 break; 1758 1759 case 'DateTime': 1760 $aEventsList[] = 'validate'; 1761 $aEventsList[] = 'keyup'; 1762 $aEventsList[] = 'change'; 1763 1764 $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), 1765 ENT_QUOTES, 'UTF-8').'"'; 1766 $sHTMLValue = "<div class=\"field_input_zone field_input_datetime\"><input title=\"$sHelpText\" class=\"datetime-pick\" type=\"text\" size=\"19\" $sPlaceholderValue name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, 1767 ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/></div>{$sValidationSpan}{$sReloadSpan}"; 1768 break; 1769 1770 case 'Duration': 1771 $aEventsList[] = 'validate'; 1772 $aEventsList[] = 'change'; 1773 $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); 1774 $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); 1775 $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); 1776 $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); 1777 $aVal = AttributeDuration::SplitDuration($value); 1778 $sDays = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"3\" name=\"attr_{$sFieldPrefix}{$sAttCode}[d]{$sNameSuffix}\" value=\"{$aVal['days']}\" id=\"{$iId}_d\"/>"; 1779 $sHours = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[h]{$sNameSuffix}\" value=\"{$aVal['hours']}\" id=\"{$iId}_h\"/>"; 1780 $sMinutes = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[m]{$sNameSuffix}\" value=\"{$aVal['minutes']}\" id=\"{$iId}_m\"/>"; 1781 $sSeconds = "<input title=\"$sHelpText\" type=\"text\" style=\"text-align:right\" size=\"2\" name=\"attr_{$sFieldPrefix}{$sAttCode}[s]{$sNameSuffix}\" value=\"{$aVal['seconds']}\" id=\"{$iId}_s\"/>"; 1782 $sHidden = "<input type=\"hidden\" id=\"{$iId}\" value=\"".htmlentities($value, ENT_QUOTES, 1783 'UTF-8')."\"/>"; 1784 $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, 1785 $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; 1786 $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); 1787 break; 1788 1789 case 'Password': 1790 $aEventsList[] = 'validate'; 1791 $aEventsList[] = 'keyup'; 1792 $aEventsList[] = 'change'; 1793 $sHTMLValue = "<div class=\"field_input_zone field_input_password\"><input title=\"$sHelpText\" type=\"password\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($value, 1794 ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/></div>{$sValidationSpan}{$sReloadSpan}"; 1795 break; 1796 1797 case 'OQLExpression': 1798 case 'Text': 1799 $aEventsList[] = 'validate'; 1800 $aEventsList[] = 'keyup'; 1801 $aEventsList[] = 'change'; 1802 $sEditValue = $oAttDef->GetEditValue($value); 1803 1804 $aStyles = array(); 1805 $sStyle = ''; 1806 $sWidth = $oAttDef->GetWidth('width', ''); 1807 if (!empty($sWidth)) 1808 { 1809 $aStyles[] = 'width:'.$sWidth; 1810 } 1811 $sHeight = $oAttDef->GetHeight('height', ''); 1812 if (!empty($sHeight)) 1813 { 1814 $aStyles[] = 'height:'.$sHeight; 1815 } 1816 if (count($aStyles) > 0) 1817 { 1818 $sStyle = 'style="'.implode('; ', $aStyles).'"'; 1819 } 1820 1821 if ($oAttDef->GetEditClass() == 'OQLExpression') 1822 { 1823 $sTestResId = 'query_res_'.$sFieldPrefix.$sAttCode.$sNameSuffix; //$oPage->GetUniqueId(); 1824 $sBaseUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?expression='; 1825 $sInitialUrl = $sBaseUrl.urlencode($sEditValue); 1826 $sAdditionalStuff = "<a id=\"$sTestResId\" target=\"_blank\" href=\"$sInitialUrl\">".Dict::S('UI:Edit:TestQuery')."</a>"; 1827 $oPage->add_ready_script("$('#$iId').bind('change keyup', function(evt, sFormId) { $('#$sTestResId').attr('href', '$sBaseUrl'+encodeURIComponent($(this).val())); } );"); 1828 } 1829 else 1830 { 1831 $sAdditionalStuff = ""; 1832 } 1833 // Ok, the text area is drawn here 1834 $sHTMLValue = "<div class=\"field_input_zone field_input_text\"><div class=\"f_i_text_header\"><span class=\"fullscreen_button\" title=\"".Dict::S('UI:ToggleFullScreen')."\"></span></div><textarea class=\"\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\" $sStyle>".htmlentities($sEditValue, 1835 ENT_QUOTES, 'UTF-8')."</textarea>$sAdditionalStuff</div>{$sValidationSpan}{$sReloadSpan}"; 1836 1837 $oPage->add_ready_script( 1838 <<<EOF 1839 $('#$iId').closest('.field_input_text').find('.fullscreen_button').on('click', function(oEvent){ 1840 var oOriginField = $('#$iId').closest('.field_input_text'); 1841 var oClonedField = oOriginField.clone(); 1842 oClonedField.addClass('fullscreen').appendTo('body'); 1843 oClonedField.find('.fullscreen_button').on('click', function(oEvent){ 1844 // Copying value to origin field 1845 oOriginField.find('textarea').val(oClonedField.find('textarea').val()); 1846 oClonedField.remove(); 1847 // Triggering change event 1848 oOriginField.find('textarea').triggerHandler('change'); 1849 }); 1850 }); 1851EOF 1852 ); 1853 break; 1854 1855 case 'CaseLog': 1856 $aStyles = array(); 1857 $sStyle = ''; 1858 $sWidth = $oAttDef->GetWidth('width', ''); 1859 if (!empty($sWidth)) 1860 { 1861 $aStyles[] = 'width:'.$sWidth; 1862 } 1863 $sHeight = $oAttDef->GetHeight('height', ''); 1864 if (!empty($sHeight)) 1865 { 1866 $aStyles[] = 'height:'.$sHeight; 1867 } 1868 if (count($aStyles) > 0) 1869 { 1870 $sStyle = 'style="'.implode('; ', $aStyles).'"'; 1871 } 1872 1873 $sHeader = '<div class="caselog_input_header"></div>'; // will be hidden in CSS (via :empty) if it remains empty 1874 $sEditValue = is_object($value) ? $value->GetModifiedEntry('html') : ''; 1875 $sPreviousLog = is_object($value) ? $value->GetAsHTML($oPage, true /* bEditMode */, 1876 array('AttributeText', 'RenderWikiHtml')) : ''; 1877 $iEntriesCount = is_object($value) ? count($value->GetIndex()) : 0; 1878 $sHidden = "<input type=\"hidden\" id=\"{$iId}_count\" value=\"$iEntriesCount\"/>"; // To know how many entries the case log already contains 1879 1880 $sHTMLValue = "<div class=\"field_input_zone field_input_caselog caselog\" $sStyle>$sHeader<textarea class=\"htmlEditor\" style=\"border:0;width:100%\" title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" rows=\"8\" cols=\"40\" id=\"$iId\">".htmlentities($sEditValue, 1881 ENT_QUOTES, 1882 'UTF-8')."</textarea>$sPreviousLog</div>{$sValidationSpan}{$sReloadSpan}$sHidden"; 1883 1884 // Note: This should be refactored for all types of attribute (see at the end of this function) but as we are doing this for a maintenance release, we are scheduling it for the next main release in to order to avoid regressions as much as possible. 1885 $sNullValue = $oAttDef->GetNullValue(); 1886 if (!is_numeric($sNullValue)) 1887 { 1888 $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number 1889 } 1890 $sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value->GetModifiedEntry('html')) : 'undefined'; 1891 1892 $oPage->add_ready_script("$('#$iId').bind('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );"); // Custom validation function 1893 1894 // Replace the text area with CKEditor 1895 // To change the default settings of the editor, 1896 // a) edit the file /js/ckeditor/config.js 1897 // b) or override some of the configuration settings, using the second parameter of ckeditor() 1898 $aConfig = array(); 1899 $sLanguage = strtolower(trim(UserRights::GetUserLanguage())); 1900 $aConfig['language'] = $sLanguage; 1901 $aConfig['contentsLanguage'] = $sLanguage; 1902 $aConfig['extraPlugins'] = 'disabler'; 1903 $aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere'); 1904 $sConfigJS = json_encode($aConfig); 1905 1906 $oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit 1907 1908 $oPage->add_ready_script( 1909<<<EOF 1910$('#$iId').bind('update', function(evt){ 1911 BlockField('cke_$iId', $('#$iId').attr('disabled')); 1912 //Delayed execution - ckeditor must be properly initialized before setting readonly 1913 var retryCount = 0; 1914 var oMe = $('#$iId'); 1915 var delayedSetReadOnly = function () { 1916 if (oMe.data('ckeditorInstance').editable() == undefined && retryCount++ < 10) { 1917 setTimeout(delayedSetReadOnly, retryCount * 100); //Wait a while longer each iteration 1918 } 1919 else 1920 { 1921 oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled')); 1922 } 1923 }; 1924 setTimeout(delayedSetReadOnly, 50); 1925}); 1926EOF 1927 ); 1928 break; 1929 1930 case 'HTML': 1931 $sEditValue = $oAttDef->GetEditValue($value); 1932 $oWidget = new UIHTMLEditorWidget($iId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText, 1933 $sValidationSpan.$sReloadSpan, $sEditValue, $bMandatory); 1934 $sHTMLValue = $oWidget->Display($oPage, $aArgs); 1935 break; 1936 1937 case 'LinkedSet': 1938 if ($oAttDef->IsIndirect()) 1939 { 1940 $oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, 1941 $oAttDef->DuplicatesAllowed()); 1942 } 1943 else 1944 { 1945 $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix); 1946 } 1947 $aEventsList[] = 'validate'; 1948 $aEventsList[] = 'change'; 1949 $oObj = isset($aArgs['this']) ? $aArgs['this'] : null; 1950 $sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj); 1951 break; 1952 1953 case 'Document': 1954 $aEventsList[] = 'validate'; 1955 $aEventsList[] = 'change'; 1956 $oDocument = $value; // Value is an ormDocument object 1957 $sFileName = ''; 1958 if (is_object($oDocument)) 1959 { 1960 $sFileName = $oDocument->GetFileName(); 1961 } 1962 $iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); 1963 $sHTMLValue = "<div class=\"field_input_zone field_input_document\">\n"; 1964 $sHTMLValue .= "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$iMaxFileSize\" />\n"; 1965 $sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[filename]\" type=\"hidden\" id=\"$iId\" \" value=\"".htmlentities($sFileName, 1966 ENT_QUOTES, 'UTF-8')."\"/>\n"; 1967 $sHTMLValue .= "<span id=\"name_$iInputId\"'>".htmlentities($sFileName, ENT_QUOTES, 1968 'UTF-8')."</span><br/>\n"; 1969 $sHTMLValue .= "<input title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}[fcontents]\" type=\"file\" id=\"file_$iId\" onChange=\"UpdateFileName('$iId', this.value)\"/>\n"; 1970 $sHTMLValue .= "</div>\n"; 1971 $sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n"; 1972 break; 1973 1974 case 'Image': 1975 $aEventsList[] = 'validate'; 1976 $aEventsList[] = 'change'; 1977 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js'); 1978 $oDocument = $value; // Value is an ormDocument object 1979 $sDefaultUrl = $oAttDef->Get('default_image'); 1980 if (is_object($oDocument) && !$oDocument->IsEmpty()) 1981 { 1982 $sUrl = 'data:'.$oDocument->GetMimeType().';base64,'.base64_encode($oDocument->GetData()); 1983 } 1984 else 1985 { 1986 $sUrl = null; 1987 } 1988 1989 $sHTMLValue = "<div class=\"field_input_zone field_input_image\"><div id=\"edit_$iInputId\" class=\"edit-image\"></div></div>\n"; 1990 $sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n"; 1991 1992 $aEditImage = array( 1993 'input_name' => 'attr_'.$sFieldPrefix.$sAttCode.$sNameSuffix, 1994 'max_file_size' => utils::ConvertToBytes(ini_get('upload_max_filesize')), 1995 'max_width_px' => $oAttDef->Get('display_max_width'), 1996 'max_height_px' => $oAttDef->Get('display_max_height'), 1997 'current_image_url' => $sUrl, 1998 'default_image_url' => $sDefaultUrl, 1999 'labels' => array( 2000 'reset_button' => htmlentities(Dict::S('UI:Button:ResetImage'), ENT_QUOTES, 'UTF-8'), 2001 'remove_button' => htmlentities(Dict::S('UI:Button:RemoveImage'), ENT_QUOTES, 'UTF-8'), 2002 'upload_button' => $sHelpText, 2003 ), 2004 ); 2005 $sEditImageOptions = json_encode($aEditImage); 2006 $oPage->add_ready_script("$('#edit_$iInputId').edit_image($sEditImageOptions);"); 2007 break; 2008 2009 case 'StopWatch': 2010 $sHTMLValue = "The edition of a stopwatch is not allowed!!!"; 2011 break; 2012 2013 case 'List': 2014 // Not editable for now... 2015 $sHTMLValue = ''; 2016 break; 2017 2018 case 'One Way Password': 2019 $aEventsList[] = 'validate'; 2020 $oWidget = new UIPasswordWidget($sAttCode, $iId, $sNameSuffix); 2021 $sHTMLValue = $oWidget->Display($oPage, $aArgs); 2022 // Event list & validation is handled directly by the widget 2023 break; 2024 2025 case 'ExtKey': 2026 $aEventsList[] = 'validate'; 2027 $aEventsList[] = 'change'; 2028 2029 if ($bPreserveCurrentValue) 2030 { 2031 $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $value); 2032 } 2033 else 2034 { 2035 $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs); 2036 } 2037 $sFieldName = $sFieldPrefix.$sAttCode.$sNameSuffix; 2038 $aExtKeyParams = $aArgs; 2039 $aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize(); 2040 $aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars(); 2041 $sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(), 2042 $oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams); 2043 $sHTMLValue .= "<!-- iFlags: $iFlags bMandatory: $bMandatory -->\n"; 2044 break; 2045 2046 case 'RedundancySetting': 2047 $sHTMLValue = '<table>'; 2048 $sHTMLValue .= '<tr>'; 2049 $sHTMLValue .= '<td>'; 2050 $sHTMLValue .= '<div id="'.$iId.'">'; 2051 $sHTMLValue .= $oAttDef->GetDisplayForm($value, $oPage, true); 2052 $sHTMLValue .= '</div>'; 2053 $sHTMLValue .= '</td>'; 2054 $sHTMLValue .= '<td>'.$sValidationSpan.$sReloadSpan.'</td>'; 2055 $sHTMLValue .= '</tr>'; 2056 $sHTMLValue .= '</table>'; 2057 $oPage->add_ready_script("$('#$iId :input').bind('keyup change validate', function(evt, sFormId) { return ValidateRedundancySettings('$iId',sFormId); } );"); // Custom validation function 2058 break; 2059 2060 case 'CustomFields': 2061 $sHTMLValue = '<table>'; 2062 $sHTMLValue .= '<tr>'; 2063 $sHTMLValue .= '<td>'; 2064 $sHTMLValue .= '<div id="'.$iId.'_console_form">'; 2065 $sHTMLValue .= '<div id="'.$iId.'_field_set">'; 2066 $sHTMLValue .= '</div>'; 2067 $sHTMLValue .= '</div>'; 2068 $sHTMLValue .= '</td>'; 2069 $sHTMLValue .= '<td>'.$sReloadSpan.'</td>'; // No validation span for this one: it does handle its own validation! 2070 $sHTMLValue .= '</tr>'; 2071 $sHTMLValue .= '</table>'; 2072 $sHTMLValue .= "<input name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iId\" value=\"\"/>\n"; 2073 2074 $oForm = $value->GetForm($sFormPrefix); 2075 $oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oForm); 2076 $aRenderRes = $oRenderer->Render(); 2077 2078 $aFieldSetOptions = array( 2079 'field_identifier_attr' => 'data-field-id', 2080 // convention: fields are rendered into a div and are identified by this attribute 2081 'fields_list' => $aRenderRes, 2082 'fields_impacts' => $oForm->GetFieldsImpacts(), 2083 'form_path' => $oForm->GetId(), 2084 ); 2085 $sFieldSetOptions = json_encode($aFieldSetOptions); 2086 $aFormHandlerOptions = array( 2087 'wizard_helper_var_name' => 'oWizardHelper'.$sFormPrefix, 2088 'custom_field_attcode' => $sAttCode, 2089 ); 2090 $sFormHandlerOptions = json_encode($aFormHandlerOptions); 2091 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_handler.js'); 2092 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/console_form_handler.js'); 2093 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_set.js'); 2094 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_field.js'); 2095 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/subform_field.js'); 2096 $oPage->add_ready_script( 2097<<<EOF 2098 $('#{$iId}_field_set').field_set($sFieldSetOptions); 2099 2100 $('#{$iId}_console_form').console_form_handler($sFormHandlerOptions); 2101 $('#{$iId}_console_form').console_form_handler('alignColumns'); 2102 $('#{$iId}_console_form').console_form_handler('option', 'field_set', $('#{$iId}_field_set')); 2103 // field_change must be processed to refresh the hidden value at anytime 2104 $('#{$iId}_console_form').bind('value_change', function() { $('#{$iId}').val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); console.error($('#{$iId}').val()); }); 2105 // Initialize the hidden value with current state 2106 // update_value is triggered when preparing the wizard helper object for ajax calls 2107 $('#{$iId}').bind('update_value', function() { $(this).val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); }); 2108 // validate is triggered by CheckFields, on all the input fields, once at page init and once before submitting the form 2109 $('#{$iId}').bind('validate', function(evt, sFormId) { 2110 $(this).val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); 2111 return ValidateCustomFields('$iId', sFormId); // Custom validation function 2112 }); 2113EOF 2114); 2115 break; 2116 2117 case 'Set': 2118 case 'TagSet': 2119 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/selectize.min.js'); 2120 $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); 2121 $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'/js/jquery.itop-set-widget.js'); 2122 2123 $oPage->add_dict_entry('Core:AttributeSet:placeholder'); 2124 2125 /** @var \ormSet $value */ 2126 $sJson = $oAttDef->GetJsonForWidget($value, $aArgs); 2127 $sEscapedJson = htmlentities($sJson, ENT_QUOTES, 'UTF-8'); 2128 $sSetInputName = "attr_{$sFormPrefix}{$sAttCode}"; 2129 2130 // handle form validation 2131 $aEventsList[] = 'change'; 2132 $aEventsList[] = 'validate'; 2133 $sNullValue = ''; 2134 $sFieldToValidateId = $sFieldToValidateId.AttributeSet::EDITABLE_INPUT_ID_SUFFIX; 2135 2136 // generate form HTML output 2137 $sValidationSpan = "<span class=\"form_validation\" id=\"v_{$sFieldToValidateId}\"></span>"; 2138 $sHTMLValue = '<div class="field_input_zone field_input_set"><input id="'.$iId.'" name="'.$sSetInputName.'" type="hidden" value="'.$sEscapedJson.'"></div>'.$sValidationSpan.$sReloadSpan; 2139 $sScript = "$('#$iId').set_widget({inputWidgetIdSuffix: '".AttributeSet::EDITABLE_INPUT_ID_SUFFIX."'});"; 2140 $oPage->add_ready_script($sScript); 2141 2142 break; 2143 2144 case 'String': 2145 default: 2146 $aEventsList[] = 'validate'; 2147 // #@# todo - add context information (depending on dimensions) 2148 $aAllowedValues = $oAttDef->GetAllowedValues($aArgs); 2149 $iFieldSize = $oAttDef->GetMaxSize(); 2150 if ($aAllowedValues !== null) 2151 { 2152 // Discrete list of values, use a SELECT or RADIO buttons depending on the config 2153 $sDisplayStyle = $oAttDef->GetDisplayStyle(); 2154 switch ($sDisplayStyle) 2155 { 2156 case 'radio': 2157 case 'radio_horizontal': 2158 case 'radio_vertical': 2159 $aEventsList[] = 'change'; 2160 $sHTMLValue = "<div class=\"field_input_zone field_input_{$sDisplayStyle}\">"; 2161 $bVertical = ($sDisplayStyle != 'radio_horizontal'); 2162 $sHTMLValue .= $oPage->GetRadioButtons($aAllowedValues, $value, $iId, 2163 "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, ''); 2164 $sHTMLValue .= "</div>{$sValidationSpan}{$sReloadSpan}\n"; 2165 break; 2166 2167 case 'select': 2168 default: 2169 $aEventsList[] = 'change'; 2170 $sHTMLValue = "<div class=\"field_input_zone field_input_string\"><select title=\"$sHelpText\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" id=\"$iId\">\n"; 2171 $sHTMLValue .= "<option value=\"\">".Dict::S('UI:SelectOne')."</option>\n"; 2172 foreach($aAllowedValues as $key => $display_value) 2173 { 2174 if ((count($aAllowedValues) == 1) && ($bMandatory == 'true')) 2175 { 2176 // When there is only once choice, select it by default 2177 $sSelected = ' selected'; 2178 } 2179 else 2180 { 2181 $sSelected = ($value == $key) ? ' selected' : ''; 2182 } 2183 $sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n"; 2184 } 2185 $sHTMLValue .= "</select></div>{$sValidationSpan}{$sReloadSpan}\n"; 2186 break; 2187 } 2188 } 2189 else 2190 { 2191 $sHTMLValue = "<div class=\"field_input_zone field_input_string\"><input title=\"$sHelpText\" type=\"text\" maxlength=\"$iFieldSize\" name=\"attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}\" value=\"".htmlentities($sDisplayValue, 2192 ENT_QUOTES, 'UTF-8')."\" id=\"$iId\"/></div>{$sValidationSpan}{$sReloadSpan}"; 2193 $aEventsList[] = 'keyup'; 2194 $aEventsList[] = 'change'; 2195 2196 // Adding tooltip so we can read the whole value when its very long (eg. URL) 2197 if (!empty($sDisplayValue)) 2198 { 2199 $oPage->add_ready_script( 2200 <<<EOF 2201 var sEscapedVal = $('<div/>').text($('#{$iId}').val()).html(); 2202 $('#{$iId}').qtip( { content: sEscapedVal, show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'bottomLeft' }, position: { corner: { target: 'topLeft', tooltip: 'bottomLeft' }, adjust: { y: -15}} } ); 2203 2204 $('#{$iId}').bind('keyup', function(evt, sFormId){ 2205 var oQTipAPI = $(this).qtip('api'); 2206 2207 if($(this).val() === '') 2208 { 2209 oQTipAPI.hide(); 2210 oQTipAPI.disable(true); 2211 } 2212 else 2213 { 2214 oQTipAPI.disable(false); 2215 } 2216 var sEscapedVal = $('<div/>').text($(this).val()).html(); 2217 oQTipAPI.updateContent(sEscapedVal); 2218 }); 2219EOF 2220 ); 2221 } 2222 } 2223 break; 2224 } 2225 $sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$'; 2226 if (!empty($aEventsList)) 2227 { 2228 if (!is_numeric($sNullValue)) 2229 { 2230 $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number 2231 } 2232 $sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value) : 'undefined'; 2233 $oPage->add_ready_script("$('#$sFieldToValidateId').bind('".implode(' ', 2234 $aEventsList)."', function(evt, sFormId) { return ValidateField('$sFieldToValidateId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );\n"); // Bind to a custom event: validate 2235 } 2236 $aDependencies = MetaModel::GetDependentAttributes($sClass, 2237 $sAttCode); // List of attributes that depend on the current one 2238 if (count($aDependencies) > 0) 2239 { 2240 // Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form 2241 $oPage->add_ready_script("$('#$iId').unbind('change.dependencies').bind('change.dependencies', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", 2242 $aDependencies)."']) } );\n"); // Bind to a custom event: validate 2243 } 2244 } 2245 $oPage->add_dict_entry('UI:ValueMustBeSet'); 2246 $oPage->add_dict_entry('UI:ValueMustBeChanged'); 2247 $oPage->add_dict_entry('UI:ValueInvalidFormat'); 2248 2249 return "<div id=\"field_{$iId}\" class=\"field_value_container\"><div class=\"attribute-edit\" data-attcode=\"$sAttCode\">{$sHTMLValue}</div></div>"; 2250 } 2251 2252 public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array()) 2253 { 2254 $sOwnershipToken = null; 2255 $iKey = $this->GetKey(); 2256 $sClass = get_class($this); 2257 if ($iKey > 0) 2258 { 2259 // The concurrent access lock makes sense only for already existing objects 2260 $LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); 2261 if ($LockEnabled) 2262 { 2263 $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); 2264 if ($sOwnershipToken !== null) 2265 { 2266 // We're probably inside something like "apply_modify" where the validation failed and we must prompt the user again to edit the object 2267 // let's extend our lock 2268 } 2269 else 2270 { 2271 $aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey); 2272 if ($aLockInfo['success']) 2273 { 2274 $sOwnershipToken = $aLockInfo['token']; 2275 } 2276 else 2277 { 2278 // If the object is locked by the current user, it's worth trying again, since 2279 // the lock may be released by 'onunload' which is called AFTER loading the current page. 2280 //$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId(); 2281 self::ReloadAndDisplay($oPage, $this, array('operation' => 'modify')); 2282 2283 return; 2284 } 2285 } 2286 } 2287 } 2288 2289 if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) 2290 { 2291 $sClassLabel = MetaModel::GetName($sClass); 2292 $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $this->GetRawName(), 2293 $sClassLabel)); // Set title will take care of the encoding 2294 $oPage->add("<div class=\"page_header\">\n"); 2295 $oPage->add("<h1>".$this->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, 2296 $this->GetName())."</h1>\n"); 2297 $oPage->add("</div>\n"); 2298 $oPage->add("<div class=\"wizContainer\">\n"); 2299 } 2300 self::$iGlobalFormId++; 2301 $this->aFieldsMap = array(); 2302 $sPrefix = ''; 2303 if (isset($aExtraParams['formPrefix'])) 2304 { 2305 $sPrefix = $aExtraParams['formPrefix']; 2306 } 2307 2308 $this->m_iFormId = $sPrefix.self::$iGlobalFormId; 2309 $oAppContext = new ApplicationContext(); 2310 if (!isset($aExtraParams['action'])) 2311 { 2312 $sFormAction = utils::GetAbsoluteUrlAppRoot().'pages/'.$this->GetUIPage(); // No parameter in the URL, the only parameter will be the ones passed through the form 2313 } 2314 else 2315 { 2316 $sFormAction = $aExtraParams['action']; 2317 } 2318 // Custom label for the apply button ? 2319 if (isset($aExtraParams['custom_button'])) 2320 { 2321 $sApplyButton = $aExtraParams['custom_button']; 2322 } 2323 else 2324 { 2325 if ($iKey > 0) 2326 { 2327 $sApplyButton = Dict::S('UI:Button:Apply'); 2328 } 2329 else 2330 { 2331 $sApplyButton = Dict::S('UI:Button:Create'); 2332 } 2333 } 2334 // Custom operation for the form ? 2335 if (isset($aExtraParams['custom_operation'])) 2336 { 2337 $sOperation = $aExtraParams['custom_operation']; 2338 } 2339 else 2340 { 2341 if ($iKey > 0) 2342 { 2343 $sOperation = 'apply_modify'; 2344 } 2345 else 2346 { 2347 $sOperation = 'apply_new'; 2348 } 2349 } 2350 if ($iKey > 0) 2351 { 2352 // The object already exists in the database, it's a modification 2353 $sButtons = "<input id=\"{$sPrefix}_id\" type=\"hidden\" name=\"id\" value=\"$iKey\">\n"; 2354 $sButtons .= "<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n"; 2355 $sButtons .= "<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button> \n"; 2356 $sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n"; 2357 } 2358 else 2359 { 2360 // The object does not exist in the database it's a creation 2361 $sButtons = "<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n"; 2362 $sButtons .= "<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button> \n"; 2363 $sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n"; 2364 } 2365 2366 $aTransitions = $this->EnumTransitions(); 2367 if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) 2368 { 2369 // transitions are displayed only for the standard new/modify actions, not for modify_all or any other case... 2370 $oSetToCheckRights = DBObjectSet::FromObject($this); 2371 $aStimuli = Metamodel::EnumStimuli($sClass); 2372 foreach($aTransitions as $sStimulusCode => $aTransitionDef) 2373 { 2374 $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, 2375 $sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO; 2376 switch ($iActionAllowed) 2377 { 2378 case UR_ALLOWED_YES: 2379 $sButtons .= "<button type=\"submit\" name=\"next_action\" value=\"{$sStimulusCode}\" class=\"action\"><span>".$aStimuli[$sStimulusCode]->GetLabel()."</span></button>\n"; 2380 break; 2381 2382 default: 2383 // Do nothing 2384 } 2385 } 2386 } 2387 2388 $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); 2389 $iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId(); 2390 $oPage->SetTransactionId($iTransactionId); 2391 $oPage->add("<form action=\"$sFormAction\" id=\"form_{$this->m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return OnSubmit('form_{$this->m_iFormId}');\">\n"); 2392 $sStatesSelection = ''; 2393 if (!isset($aExtraParams['custom_operation']) && $this->IsNew()) 2394 { 2395 $aInitialStates = MetaModel::EnumInitialStates($sClass); 2396 //$aInitialStates = array('new' => 'foo', 'closed' => 'bar'); 2397 if (count($aInitialStates) > 1) 2398 { 2399 $sStatesSelection = Dict::Format('UI:Create_Class_InState', 2400 MetaModel::GetName($sClass)).'<select name="obj_state" class="state_select_'.$this->m_iFormId.'">'; 2401 foreach($aInitialStates as $sStateCode => $sStateData) 2402 { 2403 $sSelected = ''; 2404 if ($sStateCode == $this->GetState()) 2405 { 2406 $sSelected = ' selected'; 2407 } 2408 $sStatesSelection .= '<option value="'.$sStateCode.'" '.$sSelected.'>'.MetaModel::GetStateLabel($sClass, 2409 $sStateCode).'</option>'; 2410 } 2411 $sStatesSelection .= '</select>'; 2412 $oPage->add_ready_script("$('.state_select_{$this->m_iFormId}').change( function() { oWizardHelper$sPrefix.ReloadObjectCreationForm('form_{$this->m_iFormId}', $(this).val()); } );"); 2413 } 2414 } 2415 2416 $sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage')); 2417 $sJSToken = json_encode($sOwnershipToken); 2418 $oPage->add_ready_script( 2419 <<<EOF 2420 $(window).on('unload',function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } ); 2421 window.onbeforeunload = function() { 2422 if (!window.bInSubmit && !window.bInCancel) 2423 { 2424 return '$sConfirmationMessage'; 2425 } 2426 // return nothing ! safer for IE 2427 }; 2428EOF 2429 ); 2430 2431 if ($sButtonsPosition != 'bottom') 2432 { 2433 // top or both, display the buttons here 2434 $oPage->p($sStatesSelection); 2435 $oPage->add($sButtons); 2436 } 2437 2438 $oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix); 2439 $oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB); 2440 $oPage->SetCurrentTab(Dict::S('UI:PropertiesTab')); 2441 2442 $aFieldsMap = $this->DisplayBareProperties($oPage, true, $sPrefix, $aExtraParams); 2443 if (!is_array($aFieldsMap)) 2444 { 2445 $aFieldsMap = array(); 2446 } 2447 if ($iKey > 0) 2448 { 2449 $aFieldsMap['id'] = $sPrefix.'_id'; 2450 } 2451 // Now display the relations, one tab per relation 2452 if (!isset($aExtraParams['noRelations'])) 2453 { 2454 $this->DisplayBareRelations($oPage, true); // Edit mode, will fill $this->aFieldsMap 2455 $aFieldsMap = array_merge($aFieldsMap, $this->aFieldsMap); 2456 } 2457 2458 $oPage->SetCurrentTab(''); 2459 $oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n"); 2460 $oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"$iTransactionId\">\n"); 2461 foreach($aExtraParams as $sName => $value) 2462 { 2463 if (is_scalar($value)) 2464 { 2465 $oPage->add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">\n"); 2466 } 2467 } 2468 if ($sOwnershipToken !== null) 2469 { 2470 $oPage->add("<input type=\"hidden\" name=\"ownership_token\" value=\"".htmlentities($sOwnershipToken, 2471 ENT_QUOTES, 'UTF-8')."\">\n"); 2472 } 2473 $oPage->add($oAppContext->GetForForm()); 2474 if ($sButtonsPosition != 'top') 2475 { 2476 // bottom or both: display the buttons here 2477 $oPage->p($sStatesSelection); 2478 $oPage->add($sButtons); 2479 } 2480 2481 // Hook the cancel button via jQuery so that it can be unhooked easily as well if needed 2482 $sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink(); 2483 $oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').click( function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );"); 2484 $oPage->add("</form>\n"); 2485 2486 if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) 2487 { 2488 $oPage->add("</div>\n"); 2489 } 2490 2491 $iFieldsCount = count($aFieldsMap); 2492 $sJsonFieldsMap = json_encode($aFieldsMap); 2493 $sState = $this->GetState(); 2494 $sSessionStorageKey = $sClass.'_'.$iKey; 2495 $sTempId = utils::GetUploadTempId($iTransactionId); 2496 $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); 2497 2498 $oPage->add_script( 2499 <<<EOF 2500 sessionStorage.removeItem('$sSessionStorageKey'); 2501 2502 // Create the object once at the beginning of the page... 2503 var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix', '$sState'); 2504 oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap); 2505 oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount); 2506EOF 2507 ); 2508 $oPage->add_ready_script( 2509 <<<EOF 2510 oWizardHelper$sPrefix.UpdateWizard(); 2511 // Starts the validation when the page is ready 2512 CheckFields('form_{$this->m_iFormId}', false); 2513 2514EOF 2515 ); 2516 if ($sOwnershipToken !== null) 2517 { 2518 $this->GetOwnershipJSHandler($oPage, $sOwnershipToken); 2519 } 2520 else 2521 { 2522 // Probably a new object (or no concurrent lock), let's add a watchdog so that the session is kept open while editing 2523 $iInterval = MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay') * 1000 / 2; 2524 if ($iInterval > 0) 2525 { 2526 $iInterval = max(MIN_WATCHDOG_INTERVAL * 1000, 2527 $iInterval); // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL 2528 $oPage->add_ready_script( 2529 <<<EOF 2530 window.setInterval(function() { 2531 $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'watchdog'}); 2532 }, $iInterval); 2533EOF 2534 ); 2535 } 2536 } 2537 } 2538 2539 public static function DisplayCreationForm(WebPage $oPage, $sClass, $oObjectToClone = null, $aArgs = array(), $aExtraParams = array()) 2540 { 2541 $sClass = ($oObjectToClone == null) ? $sClass : get_class($oObjectToClone); 2542 2543 if ($oObjectToClone == null) 2544 { 2545 $oObj = DBObject::MakeDefaultInstance($sClass); 2546 } 2547 else 2548 { 2549 $oObj = clone $oObjectToClone; 2550 } 2551 2552 // Pre-fill the object with default values, when there is only on possible choice 2553 // AND the field is mandatory (otherwise there is always the possiblity to let it empty) 2554 $aArgs['this'] = $oObj; 2555 $aDetailsList = self::FLattenZList(MetaModel::GetZListItems($sClass, 'details')); 2556 // Order the fields based on their dependencies 2557 $aDeps = array(); 2558 foreach($aDetailsList as $sAttCode) 2559 { 2560 $aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); 2561 } 2562 $aList = self::OrderDependentFields($aDeps); 2563 2564 // Now fill-in the fields with default/supplied values 2565 foreach($aList as $sAttCode) 2566 { 2567 if (isset($aArgs['default'][$sAttCode])) 2568 { 2569 $oObj->Set($sAttCode, $aArgs['default'][$sAttCode]); 2570 } 2571 else 2572 { 2573 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 2574 2575 // If the field is mandatory, set it to the only possible value 2576 $iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode); 2577 if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) 2578 { 2579 if ($oAttDef->IsExternalKey()) 2580 { 2581 /** @var DBObjectSet $oAllowedValues */ 2582 $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs); 2583 if ($oAllowedValues->CountWithLimit(2) == 1) 2584 { 2585 $oRemoteObj = $oAllowedValues->Fetch(); 2586 $oObj->Set($sAttCode, $oRemoteObj->GetKey()); 2587 } 2588 } 2589 else 2590 { 2591 $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); 2592 if (is_array($aAllowedValues) && (count($aAllowedValues) == 1)) 2593 { 2594 $aValues = array_keys($aAllowedValues); 2595 $oObj->Set($sAttCode, $aValues[0]); 2596 } 2597 } 2598 } 2599 } 2600 } 2601 2602 return $oObj->DisplayModifyForm($oPage, $aExtraParams); 2603 } 2604 2605 public function DisplayStimulusForm(WebPage $oPage, $sStimulus, $aPrefillFormParam = null) 2606 { 2607 $sClass = get_class($this); 2608 $iKey = $this->GetKey(); 2609 $aTransitions = $this->EnumTransitions(); 2610 $aStimuli = MetaModel::EnumStimuli($sClass); 2611 if (!isset($aTransitions[$sStimulus])) 2612 { 2613 // Invalid stimulus 2614 throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, 2615 $this->GetName(), $this->GetStateLabel())); 2616 } 2617 // Check for concurrent access lock 2618 $LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); 2619 $sOwnershipToken = null; 2620 if ($LockEnabled) 2621 { 2622 $aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey); 2623 if ($aLockInfo['success']) 2624 { 2625 $sOwnershipToken = $aLockInfo['token']; 2626 } 2627 else 2628 { 2629 // If the object is locked by the current user, it's worth trying again, since 2630 // the lock may be released by 'onunload' which is called AFTER loading the current page. 2631 //$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId(); 2632 self::ReloadAndDisplay($oPage, $this, array('operation' => 'stimulus', 'stimulus' => $sStimulus)); 2633 2634 return; 2635 } 2636 } 2637 $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); 2638 $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); 2639 $oPage->add("<div class=\"page_header\">\n"); 2640 $oPage->add("<h1>$sActionLabel - <span class=\"hilite\">{$this->GetName()}</span></h1>\n"); 2641 $oPage->set_title($sActionLabel); 2642 $oPage->add("</div>\n"); 2643 $oPage->add("<h1>$sActionDetails</h1>\n"); 2644 $sTargetState = $aTransitions[$sStimulus]['target_state']; 2645 $aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/); 2646 if ($aPrefillFormParam != null) 2647 { 2648 $aPrefillFormParam['expected_attributes'] = $aExpectedAttributes; 2649 $this->PrefillForm('state_change', $aPrefillFormParam); 2650 $aExpectedAttributes = $aPrefillFormParam['expected_attributes']; 2651 } 2652 $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); 2653 if ($sButtonsPosition == 'bottom') 2654 { 2655 // bottom: Displays the ticket details BEFORE the actions 2656 $oPage->add('<div class="ui-widget-content">'); 2657 $this->DisplayBareProperties($oPage); 2658 $oPage->add('</div>'); 2659 } 2660 $oPage->add("<div class=\"wizContainer\">\n"); 2661 $oPage->add("<form id=\"apply_stimulus\" method=\"post\" enctype=\"multipart/form-data\" onSubmit=\"return OnSubmit('apply_stimulus');\">\n"); 2662 $aDetails = array(); 2663 $iFieldIndex = 0; 2664 $aFieldsMap = array(); 2665 2666 // The list of candidate fields is made of the ordered list of "details" attributes + other attributes 2667 $aAttributes = array(); 2668 foreach($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) 2669 { 2670 $aAttributes[$sAttCode] = true; 2671 } 2672 foreach(MetaModel::GetAttributesList($sClass) as $sAttCode) 2673 { 2674 if (!array_key_exists($sAttCode, $aAttributes)) 2675 { 2676 $aAttributes[$sAttCode] = true; 2677 } 2678 } 2679 // Order the fields based on their dependencies, set the fields for which there is only one possible value 2680 // and perform this in the order of dependencies to avoid dead-ends 2681 $aDeps = array(); 2682 foreach($aAttributes as $sAttCode => $trash) 2683 { 2684 $aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); 2685 } 2686 $aList = $this->OrderDependentFields($aDeps); 2687 2688 foreach($aList as $sAttCode) 2689 { 2690 // Consider only the "expected" fields for the target state 2691 if (array_key_exists($sAttCode, $aExpectedAttributes)) 2692 { 2693 $iExpectCode = $aExpectedAttributes[$sAttCode]; 2694 // Prompt for an attribute if 2695 // - the attribute must be changed or must be displayed to the user for confirmation 2696 // - or the field is mandatory and currently empty 2697 if (($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || 2698 (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == ''))) 2699 { 2700 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 2701 $aArgs = array('this' => $this); 2702 // If the field is mandatory, set it to the only possible value 2703 if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY)) 2704 { 2705 if ($oAttDef->IsExternalKey()) 2706 { 2707 /** @var DBObjectSet $oAllowedValues */ 2708 $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', 2709 $this->Get($sAttCode)); 2710 if ($oAllowedValues->CountWithLimit(2) == 1) 2711 { 2712 $oRemoteObj = $oAllowedValues->Fetch(); 2713 $this->Set($sAttCode, $oRemoteObj->GetKey()); 2714 } 2715 } 2716 else 2717 { 2718 $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); 2719 if (is_array($aAllowedValues) && count($aAllowedValues) == 1) 2720 { 2721 $aValues = array_keys($aAllowedValues); 2722 $this->Set($sAttCode, $aValues[0]); 2723 } 2724 } 2725 } 2726 $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, 2727 $this->Get($sAttCode), $this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, 2728 $aArgs); 2729 $aDetails[] = array( 2730 'label' => '<span>'.$oAttDef->GetLabel().'</span>', 2731 'value' => "<span id=\"field_att_$iFieldIndex\">$sHTMLValue</span>", 2732 ); 2733 $aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex; 2734 $iFieldIndex++; 2735 } 2736 } 2737 } 2738 2739 $oPage->add('<table><tr><td>'); 2740 $oPage->details($aDetails); 2741 $oPage->add('</td></tr></table>'); 2742 $oPage->add("<input type=\"hidden\" name=\"id\" value=\"".$this->GetKey()."\" id=\"id\">\n"); 2743 $aFieldsMap['id'] = 'id'; 2744 $oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n"); 2745 $oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_stimulus\">\n"); 2746 $oPage->add("<input type=\"hidden\" name=\"stimulus\" value=\"$sStimulus\">\n"); 2747 $iTransactionId = utils::GetNewTransactionId(); 2748 $oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".$iTransactionId."\">\n"); 2749 if ($sOwnershipToken !== null) 2750 { 2751 $oPage->add("<input type=\"hidden\" name=\"ownership_token\" value=\"".htmlentities($sOwnershipToken, 2752 ENT_QUOTES, 'UTF-8')."\">\n"); 2753 } 2754 $oAppContext = new ApplicationContext(); 2755 $oPage->add($oAppContext->GetForForm()); 2756 $oPage->add("<button type=\"button\" class=\"action cancel\" onClick=\"BackToDetails('$sClass', ".$this->GetKey().", '', '$sOwnershipToken')\"><span>".Dict::S('UI:Button:Cancel')."</span></button> \n"); 2757 $oPage->add("<button type=\"submit\" class=\"action\"><span>$sActionLabel</span></button>\n"); 2758 $oPage->add("</form>\n"); 2759 $oPage->add("</div>\n"); 2760 if ($sButtonsPosition != 'top') 2761 { 2762 // bottom or both: Displays the ticket details AFTER the actions 2763 $oPage->add('<div class="ui-widget-content">'); 2764 $this->DisplayBareProperties($oPage); 2765 $oPage->add('</div>'); 2766 } 2767 2768 $iFieldsCount = count($aFieldsMap); 2769 $sJsonFieldsMap = json_encode($aFieldsMap); 2770 2771 $oPage->add_script( 2772 <<<EOF 2773 // Initializes the object once at the beginning of the page... 2774 var oWizardHelper = new WizardHelper('$sClass', '', '$sTargetState', '{$this->GetState()}', '$sStimulus'); 2775 oWizardHelper.SetFieldsMap($sJsonFieldsMap); 2776 oWizardHelper.SetFieldsCount($iFieldsCount); 2777EOF 2778 ); 2779 $sJSToken = json_encode($sOwnershipToken); 2780 $oPage->add_ready_script( 2781 <<<EOF 2782 // Starts the validation when the page is ready 2783 CheckFields('apply_stimulus', false); 2784 $(window).on('unload', function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } ); 2785EOF 2786 ); 2787 2788 if ($sOwnershipToken !== null) 2789 { 2790 $this->GetOwnershipJSHandler($oPage, $sOwnershipToken); 2791 } 2792 2793 // Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug n°1240. 2794 $sTempId = utils::GetUploadTempId($iTransactionId); 2795 $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); 2796 } 2797 2798 public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet) 2799 { 2800 $index = 0; 2801 foreach($aList as $sKey => $value) 2802 { 2803 if (is_array($value)) 2804 { 2805 if (preg_match('/^(.*):(.*)$/U', $sKey, $aMatches)) 2806 { 2807 $sCode = $aMatches[1]; 2808 $sName = $aMatches[2]; 2809 switch ($sCode) 2810 { 2811 case 'tab': 2812 if (!isset($aDetails[$sName])) 2813 { 2814 $aDetails[$sName] = array('col1' => array()); 2815 } 2816 $aDetails = self::ProcessZlist($value, $aDetails, $sName, 'col1', ''); 2817 break; 2818 2819 case 'fieldset': 2820 if (!isset($aDetailsStruct[$sCurrentTab][$sCurrentCol][$sName])) 2821 { 2822 $aDetails[$sCurrentTab][$sCurrentCol][$sName] = array(); 2823 } 2824 $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sCurrentCol, $sName); 2825 break; 2826 2827 default: 2828 case 'col': 2829 if (!isset($aDetails[$sCurrentTab][$sName])) 2830 { 2831 $aDetails[$sCurrentTab][$sName] = array(); 2832 } 2833 $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sName, ''); 2834 break; 2835 } 2836 } 2837 } 2838 else 2839 { 2840 if (empty($sCurrentSet)) 2841 { 2842 $aDetails[$sCurrentTab][$sCurrentCol]['_'.$index][] = $value; 2843 } 2844 else 2845 { 2846 $aDetails[$sCurrentTab][$sCurrentCol][$sCurrentSet][] = $value; 2847 } 2848 } 2849 $index++; 2850 } 2851 2852 return $aDetails; 2853 } 2854 2855 static function FlattenZList($aList) 2856 { 2857 $aResult = array(); 2858 foreach($aList as $value) 2859 { 2860 if (!is_array($value)) 2861 { 2862 $aResult[] = $value; 2863 } 2864 else 2865 { 2866 $aResult = array_merge($aResult, self::FlattenZList($value)); 2867 } 2868 } 2869 2870 return $aResult; 2871 } 2872 2873 protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode) 2874 { 2875 $retVal = null; 2876 if ($this->IsNew()) 2877 { 2878 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 2879 } 2880 else 2881 { 2882 $iFlags = $this->GetAttributeFlags($sAttCode); 2883 } 2884 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 2885 if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) 2886 { 2887 // The field is visible in the current state of the object 2888 if ($sStateAttCode == $sAttCode) 2889 { 2890 // Special display for the 'state' attribute itself 2891 $sDisplayValue = $this->GetStateLabel(); 2892 } 2893 else 2894 { 2895 if ($oAttDef->GetEditClass() == 'Document') 2896 { 2897 $oDocument = $this->Get($sAttCode); 2898 $sDisplayValue = $this->GetAsHTML($sAttCode); 2899 $sDisplayValue .= "<br/>".Dict::Format('UI:OpenDocumentInNewWindow_', 2900 $oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; 2901 $sDisplayValue .= "<br/>".Dict::Format('UI:DownloadDocument_', 2902 $oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; 2903 } 2904 elseif ($oAttDef instanceof AttributeDashboard) 2905 { 2906 $sDisplayValue = ''; 2907 } 2908 else 2909 { 2910 $sDisplayValue = $this->GetAsHTML($sAttCode); 2911 } 2912 } 2913 $retVal = array( 2914 'label' => '<span title="'.MetaModel::GetDescription($sClass, 2915 $sAttCode).'">'.MetaModel::GetLabel($sClass, $sAttCode).'</span>', 2916 'value' => $sDisplayValue, 2917 'attcode' => $sAttCode, 2918 ); 2919 2920 // Checking how the field should be rendered 2921 // Note: For edit mode, this is done in self::GetBareProperties() 2922 // Note 2: Shouldn't this be a AttDef property instead of an array to maintain? 2923 if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) 2924 { 2925 $retVal['layout'] = 'large'; 2926 } 2927 else 2928 { 2929 $retVal['layout'] = 'small'; 2930 } 2931 } 2932 2933 return $retVal; 2934 } 2935 2936 /** 2937 * Displays a blob document *inline* (if possible, depending on the type of the document) 2938 * 2939 * @param \WebPage $oPage 2940 * @param $sAttCode 2941 * 2942 * @return string 2943 * @throws \CoreException 2944 */ 2945 public function DisplayDocumentInline(WebPage $oPage, $sAttCode) 2946 { 2947 $oDoc = $this->Get($sAttCode); 2948 $sClass = get_class($this); 2949 $Id = $this->GetKey(); 2950 switch ($oDoc->GetMainMimeType()) 2951 { 2952 case 'text': 2953 case 'html': 2954 $data = $oDoc->GetData(); 2955 switch ($oDoc->GetMimeType()) 2956 { 2957 case 'text/html': 2958 case 'text/xml': 2959 $oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n"); 2960 break; 2961 2962 default: 2963 $oPage->add("<pre>".htmlentities(MyHelpers::beautifulstr($data, 1000, true), ENT_QUOTES, 2964 'UTF-8')."</pre>\n"); 2965 } 2966 break; 2967 2968 case 'application': 2969 switch ($oDoc->GetMimeType()) 2970 { 2971 case 'application/pdf': 2972 $oPage->add("<iframe id='preview_$sAttCode' src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n"); 2973 break; 2974 2975 default: 2976 $oPage->add(Dict::S('UI:Document:NoPreview')); 2977 } 2978 break; 2979 2980 case 'image': 2981 $oPage->add("<img src=\"".utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" />\n"); 2982 break; 2983 2984 default: 2985 $oPage->add(Dict::S('UI:Document:NoPreview')); 2986 } 2987 return ''; 2988 } 2989 2990 // $m_highlightComparison[previous][new] => next value 2991 protected static $m_highlightComparison = array( 2992 HILIGHT_CLASS_CRITICAL => array( 2993 HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL, 2994 HILIGHT_CLASS_WARNING => HILIGHT_CLASS_CRITICAL, 2995 HILIGHT_CLASS_OK => HILIGHT_CLASS_CRITICAL, 2996 HILIGHT_CLASS_NONE => HILIGHT_CLASS_CRITICAL, 2997 ), 2998 HILIGHT_CLASS_WARNING => array( 2999 HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL, 3000 HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING, 3001 HILIGHT_CLASS_OK => HILIGHT_CLASS_WARNING, 3002 HILIGHT_CLASS_NONE => HILIGHT_CLASS_WARNING, 3003 ), 3004 HILIGHT_CLASS_OK => array( 3005 HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL, 3006 HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING, 3007 HILIGHT_CLASS_OK => HILIGHT_CLASS_OK, 3008 HILIGHT_CLASS_NONE => HILIGHT_CLASS_OK, 3009 ), 3010 HILIGHT_CLASS_NONE => array( 3011 HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL, 3012 HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING, 3013 HILIGHT_CLASS_OK => HILIGHT_CLASS_OK, 3014 HILIGHT_CLASS_NONE => HILIGHT_CLASS_NONE, 3015 ), 3016 ); 3017 3018 /** 3019 * This function returns a 'hilight' CSS class, used to hilight a given row in a table 3020 * There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL, 3021 * HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE 3022 * To Be overridden by derived classes 3023 * 3024 * @param void 3025 * 3026 * @return String The desired higlight class for the object/row 3027 */ 3028 public function GetHilightClass() 3029 { 3030 // Possible return values are: 3031 // HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE 3032 $current = parent::GetHilightClass(); // Default computation 3033 3034 // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information 3035 foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) 3036 { 3037 $new = $oExtensionInstance->GetHilightClass($this); 3038 @$current = self::$m_highlightComparison[$current][$new]; 3039 } 3040 3041 return $current; 3042 } 3043 3044 /** 3045 * Re-order the fields based on their inter-dependencies 3046 * 3047 * @params hash @aFields field_code => array_of_depencies 3048 * 3049 * @param $aFields 3050 * @return array Ordered array of fields or throws an exception 3051 * @throws \Exception 3052 */ 3053 public static function OrderDependentFields($aFields) 3054 { 3055 $aResult = array(); 3056 $iCount = 0; 3057 do 3058 { 3059 $bSet = false; 3060 $iCount++; 3061 foreach($aFields as $sFieldCode => $aDeps) 3062 { 3063 foreach($aDeps as $key => $sDependency) 3064 { 3065 if (in_array($sDependency, $aResult)) 3066 { 3067 // Dependency is resolved, remove it 3068 unset($aFields[$sFieldCode][$key]); 3069 } 3070 else 3071 { 3072 if (!array_key_exists($sDependency, $aFields)) 3073 { 3074 // The current fields depends on a field not present in the form 3075 // let's ignore it (since it cannot change) 3076 unset($aFields[$sFieldCode][$key]); 3077 } 3078 } 3079 } 3080 if (count($aFields[$sFieldCode]) == 0) 3081 { 3082 // No more pending depencies for this field, add it to the list 3083 $aResult[] = $sFieldCode; 3084 unset($aFields[$sFieldCode]); 3085 $bSet = true; 3086 } 3087 } 3088 } while ($bSet && (count($aFields) > 0)); 3089 3090 if (count($aFields) > 0) 3091 { 3092 $sMessage = "Error: Circular dependencies between the fields! <pre>".print_r($aFields, true)."</pre>"; 3093 throw(new Exception($sMessage)); 3094 } 3095 3096 return $aResult; 3097 } 3098 3099 /** 3100 * Get the list of actions to be displayed as 'shortcuts' (i.e buttons) instead of inside the Actions popup menu 3101 * 3102 * @param $sFinalClass string The actual class of the objects for which to display the menu 3103 * 3104 * @return array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the 3105 * actions menu 3106 */ 3107 public static function GetShortcutActions($sFinalClass) 3108 { 3109 $sShortcutActions = MetaModel::GetConfig()->Get('shortcut_actions'); 3110 $aShortcutActions = explode(',', $sShortcutActions); 3111 3112 return $aShortcutActions; 3113 } 3114 3115 /** 3116 * Maps the given context parameter name to the appropriate filter/search code for this class 3117 * 3118 * @param string $sContextParam Name of the context parameter, i.e. 'org_id' 3119 * 3120 * @return string Filter code, i.e. 'customer_id' 3121 */ 3122 public static function MapContextParam($sContextParam) 3123 { 3124 if ($sContextParam == 'menu') 3125 { 3126 return null; 3127 } 3128 else 3129 { 3130 return $sContextParam; 3131 } 3132 } 3133 3134 /** 3135 * Updates the object from a flat array of values 3136 * 3137 * @param $aAttList array $aAttList array of attcode 3138 * @param $aErrors array Returns information about slave attributes 3139 * @param $aAttFlags array Attribute codes => Flags to use instead of those from the MetaModel 3140 * 3141 * @return array of attcodes that can be used for writing on the current object 3142 * @throws \CoreException 3143 */ 3144 public function GetWriteableAttList($aAttList, &$aErrors, $aAttFlags = array()) 3145 { 3146 if (!is_array($aAttList)) 3147 { 3148 $aAttList = $this->FlattenZList(MetaModel::GetZListItems(get_class($this), 'details')); 3149 // Special case to process the case log, if any... 3150 // WARNING: if you change this also check the functions DisplayModifyForm and DisplayCaseLog 3151 foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) 3152 { 3153 3154 if (array_key_exists($sAttCode, $aAttFlags)) 3155 { 3156 $iFlags = $aAttFlags[$sAttCode]; 3157 } 3158 elseif ($this->IsNew()) 3159 { 3160 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 3161 } 3162 else 3163 { 3164 $aVoid = array(); 3165 $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid); 3166 } 3167 if ($oAttDef instanceof AttributeCaseLog) 3168 { 3169 if (!($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_SLAVE | OPT_ATT_READONLY))) 3170 { 3171 // The case log is editable, append it to the list of fields to retrieve 3172 $aAttList[] = $sAttCode; 3173 } 3174 } 3175 } 3176 } 3177 $aWriteableAttList = array(); 3178 foreach($aAttList as $sAttCode) 3179 { 3180 $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); 3181 3182 if (array_key_exists($sAttCode, $aAttFlags)) 3183 { 3184 $iFlags = $aAttFlags[$sAttCode]; 3185 } 3186 elseif ($this->IsNew()) 3187 { 3188 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 3189 } 3190 else 3191 { 3192 $aVoid = array(); 3193 $iFlags = $this->GetAttributeFlags($sAttCode, $aVoid); 3194 } 3195 if ($oAttDef->IsWritable()) 3196 { 3197 if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) 3198 { 3199 // Non-visible, or read-only attribute, do nothing 3200 } 3201 elseif ($iFlags & OPT_ATT_SLAVE) 3202 { 3203 $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); 3204 } 3205 else 3206 { 3207 $aWriteableAttList[$sAttCode] = $oAttDef; 3208 } 3209 } 3210 } 3211 3212 return $aWriteableAttList; 3213 } 3214 3215 /** 3216 * Compute the attribute flags depending on the object state 3217 */ 3218 public function GetFormAttributeFlags($sAttCode) 3219 { 3220 if ($this->IsNew()) 3221 { 3222 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 3223 } 3224 else 3225 { 3226 $iFlags = $this->GetAttributeFlags($sAttCode); 3227 } 3228 if (($iFlags & OPT_ATT_MANDATORY) && $this->IsNew()) 3229 { 3230 $iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object 3231 } 3232 3233 return $iFlags; 3234 } 3235 3236 /** 3237 * Updates the object from a flat array of values 3238 * 3239 * @param $aValues array of attcode => scalar or array (N-N links) 3240 * 3241 * @return void 3242 * @throws \ArchivedObjectException 3243 * @throws \CoreException 3244 * @throws \CoreUnexpectedValue 3245 */ 3246 public function UpdateObjectFromArray($aValues) 3247 { 3248 foreach($aValues as $sAttCode => $value) 3249 { 3250 $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); 3251 switch ($oAttDef->GetEditClass()) 3252 { 3253 case 'Document': 3254 // There should be an uploaded file with the named attr_<attCode> 3255 $oDocument = $value['fcontents']; 3256 if (!$oDocument->IsEmpty()) 3257 { 3258 // A new file has been uploaded 3259 $this->Set($sAttCode, $oDocument); 3260 } 3261 break; 3262 case 'Image': 3263 // There should be an uploaded file with the named attr_<attCode> 3264 if ($value['remove']) 3265 { 3266 $this->Set($sAttCode, null); 3267 } 3268 else 3269 { 3270 $oDocument = $value['fcontents']; 3271 if (!$oDocument->IsEmpty()) 3272 { 3273 // A new file has been uploaded 3274 $this->Set($sAttCode, $oDocument); 3275 } 3276 } 3277 break; 3278 case 'One Way Password': 3279 // Check if the password was typed/changed 3280 $aPwdData = $value; 3281 if (!is_null($aPwdData) && $aPwdData['changed']) 3282 { 3283 // The password has been changed or set 3284 $this->Set($sAttCode, $aPwdData['value']); 3285 } 3286 break; 3287 case 'Duration': 3288 $aDurationData = $value; 3289 if (!is_array($aDurationData)) 3290 { 3291 break; 3292 } 3293 3294 $iValue = (((24 * $aDurationData['d']) + $aDurationData['h']) * 60 + $aDurationData['m']) * 60 + $aDurationData['s']; 3295 $this->Set($sAttCode, $iValue); 3296 $previousValue = $this->Get($sAttCode); 3297 if ($previousValue !== $iValue) 3298 { 3299 $this->Set($sAttCode, $iValue); 3300 } 3301 break; 3302 case 'CustomFields': 3303 $this->Set($sAttCode, $value); 3304 break; 3305 case 'LinkedSet': 3306 if ($this->IsValueModified($value)) 3307 { 3308 $oLinkSet = $this->Get($sAttCode); 3309 $sLinkedClass = $oAttDef->GetLinkedClass(); 3310 if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0)) 3311 { 3312 // Now handle the links to be created 3313 foreach ($value['to_be_created'] as $aData) 3314 { 3315 $sSubClass = $aData['class']; 3316 if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass))) 3317 { 3318 $aObjData = $aData['data']; 3319 $oLink = MetaModel::NewObject($sSubClass); 3320 $oLink->UpdateObjectFromArray($aObjData); 3321 $oLinkSet->AddItem($oLink); 3322 } 3323 } 3324 } 3325 if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0)) 3326 { 3327 // Now handle the links to be added by making the remote object point to self 3328 foreach ($value['to_be_added'] as $iObjKey) 3329 { 3330 $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); 3331 if ($oLink) 3332 { 3333 $oLinkSet->AddItem($oLink); 3334 } 3335 } 3336 } 3337 if (array_key_exists('to_be_modified', $value) && (count($value['to_be_modified']) > 0)) 3338 { 3339 // Now handle the links to be added by making the remote object point to self 3340 foreach ($value['to_be_modified'] as $iObjKey => $aData) 3341 { 3342 $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); 3343 if ($oLink) 3344 { 3345 $aObjData = $aData['data']; 3346 $oLink->UpdateObjectFromArray($aObjData); 3347 $oLinkSet->ModifyItem($oLink); 3348 } 3349 } 3350 } 3351 if (array_key_exists('to_be_removed', $value) && (count($value['to_be_removed']) > 0)) 3352 { 3353 foreach ($value['to_be_removed'] as $iObjKey) 3354 { 3355 $oLinkSet->RemoveItem($iObjKey); 3356 } 3357 } 3358 if (array_key_exists('to_be_deleted', $value) && (count($value['to_be_deleted']) > 0)) 3359 { 3360 foreach ($value['to_be_deleted'] as $iObjKey) 3361 { 3362 $oLinkSet->RemoveItem($iObjKey); 3363 } 3364 } 3365 $this->Set($sAttCode, $oLinkSet); 3366 } 3367 break; 3368 3369 case 'TagSet': 3370 /** @var ormTagSet $oTagSet */ 3371 $oTagSet = $this->Get($sAttCode); 3372 if (is_null($oTagSet)) 3373 { 3374 $oTagSet = new ormTagSet(get_class($this), $sAttCode, $oAttDef->GetMaxItems()); 3375 } 3376 $oTagSet->ApplyDelta($value); 3377 $this->Set($sAttCode, $oTagSet); 3378 break; 3379 3380 case 'Set': 3381 /** @var ormSet $oSet */ 3382 $oSet = $this->Get($sAttCode); 3383 if (is_null($oSet)) 3384 { 3385 $oSet = new ormSet(get_class($this), $sAttCode, $oAttDef->GetMaxItems()); 3386 } 3387 $oSet->ApplyDelta($value); 3388 $this->Set($sAttCode, $oSet); 3389 break; 3390 3391 default: 3392 if (!is_null($value)) 3393 { 3394 $aAttributes[$sAttCode] = trim($value); 3395 $previousValue = $this->Get($sAttCode); 3396 if ($previousValue !== $aAttributes[$sAttCode]) 3397 { 3398 $this->Set($sAttCode, $aAttributes[$sAttCode]); 3399 } 3400 } 3401 } 3402 } 3403 } 3404 3405 private function IsValueModified($value) 3406 { 3407 $aModifiedKeys = ['to_be_created', 'to_be_added', 'to_be_modified', 'to_be_removed', 'to_be_deleted']; 3408 foreach ($aModifiedKeys as $sModifiedKey) { 3409 if (array_key_exists( $sModifiedKey, $value) && (count($value[$sModifiedKey]) > 0)) 3410 { 3411 return true; 3412 } 3413 } 3414 return false; 3415 } 3416 3417 /** 3418 * Updates the object from the POSTed parameters (form) 3419 */ 3420 public function UpdateObjectFromPostedForm($sFormPrefix = '', $aAttList = null, $aAttFlags = array()) 3421 { 3422 if (is_null($aAttList)) 3423 { 3424 $aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this))); 3425 } 3426 $aValues = array(); 3427 foreach($aAttList as $sAttCode) 3428 { 3429 $value = $this->PrepareValueFromPostedForm($sFormPrefix, $sAttCode); 3430 if (!is_null($value)) 3431 { 3432 $aValues[$sAttCode] = $value; 3433 } 3434 } 3435 3436 $aErrors = array(); 3437 $aFinalValues = array(); 3438 foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $aAttFlags) as $sAttCode => $oAttDef) 3439 { 3440 $aFinalValues[$sAttCode] = $aValues[$sAttCode]; 3441 } 3442 try 3443 { 3444 $this->UpdateObjectFromArray($aFinalValues); 3445 } 3446 catch (CoreException $e) 3447 { 3448 $aErrors[] = $e->getMessage(); 3449 } 3450 if (!$this->IsNew()) // for new objects this is performed in DBInsertNoReload() 3451 { 3452 InlineImage::FinalizeInlineImages($this); 3453 } 3454 3455 // Invoke extensions after the update of the object from the form 3456 foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) 3457 { 3458 $oExtensionInstance->OnFormSubmit($this, $sFormPrefix); 3459 } 3460 3461 return $aErrors; 3462 } 3463 3464 /** 3465 * @param string $sFormPrefix 3466 * @param string $sAttCode 3467 * @param string $sClass Optional parameter, host object's class for the $sAttCode 3468 * @param array $aPostedData Optional parameter, used through recursive calls 3469 * 3470 * @return array|null 3471 * @throws \FileUploadException 3472 */ 3473 protected function PrepareValueFromPostedForm($sFormPrefix, $sAttCode, $sClass = null, $aPostedData = null) 3474 { 3475 if ($sClass === null) 3476 { 3477 $sClass = get_class($this); 3478 } 3479 3480 $value = null; 3481 3482 $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); 3483 switch ($oAttDef->GetEditClass()) 3484 { 3485 case 'Document': 3486 $value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents')); 3487 break; 3488 3489 case 'Image': 3490 $oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'); 3491 $aSize = utils::GetImageSize($oImage->GetData()); 3492 $oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'), 3493 $oAttDef->Get('storage_max_height')); 3494 $aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); 3495 if (is_array($aOtherData)) 3496 { 3497 $value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']); 3498 } 3499 else 3500 { 3501 $value = null; 3502 } 3503 break; 3504 3505 case 'RedundancySetting': 3506 $value = $oAttDef->ReadValueFromPostedForm($sFormPrefix); 3507 break; 3508 3509 case 'CustomFields': 3510 $value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix); 3511 break; 3512 3513 case 'LinkedSet': 3514 /** @var AttributeLinkedSet $oAttDef */ 3515 $aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 3516 'raw_data'), true); 3517 $aToBeCreated = array(); 3518 foreach($aRawToBeCreated as $aData) 3519 { 3520 $sSubFormPrefix = $aData['formPrefix']; 3521 $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); 3522 $aObjData = array(); 3523 foreach($aData as $sKey => $value) 3524 { 3525 if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) 3526 { 3527 $oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]); 3528 // Recursing over n:n link datetime attributes 3529 // Note: We might need to do it with other attribute types, like Document or redundancy setting. 3530 if ($oLinkAttDef instanceof AttributeDateTime) 3531 { 3532 $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, 3533 $aMatches[1], $sObjClass, $aData); 3534 } 3535 else 3536 { 3537 $aObjData[$aMatches[1]] = $value; 3538 } 3539 } 3540 } 3541 $aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData); 3542 } 3543 3544 $aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}', 3545 'raw_data'), true); 3546 $aToBeModified = array(); 3547 foreach($aRawToBeModified as $iObjKey => $aData) 3548 { 3549 $sSubFormPrefix = $aData['formPrefix']; 3550 $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); 3551 $aObjData = array(); 3552 foreach($aData as $sKey => $value) 3553 { 3554 if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) 3555 { 3556 $oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]); 3557 // Recursing over n:n link datetime attributes 3558 // Note: We might need to do it with other attribute types, like Document or redundancy setting. 3559 if ($oLinkAttDef instanceof AttributeDateTime) 3560 { 3561 $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, 3562 $aMatches[1], $sObjClass, $aData); 3563 } 3564 else 3565 { 3566 $aObjData[$aMatches[1]] = $value; 3567 } 3568 } 3569 } 3570 $aToBeModified[$iObjKey] = array('data' => $aObjData); 3571 } 3572 3573 $value = array( 3574 'to_be_created' => $aToBeCreated, 3575 'to_be_modified' => $aToBeModified, 3576 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 3577 'raw_data'), true), 3578 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 3579 'raw_data'), true), 3580 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 3581 'raw_data'), true), 3582 ); 3583 break; 3584 3585 case 'Set': 3586 case 'TagSet': 3587 $sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); 3588 $value = json_decode($sTagSetJson, true); 3589 break; 3590 3591 default: 3592 if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime 3593 { 3594 // Retrieving value from array when present (means what we are in a recursion) 3595 if ($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) 3596 { 3597 $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; 3598 } 3599 else 3600 { 3601 $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); 3602 } 3603 3604 if ($value != null) 3605 { 3606 $oDate = $oAttDef->GetFormat()->Parse($value); 3607 if ($oDate instanceof DateTime) 3608 { 3609 $value = $oDate->format($oAttDef->GetInternalFormat()); 3610 } 3611 else 3612 { 3613 $value = null; 3614 } 3615 } 3616 } 3617 else 3618 { 3619 // Retrieving value from array when present (means what we are in a recursion) 3620 if ($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) 3621 { 3622 $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; 3623 } 3624 else 3625 { 3626 $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); 3627 } 3628 } 3629 break; 3630 } 3631 3632 return $value; 3633 } 3634 3635 /** 3636 * Updates the object from a given page argument 3637 */ 3638 public function UpdateObjectFromArg($sArgName, $aAttList = null, $aAttFlags = array()) 3639 { 3640 if (is_null($aAttList)) 3641 { 3642 $aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this))); 3643 } 3644 $aRawValues = utils::ReadParam($sArgName, array(), '', 'raw_data'); 3645 $aValues = array(); 3646 foreach($aAttList as $sAttCode) 3647 { 3648 if (isset($aRawValues[$sAttCode])) 3649 { 3650 $aValues[$sAttCode] = $aRawValues[$sAttCode]; 3651 } 3652 } 3653 3654 $aErrors = array(); 3655 $aFinalValues = array(); 3656 foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $aAttFlags) as $sAttCode => $oAttDef) 3657 { 3658 if ($oAttDef->IsLinkSet()) 3659 { 3660 $aFinalValues[$sAttCode] = json_decode($aValues[$sAttCode], true); 3661 } 3662 else 3663 { 3664 $aFinalValues[$sAttCode] = $aValues[$sAttCode]; 3665 } 3666 } 3667 try 3668 { 3669 $this->UpdateObjectFromArray($aFinalValues); 3670 } 3671 catch (CoreException $e) 3672 { 3673 $aErrors[] = $e->getMessage(); 3674 } 3675 return $aErrors; 3676 } 3677 3678 /** 3679 * @inheritdoc 3680 */ 3681 public function DBInsertNoReload() 3682 { 3683 $res = parent::DBInsertNoReload(); 3684 3685 $this->SetWarningsAsSessionMessages('create'); 3686 3687 // Invoke extensions after insertion (the object must exist, have an id, etc.) 3688 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3689 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3690 { 3691 $oExtensionInstance->OnDBInsert($this, self::GetCurrentChange()); 3692 } 3693 3694 return $res; 3695 } 3696 3697 /** 3698 * @inheritdoc 3699 * Attaches InlineImages to the current object 3700 */ 3701 protected function OnObjectKeyReady() 3702 { 3703 InlineImage::FinalizeInlineImages($this); 3704 } 3705 3706 protected function DBCloneTracked_Internal($newKey = null) 3707 { 3708 $oNewObj = parent::DBCloneTracked_Internal($newKey); 3709 3710 // Invoke extensions after insertion (the object must exist, have an id, etc.) 3711 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3712 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3713 { 3714 $oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange()); 3715 } 3716 3717 return $oNewObj; 3718 } 3719 3720 public function DBUpdate() 3721 { 3722 $res = parent::DBUpdate(); 3723 3724 $this->SetWarningsAsSessionMessages('update'); 3725 3726 // Protection against reentrance (e.g. cascading the update of ticket logs) 3727 // Note: This is based on the fix made on r 3190 in DBObject::DBUpdate() 3728 static $aUpdateReentrance = array(); 3729 $sKey = get_class($this).'::'.$this->GetKey(); 3730 if (array_key_exists($sKey, $aUpdateReentrance)) 3731 { 3732 return $res; 3733 } 3734 $aUpdateReentrance[$sKey] = true; 3735 3736 try 3737 { 3738 // Invoke extensions after the update (could be before) 3739 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3740 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3741 { 3742 $oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange()); 3743 } 3744 } catch (Exception $e) 3745 { 3746 unset($aUpdateReentrance[$sKey]); 3747 throw $e; 3748 } 3749 3750 unset($aUpdateReentrance[$sKey]); 3751 3752 return $res; 3753 } 3754 3755 /** 3756 * @param string $sMessageIdPrefix 3757 * 3758 * @since 2.6 3759 */ 3760 protected function SetWarningsAsSessionMessages($sMessageIdPrefix) 3761 { 3762 if (!empty($this->m_aCheckWarnings) && is_array($this->m_aCheckWarnings)) 3763 { 3764 $iMsgNb = 0; 3765 foreach ($this->m_aCheckWarnings as $sWarningMessage) 3766 { 3767 $iMsgNb++; 3768 $sMessageId = "$sMessageIdPrefix-$iMsgNb"; // each message must have its own messageId ! 3769 $this->SetSessionMessageFromInstance($sMessageId, $sWarningMessage, 'info', 0); 3770 } 3771 } 3772 } 3773 3774 protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) 3775 { 3776 // Todo - invoke the extension 3777 return parent::BulkUpdateTracked_Internal($oFilter, $aValues); 3778 } 3779 3780 protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) 3781 { 3782 // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information 3783 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3784 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3785 { 3786 $oExtensionInstance->OnDBDelete($this, self::GetCurrentChange()); 3787 } 3788 3789 return parent::DBDeleteTracked_Internal($oDeletionPlan); 3790 } 3791 3792 public function IsModified() 3793 { 3794 if (parent::IsModified()) 3795 { 3796 return true; 3797 } 3798 3799 // Plugins 3800 // 3801 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3802 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3803 { 3804 if ($oExtensionInstance->OnIsModified($this)) 3805 { 3806 return true; 3807 } 3808 } 3809 3810 return false; 3811 } 3812 3813 /** 3814 * Bypass the check of the user rights when writing this object 3815 * 3816 * @param bool $bAllow True to bypass the checks, false to restore the default behavior 3817 */ 3818 public function AllowWrite($bAllow = true) 3819 { 3820 $this->bAllowWrite = $bAllow; 3821 } 3822 3823 /** 3824 * @inheritdoc 3825 * @throws \ArchivedObjectException 3826 * @throws \CoreException 3827 * @throws \OQLException 3828 */ 3829 public function DoCheckToWrite() 3830 { 3831 parent::DoCheckToWrite(); 3832 3833 // Plugins 3834 // 3835 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3836 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3837 { 3838 $aNewIssues = $oExtensionInstance->OnCheckToWrite($this); 3839 if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array 3840 { 3841 $this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues); 3842 } 3843 } 3844 3845 // User rights 3846 // 3847 if (!$this->bAllowWrite) 3848 { 3849 $aChanges = $this->ListChanges(); 3850 if (count($aChanges) > 0) 3851 { 3852 $aForbiddenFields = array(); 3853 foreach($this->ListChanges() as $sAttCode => $value) 3854 { 3855 $bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, 3856 UR_ACTION_MODIFY, DBObjectSet::FromObject($this)); 3857 if (!$bUpdateAllowed) 3858 { 3859 $oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode); 3860 $aForbiddenFields[] = $oAttCode->GetLabel(); 3861 } 3862 } 3863 if (count($aForbiddenFields) > 0) 3864 { 3865 // Security issue 3866 $this->m_bSecurityIssue = true; 3867 $this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields', 3868 implode(', ', $aForbiddenFields)); 3869 } 3870 } 3871 } 3872 } 3873 3874 protected function DoCheckToDelete(&$oDeletionPlan) 3875 { 3876 parent::DoCheckToDelete($oDeletionPlan); 3877 3878 // Plugins 3879 // 3880 /** @var \iApplicationObjectExtension $oExtensionInstance */ 3881 foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) 3882 { 3883 $aNewIssues = $oExtensionInstance->OnCheckToDelete($this); 3884 if (is_array($aNewIssues) && count($aNewIssues) > 0) 3885 { 3886 $this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues); 3887 } 3888 } 3889 3890 // User rights 3891 // 3892 $bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, 3893 DBObjectSet::FromObject($this)); 3894 if (!$bDeleteAllowed) 3895 { 3896 // Security issue 3897 $this->m_bSecurityIssue = true; 3898 $this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete'); 3899 } 3900 } 3901 3902 /** 3903 * Special display where the case log uses the whole "screen" at the bottom of the "Properties" tab 3904 */ 3905 public function DisplayCaseLog(WebPage $oPage, $sAttCode, $sComment = '', $sPrefix = '', $bEditMode = false) 3906 { 3907 $oPage->SetCurrentTab(Dict::S('UI:PropertiesTab')); 3908 $sClass = get_class($this); 3909 if ($this->IsNew()) 3910 { 3911 $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); 3912 } 3913 else 3914 { 3915 $iFlags = $this->GetAttributeFlags($sAttCode); 3916 } 3917 if ($iFlags & OPT_ATT_HIDDEN) 3918 { 3919 // The case log is hidden do nothing 3920 } 3921 else 3922 { 3923 $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); 3924 $sInputId = $this->m_iFormId.'_'.$sAttCode; 3925 3926 if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE))) 3927 { 3928 // Check if the attribute is not read-only because of a synchro... 3929 $sSynchroIcon = ''; 3930 if ($iFlags & OPT_ATT_SLAVE) 3931 { 3932 $aReasons = array(); 3933 $iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons); 3934 $sSynchroIcon = " <img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>"; 3935 $sTip = ''; 3936 foreach($aReasons as $aRow) 3937 { 3938 $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); 3939 $sDescription = str_replace(array("\r\n", "\n"), "<br/>", $sDescription); 3940 $sTip .= "<div class='synchro-source'>"; 3941 $sTip .= "<div class='synchro-source-title'>Synchronized with {$aRow['name']}</div>"; 3942 $sTip .= "<div class='synchro-source-description'>$sDescription</div>"; 3943 } 3944 $oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"); 3945 } 3946 3947 // Attribute is read-only 3948 $sHTMLValue = $this->GetAsHTML($sAttCode); 3949 $sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->GetEditValue($sAttCode), 3950 ENT_QUOTES, 'UTF-8').'"/>'; 3951 $aFieldsMap[$sAttCode] = $sInputId; 3952 $sComment .= $sSynchroIcon; 3953 } 3954 else 3955 { 3956 $sValue = $this->Get($sAttCode); 3957 $sDisplayValue = $this->GetEditValue($sAttCode); 3958 $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); 3959 $sHTMLValue = ''; 3960 if ($sComment != '') 3961 { 3962 $sHTMLValue = '<span>'.$sComment.'</span><br/>'; 3963 } 3964 $sHTMLValue .= "<span style=\"font-family:Tahoma,Verdana,Arial,Helvetica;font-size:12px;\" id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, 3965 $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, 3966 $aArgs).'</span>'; 3967 $aFieldsMap[$sAttCode] = $sInputId; 3968 } 3969 $oPage->add('<fieldset><legend>'.$oAttDef->GetLabel().'</legend>'); 3970 $oPage->add($sHTMLValue); 3971 $oPage->add('</fieldset>'); 3972 } 3973 } 3974 3975 /** 3976 * @param $sCurrentState 3977 * @param $sStimulus 3978 * @param $bOnlyNewOnes 3979 * 3980 * @return array 3981 * @throws \ApplicationException 3982 * @throws \CoreException 3983 * @deprecated Since iTop 2.4, use DBObject::GetTransitionAttributes() instead. 3984 */ 3985 public function GetExpectedAttributes($sCurrentState, $sStimulus, $bOnlyNewOnes) 3986 { 3987 $aTransitions = $this->EnumTransitions(); 3988 $aStimuli = MetaModel::EnumStimuli(get_class($this)); 3989 if (!isset($aTransitions[$sStimulus])) 3990 { 3991 // Invalid stimulus 3992 throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, 3993 $this->GetName(), $this->GetStateLabel())); 3994 } 3995 $aTransition = $aTransitions[$sStimulus]; 3996 $sTargetState = $aTransition['target_state']; 3997 $aTargetStates = MetaModel::EnumStates(get_class($this)); 3998 $aTargetState = $aTargetStates[$sTargetState]; 3999 $aCurrentState = $aTargetStates[$this->GetState()]; 4000 $aExpectedAttributes = $aTargetState['attribute_list']; 4001 $aCurrentAttributes = $aCurrentState['attribute_list']; 4002 4003 $aComputedAttributes = array(); 4004 foreach($aExpectedAttributes as $sAttCode => $iExpectCode) 4005 { 4006 if (!array_key_exists($sAttCode, $aCurrentAttributes)) 4007 { 4008 $aComputedAttributes[$sAttCode] = $iExpectCode; 4009 } 4010 else 4011 { 4012 if (!($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))) 4013 { 4014 $iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT | OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags 4015 } 4016 // Later: better check if the attribute is not *null* 4017 if (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) != '')) 4018 { 4019 $iExpectCode = $iExpectCode & ~(OPT_ATT_MANDATORY); // If the attribute is present, then no need to request its presence 4020 } 4021 4022 $aComputedAttributes[$sAttCode] = $iExpectCode; 4023 } 4024 4025 $aComputedAttributes[$sAttCode] = $aComputedAttributes[$sAttCode] & ~(OPT_ATT_READONLY | OPT_ATT_HIDDEN); // Don't care about this form now 4026 4027 if ($aComputedAttributes[$sAttCode] == 0) 4028 { 4029 unset($aComputedAttributes[$sAttCode]); 4030 } 4031 } 4032 4033 return $aComputedAttributes; 4034 } 4035 4036 /** 4037 * Display a form for modifying several objects at once 4038 * The form will be submitted to the current page, with the specified additional values 4039 * 4040 * @param \iTopWebPage $oP 4041 * @param string $sClass 4042 * @param array $aSelectedObj 4043 * @param string $sCustomOperation 4044 * @param string $sCancelUrl 4045 * @param array $aExcludeAttributes 4046 * @param array $aContextData 4047 * 4048 * @throws \CoreException 4049 * @throws \CoreUnexpectedValue 4050 * @throws \MySQLException 4051 * @throws \OQLException 4052 */ 4053 public static function DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, $sCustomOperation, $sCancelUrl, $aExcludeAttributes = array(), $aContextData = array()) 4054 { 4055 if (count($aSelectedObj) > 0) 4056 { 4057 $iAllowedCount = count($aSelectedObj); 4058 $sSelectedObj = implode(',', $aSelectedObj); 4059 4060 $sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")"; 4061 $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL)); 4062 4063 // Compute the distribution of the values for each field to determine which of the "scalar" fields are homogeneous 4064 $aList = MetaModel::ListAttributeDefs($sClass); 4065 $aValues = array(); 4066 foreach($aList as $sAttCode => $oAttDef) 4067 { 4068 if ($oAttDef->IsScalar()) 4069 { 4070 $aValues[$sAttCode] = array(); 4071 } 4072 } 4073 while ($oObj = $oSet->Fetch()) 4074 { 4075 foreach($aList as $sAttCode => $oAttDef) 4076 { 4077 if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) 4078 { 4079 $currValue = $oObj->Get($sAttCode); 4080 if ($oAttDef instanceof AttributeCaseLog) 4081 { 4082 $currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059. 4083 } 4084 elseif ($currValue instanceof ormSet) 4085 { 4086 $currValue = $oAttDef->GetEditValue($currValue, $oObj); 4087 } 4088 if (is_object($currValue)) 4089 { 4090 continue; 4091 } // Skip non scalar values... 4092 if (!array_key_exists($currValue, $aValues[$sAttCode])) 4093 { 4094 $aValues[$sAttCode][$currValue] = array( 4095 'count' => 1, 4096 'display' => $oObj->GetAsHTML($sAttCode), 4097 ); 4098 } 4099 else 4100 { 4101 $aValues[$sAttCode][$currValue]['count']++; 4102 } 4103 } 4104 } 4105 } 4106 // Now create an object that has values for the homogeneous values only 4107 /** @var \cmdbAbstractObject $oDummyObj */ 4108 $oDummyObj = new $sClass(); // @@ What if the class is abstract ? 4109 $aComments = array(); 4110 function MyComparison($a, $b) // Sort descending 4111 { 4112 if ($a['count'] == $b['count']) 4113 { 4114 return 0; 4115 } 4116 4117 return ($a['count'] > $b['count']) ? -1 : 1; 4118 } 4119 4120 $iFormId = cmdbAbstractObject::GetNextFormId(); // Identifier that prefixes all the form fields 4121 $sReadyScript = ''; 4122 $sFormPrefix = '2_'; 4123 foreach($aList as $sAttCode => $oAttDef) 4124 { 4125 $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, 4126 $sAttCode); // List of attributes that are needed for the current one 4127 if (count($aPrerequisites) > 0) 4128 { 4129 // When 'enabling' a field, all its prerequisites must be enabled too 4130 $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']"; 4131 $oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); 4132 } 4133 $aDependents = MetaModel::GetDependentAttributes($sClass, 4134 $sAttCode); // List of attributes that are needed for the current one 4135 if (count($aDependents) > 0) 4136 { 4137 // When 'disabling' a field, all its dependent fields must be disabled too 4138 $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']"; 4139 $oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); 4140 } 4141 if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) 4142 { 4143 if ($oAttDef->GetEditClass() == 'One Way Password') 4144 { 4145 4146 $sTip = "Unknown values"; 4147 $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"; 4148 4149 $oDummyObj->Set($sAttCode, null); 4150 $aComments[$sAttCode] = '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>'; 4151 $aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'"> ? </div>'; 4152 $sReadyScript .= 'ToggleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n"; 4153 } 4154 else 4155 { 4156 $iCount = count($aValues[$sAttCode]); 4157 if ($iCount == 1) 4158 { 4159 // Homogeneous value 4160 reset($aValues[$sAttCode]); 4161 $aKeys = array_keys($aValues[$sAttCode]); 4162 $currValue = $aKeys[0]; // The only value is the first key 4163 //echo "<p>current value for $sAttCode : $currValue</p>"; 4164 $oDummyObj->Set($sAttCode, $currValue); 4165 $aComments[$sAttCode] = ''; 4166 if ($sAttCode != MetaModel::GetStateAttributeCode($sClass)) 4167 { 4168 $aComments[$sAttCode] .= '<input type="checkbox" checked id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>'; 4169 } 4170 $aComments[$sAttCode] .= '<div class="mono_value">1</div>'; 4171 } 4172 else 4173 { 4174 // Non-homogeneous value 4175 $aMultiValues = $aValues[$sAttCode]; 4176 uasort($aMultiValues, 'MyComparison'); 4177 $iMaxCount = 5; 4178 $sTip = "<p><b>".Dict::Format('UI:BulkModify_Count_DistinctValues', $iCount)."</b><ul>"; 4179 $index = 0; 4180 foreach($aMultiValues as $sCurrValue => $aVal) 4181 { 4182 $sDisplayValue = empty($aVal['display']) ? '<i>'.Dict::S('Enum:Undefined').'</i>' : str_replace(array( 4183 "\n", 4184 "\r", 4185 ), " ", $aVal['display']); 4186 $sTip .= "<li>".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, 4187 $aVal['count'])."</li>"; 4188 $index++; 4189 if ($iMaxCount == $index) 4190 { 4191 $sTip .= "<li>".Dict::Format('UI:BulkModify:N_MoreValues', 4192 count($aMultiValues) - $iMaxCount)."</li>"; 4193 break; 4194 } 4195 } 4196 $sTip .= "</ul></p>"; 4197 $sTip = addslashes($sTip); 4198 $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"; 4199 4200 if (($oAttDef->GetEditClass() == 'TagSet') || ($oAttDef->GetEditClass() == 'Set')) 4201 { 4202 // Set the value by adding the values to the first one 4203 reset($aMultiValues); 4204 $aKeys = array_keys($aMultiValues); 4205 $currValue = $aKeys[0]; 4206 $oDummyObj->Set($sAttCode, $currValue); 4207 /** @var ormTagSet $oTagSet */ 4208 $oTagSet = $oDummyObj->Get($sAttCode); 4209 $oTagSet->SetDisplayPartial(true); 4210 foreach($aKeys as $iIndex => $sValues) 4211 { 4212 if ($iIndex == 0) 4213 { 4214 continue; 4215 } 4216 $aTagCodes = $oAttDef->FromStringToArray($sValues); 4217 $oTagSet->GenerateDiffFromArray($aTagCodes); 4218 } 4219 $oDummyObj->Set($sAttCode, $oTagSet); 4220 } 4221 else 4222 { 4223 $oDummyObj->Set($sAttCode, null); 4224 } 4225 $aComments[$sAttCode] = ''; 4226 if ($sAttCode != MetaModel::GetStateAttributeCode($sClass)) 4227 { 4228 $aComments[$sAttCode] .= '<input type="checkbox" id="enable_'.$iFormId.'_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$iFormId.'_'.$sAttCode.'\')"/>'; 4229 } 4230 $aComments[$sAttCode] .= '<div class="multi_values" id="multi_values_'.$sAttCode.'">'.$iCount.'</div>'; 4231 } 4232 $sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n"; 4233 } 4234 } 4235 } 4236 4237 $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); 4238 if (($sStateAttCode != '') && ($oDummyObj->GetState() == '')) 4239 { 4240 // Hmmm, it's not gonna work like this ! Set a default value for the "state" 4241 // Maybe we should use the "state" that is the most common among the objects... 4242 $aMultiValues = $aValues[$sStateAttCode]; 4243 uasort($aMultiValues, 'MyComparison'); 4244 foreach($aMultiValues as $sCurrValue => $aVal) 4245 { 4246 $oDummyObj->Set($sStateAttCode, $sCurrValue); 4247 break; 4248 } 4249 //$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode); 4250 //$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue()); 4251 } 4252 $oP->add("<div class=\"page_header\">\n"); 4253 $oP->add("<h1>".$oDummyObj->GetIcon()." ".Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', 4254 $iAllowedCount, $sClass, $iAllowedCount)."</h1>\n"); 4255 $oP->add("</div>\n"); 4256 4257 $oP->add("<div class=\"wizContainer\">\n"); 4258 $sDisableFields = json_encode($aExcludeAttributes); 4259 4260 $aParams = array 4261 ( 4262 'fieldsComments' => $aComments, 4263 'noRelations' => true, 4264 'custom_operation' => $sCustomOperation, 4265 'custom_button' => Dict::S('UI:Button:PreviewModifications'), 4266 'selectObj' => $sSelectedObj, 4267 'preview_mode' => true, 4268 'disabled_fields' => $sDisableFields, 4269 'disable_plugins' => true, 4270 ); 4271 $aParams = $aParams + $aContextData; // merge keeping associations 4272 4273 $oDummyObj->DisplayModifyForm($oP, $aParams); 4274 $oP->add("</div>\n"); 4275 $oP->add_ready_script($sReadyScript); 4276 $oP->add_ready_script( 4277 <<<EOF 4278$('.wizContainer button.cancel').unbind('click'); 4279$('.wizContainer button.cancel').click( function() { window.location.href = '$sCancelUrl'; } ); 4280EOF 4281 ); 4282 4283 } // Else no object selected ??? 4284 else 4285 { 4286 $oP->p("No object selected !, nothing to do"); 4287 } 4288 } 4289 4290 /** 4291 * Process the reply made from a form built with DisplayBulkModifyForm 4292 */ 4293 public static function DoBulkModify($oP, $sClass, $aSelectedObj, $sCustomOperation, $bPreview, $sCancelUrl, $aContextData = array()) 4294 { 4295 $aHeaders = array( 4296 'form::select' => array( 4297 'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList:not(:disabled)', this.checked);\"></input>", 4298 'description' => Dict::S('UI:SelectAllToggle+'), 4299 ), 4300 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), 4301 'status' => array( 4302 'label' => Dict::S('UI:BulkModifyStatus'), 4303 'description' => Dict::S('UI:BulkModifyStatus+'), 4304 ), 4305 'errors' => array( 4306 'label' => Dict::S('UI:BulkModifyErrors'), 4307 'description' => Dict::S('UI:BulkModifyErrors+'), 4308 ), 4309 ); 4310 $aRows = array(); 4311 4312 $oP->add("<div class=\"page_header\">\n"); 4313 $oP->add("<h1>".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Modify_N_ObjectsOf_Class', 4314 count($aSelectedObj), MetaModel::GetName($sClass))."</h1>\n"); 4315 $oP->add("</div>\n"); 4316 $oP->set_title(Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), $sClass)); 4317 if (!$bPreview) 4318 { 4319 // Not in preview mode, do the update for real 4320 $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); 4321 if (!utils::IsTransactionValid($sTransactionId, false)) 4322 { 4323 throw new Exception(Dict::S('UI:Error:ObjectAlreadyUpdated')); 4324 } 4325 utils::RemoveTransaction($sTransactionId); 4326 } 4327 $iPreviousTimeLimit = ini_get('max_execution_time'); 4328 $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); 4329 foreach($aSelectedObj as $iId) 4330 { 4331 set_time_limit($iLoopTimeLimit); 4332 /** @var \cmdbAbstractObject $oObj */ 4333 $oObj = MetaModel::GetObject($sClass, $iId); 4334 $aErrors = $oObj->UpdateObjectFromPostedForm(''); 4335 $bResult = (count($aErrors) == 0); 4336 if ($bResult) 4337 { 4338 list($bResult, $aErrors) = $oObj->CheckToWrite(); 4339 } 4340 if ($bPreview) 4341 { 4342 $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusOk') : Dict::S('UI:BulkModifyStatusError'); 4343 } 4344 else 4345 { 4346 $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); 4347 } 4348 $sCSSClass = $bResult ? HILIGHT_CLASS_NONE : HILIGHT_CLASS_CRITICAL; 4349 $sChecked = $bResult ? 'checked' : ''; 4350 $sDisabled = $bResult ? '' : 'disabled'; 4351 $aRows[] = array( 4352 'form::select' => "<input type=\"checkbox\" class=\"selectList\" $sChecked $sDisabled\"></input>", 4353 'object' => $oObj->GetHyperlink(), 4354 'status' => $sStatus, 4355 'errors' => '<p>'.($bResult ? '' : implode('</p><p>', $aErrors)).'</p>', 4356 '@class' => $sCSSClass, 4357 ); 4358 if ($bResult && (!$bPreview)) 4359 { 4360 $oObj->DBUpdate(); 4361 } 4362 } 4363 set_time_limit($iPreviousTimeLimit); 4364 $oP->Table($aHeaders, $aRows); 4365 if ($bPreview) 4366 { 4367 $sFormAction = utils::GetAbsoluteUrlAppRoot().'pages/UI.php'; // No parameter in the URL, the only parameter will be the ones passed through the form 4368 // Form to submit: 4369 $oP->add("<form method=\"post\" action=\"$sFormAction\" enctype=\"multipart/form-data\">\n"); 4370 $aDefaults = utils::ReadParam('default', array()); 4371 $oAppContext = new ApplicationContext(); 4372 $oP->add($oAppContext->GetForForm()); 4373 foreach($aContextData as $sKey => $value) 4374 { 4375 $oP->add("<input type=\"hidden\" name=\"{$sKey}\" value=\"$value\">\n"); 4376 } 4377 $oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sCustomOperation\">\n"); 4378 $oP->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n"); 4379 $oP->add("<input type=\"hidden\" name=\"preview_mode\" value=\"0\">\n"); 4380 $oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n"); 4381 $oP->add("<button type=\"button\" class=\"action cancel\" onClick=\"window.location.href='$sCancelUrl'\">".Dict::S('UI:Button:Cancel')."</button> \n"); 4382 $oP->add("<button type=\"submit\" class=\"action\"><span>".Dict::S('UI:Button:ModifyAll')."</span></button>\n"); 4383 foreach($_POST as $sKey => $value) 4384 { 4385 if (preg_match('/attr_(.+)/', $sKey, $aMatches)) 4386 { 4387 // Beware: some values (like durations) are passed as arrays 4388 if (is_array($value)) 4389 { 4390 foreach($value as $vKey => $vValue) 4391 { 4392 $oP->add("<input type=\"hidden\" name=\"{$sKey}[$vKey]\" value=\"".htmlentities($vValue, 4393 ENT_QUOTES, 'UTF-8')."\">\n"); 4394 } 4395 } 4396 else 4397 { 4398 $oP->add("<input type=\"hidden\" name=\"$sKey\" value=\"".htmlentities($value, ENT_QUOTES, 4399 'UTF-8')."\">\n"); 4400 } 4401 } 4402 } 4403 $oP->add("</form>\n"); 4404 } 4405 else 4406 { 4407 $oP->add("<button type=\"button\" onClick=\"window.location.href='$sCancelUrl'\" class=\"action\"><span>".Dict::S('UI:Button:Done')."</span></button>\n"); 4408 } 4409 } 4410 4411 /** 4412 * Perform all the needed checks to delete one (or more) objects 4413 * 4414 * @param \WebPage $oP 4415 * @param $sClass 4416 * @param $aObjects 4417 * @param $bPreview 4418 * @param $sCustomOperation 4419 * @param array $aContextData 4420 * 4421 * @throws \CoreException 4422 * @throws \DictExceptionMissingString 4423 */ 4424 public static function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bPreview, $sCustomOperation, $aContextData = array()) 4425 { 4426 $oDeletionPlan = new DeletionPlan(); 4427 4428 foreach($aObjects as $oObj) 4429 { 4430 if ($bPreview) 4431 { 4432 $oObj->CheckToDelete($oDeletionPlan); 4433 } 4434 else 4435 { 4436 $oObj->DBDeleteTracked(CMDBObject::GetCurrentChange(), null, $oDeletionPlan); 4437 } 4438 } 4439 4440 if ($bPreview) 4441 { 4442 if (count($aObjects) == 1) 4443 { 4444 $oObj = $aObjects[0]; 4445 $oP->add("<h1>".Dict::Format('UI:Delete:ConfirmDeletionOf_Name', $oObj->GetName())."</h1>\n"); 4446 } 4447 else 4448 { 4449 $oP->add("<h1>".Dict::Format('UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class', count($aObjects), 4450 MetaModel::GetName($sClass))."</h1>\n"); 4451 } 4452 // Explain what should be done 4453 // 4454 $aDisplayData = array(); 4455 foreach($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) 4456 { 4457 foreach($aDeletes as $iId => $aData) 4458 { 4459 $oToDelete = $aData['to_delete']; 4460 $bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO)); 4461 if (array_key_exists('issue', $aData)) 4462 { 4463 if ($bAutoDel) 4464 { 4465 if (isset($aData['requested_explicitely'])) 4466 { 4467 $sConsequence = Dict::Format('UI:Delete:CannotDeleteBecause', $aData['issue']); 4468 } 4469 else 4470 { 4471 $sConsequence = Dict::Format('UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible', 4472 $aData['issue']); 4473 } 4474 } 4475 else 4476 { 4477 $sConsequence = Dict::Format('UI:Delete:MustBeDeletedManuallyButNotPossible', 4478 $aData['issue']); 4479 } 4480 } 4481 else 4482 { 4483 if ($bAutoDel) 4484 { 4485 if (isset($aData['requested_explicitely'])) 4486 { 4487 $sConsequence = ''; // not applicable 4488 } 4489 else 4490 { 4491 $sConsequence = Dict::S('UI:Delete:WillBeDeletedAutomatically'); 4492 } 4493 } 4494 else 4495 { 4496 $sConsequence = Dict::S('UI:Delete:MustBeDeletedManually'); 4497 } 4498 } 4499 $aDisplayData[] = array( 4500 'class' => MetaModel::GetName(get_class($oToDelete)), 4501 'object' => $oToDelete->GetHyperLink(), 4502 'consequence' => $sConsequence, 4503 ); 4504 } 4505 } 4506 foreach($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate) 4507 { 4508 foreach($aToUpdate as $iId => $aData) 4509 { 4510 $oToUpdate = $aData['to_reset']; 4511 if (array_key_exists('issue', $aData)) 4512 { 4513 $sConsequence = Dict::Format('UI:Delete:CannotUpdateBecause_Issue', $aData['issue']); 4514 } 4515 else 4516 { 4517 $sConsequence = Dict::Format('UI:Delete:WillAutomaticallyUpdate_Fields', 4518 $aData['attributes_list']); 4519 } 4520 $aDisplayData[] = array( 4521 'class' => MetaModel::GetName(get_class($oToUpdate)), 4522 'object' => $oToUpdate->GetHyperLink(), 4523 'consequence' => $sConsequence, 4524 ); 4525 } 4526 } 4527 4528 $iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects); 4529 if ($iImpactedIndirectly > 0) 4530 { 4531 if (count($aObjects) == 1) 4532 { 4533 $oObj = $aObjects[0]; 4534 $oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly, 4535 $oObj->GetName())); 4536 } 4537 else 4538 { 4539 $oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencingTheObjects', $iImpactedIndirectly)); 4540 } 4541 $oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity')); 4542 } 4543 4544 if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper()) 4545 { 4546 $aDisplayConfig = array(); 4547 $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); 4548 $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); 4549 $aDisplayConfig['consequence'] = array( 4550 'label' => 'Consequence', 4551 'description' => Dict::S('UI:Delete:Consequence+'), 4552 ); 4553 $oP->table($aDisplayConfig, $aDisplayData); 4554 } 4555 4556 if ($oDeletionPlan->FoundStopper()) 4557 { 4558 if ($oDeletionPlan->FoundSecurityIssue()) 4559 { 4560 $oP->p(Dict::S('UI:Delete:SorryDeletionNotAllowed')); 4561 } 4562 elseif ($oDeletionPlan->FoundManualOperation()) 4563 { 4564 $oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations')); 4565 } 4566 else // $bFoundManualOp 4567 { 4568 $oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations')); 4569 } 4570 $oAppContext = new ApplicationContext(); 4571 $oP->add("<form method=\"post\">\n"); 4572 $oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::ReadParam('transaction_id', '', false, 4573 'transaction_id') 4574 ."\">\n"); 4575 $oP->add("<input type=\"button\" onclick=\"window.history.back();\" value=\"".Dict::S('UI:Button:Back')."\">\n"); 4576 $oP->add("<input DISABLED type=\"submit\" name=\"\" value=\"".Dict::S('UI:Button:Delete')."\">\n"); 4577 $oP->add($oAppContext->GetForForm()); 4578 $oP->add("</form>\n"); 4579 } 4580 else 4581 { 4582 if (count($aObjects) == 1) 4583 { 4584 $oObj = $aObjects[0]; 4585 $id = $oObj->GetKey(); 4586 $oP->p('<h1>'.Dict::Format('UI:Delect:Confirm_Object', $oObj->GetHyperLink()).'</h1>'); 4587 } 4588 else 4589 { 4590 $oP->p('<h1>'.Dict::Format('UI:Delect:Confirm_Count_ObjectsOf_Class', count($aObjects), 4591 MetaModel::GetName($sClass)).'</h1>'); 4592 } 4593 foreach($aObjects as $oObj) 4594 { 4595 $aKeys[] = $oObj->GetKey(); 4596 } 4597 $oFilter = new DBObjectSearch($sClass); 4598 $oFilter->AddCondition('id', $aKeys, 'IN'); 4599 $oSet = new CMDBobjectSet($oFilter); 4600 $oP->add('<div id="0">'); 4601 CMDBAbstractObject::DisplaySet($oP, $oSet, array('display_limit' => false, 'menu' => false)); 4602 $oP->add("</div>\n"); 4603 $oP->add("<form method=\"post\">\n"); 4604 foreach($aContextData as $sKey => $value) 4605 { 4606 $oP->add("<input type=\"hidden\" name=\"{$sKey}\" value=\"$value\">\n"); 4607 } 4608 $oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n"); 4609 $oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sCustomOperation\">\n"); 4610 $oP->add("<input type=\"hidden\" name=\"filter\" value=\"".htmlentities($oFilter->Serialize(), ENT_QUOTES, 4611 'UTF-8')."\">\n"); 4612 $oP->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n"); 4613 foreach($aObjects as $oObj) 4614 { 4615 $oP->add("<input type=\"hidden\" name=\"selectObject[]\" value=\"".$oObj->GetKey()."\">\n"); 4616 } 4617 $oP->add("<input type=\"button\" onclick=\"window.history.back();\" value=\"".Dict::S('UI:Button:Back')."\">\n"); 4618 $oP->add("<input type=\"submit\" name=\"\" value=\"".Dict::S('UI:Button:Delete')."\">\n"); 4619 $oAppContext = new ApplicationContext(); 4620 $oP->add($oAppContext->GetForForm()); 4621 $oP->add("</form>\n"); 4622 } 4623 } 4624 else // if ($bPreview)... 4625 { 4626 // Execute the deletion 4627 // 4628 if (count($aObjects) == 1) 4629 { 4630 $oObj = $aObjects[0]; 4631 $oP->add("<h1>".Dict::Format('UI:Title:DeletionOf_Object', $oObj->GetName())."</h1>\n"); 4632 } 4633 else 4634 { 4635 $oP->add("<h1>".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), 4636 MetaModel::GetName($sClass))."</h1>\n"); 4637 } 4638 // Security - do not allow the user to force a forbidden delete by the mean of page arguments... 4639 if ($oDeletionPlan->FoundSecurityIssue()) 4640 { 4641 throw new CoreException(Dict::S('UI:Error:NotEnoughRightsToDelete')); 4642 } 4643 if ($oDeletionPlan->FoundManualOperation()) 4644 { 4645 throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseManualOpNeeded')); 4646 } 4647 if ($oDeletionPlan->FoundManualDelete()) 4648 { 4649 throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseOfDepencies')); 4650 } 4651 4652 // Report deletions 4653 // 4654 $aDisplayData = array(); 4655 foreach($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) 4656 { 4657 foreach($aDeletes as $iId => $aData) 4658 { 4659 $oToDelete = $aData['to_delete']; 4660 4661 if (isset($aData['requested_explicitely'])) 4662 { 4663 $sMessage = Dict::S('UI:Delete:Deleted'); 4664 } 4665 else 4666 { 4667 $sMessage = Dict::S('UI:Delete:AutomaticallyDeleted'); 4668 } 4669 $aDisplayData[] = array( 4670 'class' => MetaModel::GetName(get_class($oToDelete)), 4671 'object' => $oToDelete->GetName(), 4672 'consequence' => $sMessage, 4673 ); 4674 } 4675 } 4676 4677 // Report updates 4678 // 4679 foreach($oDeletionPlan->ListUpdates() as $sTargetClass => $aToUpdate) 4680 { 4681 foreach($aToUpdate as $iId => $aData) 4682 { 4683 $oToUpdate = $aData['to_reset']; 4684 $aDisplayData[] = array( 4685 'class' => MetaModel::GetName(get_class($oToUpdate)), 4686 'object' => $oToUpdate->GetHyperLink(), 4687 'consequence' => Dict::Format('UI:Delete:AutomaticResetOf_Fields', $aData['attributes_list']), 4688 ); 4689 } 4690 } 4691 4692 // Report automatic jobs 4693 // 4694 if ($oDeletionPlan->GetTargetCount() > 0) 4695 { 4696 if (count($aObjects) == 1) 4697 { 4698 $oObj = $aObjects[0]; 4699 $oP->p(Dict::Format('UI:Delete:CleaningUpRefencesTo_Object', $oObj->GetName())); 4700 } 4701 else 4702 { 4703 $oP->p(Dict::Format('UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class', count($aObjects), 4704 MetaModel::GetName($sClass))); 4705 } 4706 $aDisplayConfig = array(); 4707 $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); 4708 $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); 4709 $aDisplayConfig['consequence'] = array('label' => 'Done', 'description' => Dict::S('UI:Delete:Done+')); 4710 $oP->table($aDisplayConfig, $aDisplayData); 4711 } 4712 } 4713 } 4714 4715 /** 4716 * Find redundancy settings that can be viewed and modified in a tab 4717 * Settings are distributed to the corresponding link set attribute so as to be shown in the relevant tab 4718 */ 4719 protected function FindVisibleRedundancySettings() 4720 { 4721 $aRet = array(); 4722 foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) 4723 { 4724 if ($oAttDef instanceof AttributeRedundancySettings) 4725 { 4726 if ($oAttDef->IsVisible()) 4727 { 4728 $aQueryInfo = $oAttDef->GetRelationQueryData(); 4729 if (isset($aQueryInfo['sAttribute'])) 4730 { 4731 $oUpperAttDef = MetaModel::GetAttributeDef($aQueryInfo['sFromClass'], 4732 $aQueryInfo['sAttribute']); 4733 $oHostAttDef = $oUpperAttDef->GetMirrorLinkAttribute(); 4734 if ($oHostAttDef) 4735 { 4736 $sHostAttCode = $oHostAttDef->GetCode(); 4737 $aRet[$sHostAttCode][] = $oAttDef; 4738 } 4739 } 4740 } 4741 } 4742 } 4743 4744 return $aRet; 4745 } 4746 4747 /** 4748 * Generates the javascript code handle the "watchdog" associated with the concurrent access locking mechanism 4749 * 4750 * @param Webpage $oPage 4751 * @param string $sOwnershipToken 4752 */ 4753 protected function GetOwnershipJSHandler($oPage, $sOwnershipToken) 4754 { 4755 $iInterval = max(MIN_WATCHDOG_INTERVAL, 4756 MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')) * 1000 / 2; // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL 4757 $sJSClass = json_encode(get_class($this)); 4758 $iKey = (int)$this->GetKey(); 4759 $sJSToken = json_encode($sOwnershipToken); 4760 $sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle')); 4761 $sJSOk = json_encode(Dict::S('UI:Button:Ok')); 4762 $oPage->add_ready_script( 4763 <<<EOF 4764 window.setInterval(function() { 4765 if (window.bInSubmit || window.bInCancel) return; 4766 4767 $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'extend_lock', obj_class: $sJSClass, obj_key: $iKey, token: $sJSToken }, function(data) { 4768 if (!data.status) 4769 { 4770 if ($('.lock_owned').length == 0) 4771 { 4772 $('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>'); 4773 $('<div>'+data.popup_message+'</div>').dialog({title: $sJSTitle, modal: true, autoOpen: true, buttons:[ {text: $sJSOk, click: function() { $(this).dialog('close'); } }], close: function() { $(this).remove(); }}); 4774 } 4775 $('.wizContainer form button.action:not(.cancel)').prop('disabled', true); 4776 } 4777 else if ((data.operation == 'lost') || (data.operation == 'expired')) 4778 { 4779 if ($('.lock_owned').length == 0) 4780 { 4781 $('.ui-layout-content').prepend('<div class="header_message message_error lock_owned">'+data.message+'</div>'); 4782 $('<div>'+data.popup_message+'</div>').dialog({title: $sJSTitle, modal: true, autoOpen: true, buttons:[ {text: $sJSOk, click: function() { $(this).dialog('close'); } }], close: function() { $(this).remove(); }}); 4783 } 4784 $('.wizContainer form button.action:not(.cancel)').prop('disabled', true); 4785 } 4786 }, 'json'); 4787 }, $iInterval); 4788EOF 4789 ); 4790 } 4791} 4792