diff --git a/.idea/inspectionProfiles/Combodo.xml b/.idea/inspectionProfiles/Combodo.xml index bfed04c921..c14118b116 100644 --- a/.idea/inspectionProfiles/Combodo.xml +++ b/.idea/inspectionProfiles/Combodo.xml @@ -2,11 +2,7 @@ \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 408277a969..0000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 164ed09497..0a7cf585f3 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1165,6 +1165,14 @@ class RestUtils } $value = DBObjectSet::FromArray($sLnkClass, $aLinks); } + elseif ($oAttDef instanceof AttributeTagSet) + { + if (!is_array($value)) + { + throw new Exception("A tag set must be defined by an array of tag codes"); + } + $value = $oAttDef->FromJSONToValue($value); + } else { $value = $oAttDef->FromJSONToValue($value); diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 4254d0696f..0683dad000 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -34,48 +34,50 @@ define('HILIGHT_CLASS_NONE', ''); define('MIN_WATCHDOG_INTERVAL', 15); // Minimum interval for the watchdog: 15s -require_once(APPROOT.'/core/cmdbobject.class.inc.php'); -require_once(APPROOT.'/application/applicationextension.inc.php'); -require_once(APPROOT.'/application/utils.inc.php'); -require_once(APPROOT.'/application/applicationcontext.class.inc.php'); -require_once(APPROOT.'/application/ui.linkswidget.class.inc.php'); -require_once(APPROOT.'/application/ui.linksdirectwidget.class.inc.php'); -require_once(APPROOT.'/application/ui.passwordwidget.class.inc.php'); -require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php'); -require_once(APPROOT.'/application/ui.htmleditorwidget.class.inc.php'); -require_once(APPROOT.'/application/datatable.class.inc.php'); -require_once(APPROOT.'/sources/renderer/console/consoleformrenderer.class.inc.php'); -require_once(APPROOT.'/sources/application/search/searchform.class.inc.php'); -require_once(APPROOT.'/sources/application/search/criterionparser.class.inc.php'); -require_once(APPROOT.'/sources/application/search/criterionconversionabstract.class.inc.php'); -require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontooql.class.inc.php'); -require_once(APPROOT.'/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php'); +require_once(APPROOT.'core/cmdbobject.class.inc.php'); +require_once(APPROOT.'application/applicationextension.inc.php'); +require_once(APPROOT.'application/utils.inc.php'); +require_once(APPROOT.'application/applicationcontext.class.inc.php'); +require_once(APPROOT.'application/ui.linkswidget.class.inc.php'); +require_once(APPROOT.'application/ui.linksdirectwidget.class.inc.php'); +require_once(APPROOT.'application/ui.passwordwidget.class.inc.php'); +require_once(APPROOT.'application/ui.extkeywidget.class.inc.php'); +require_once(APPROOT.'application/ui.htmleditorwidget.class.inc.php'); +require_once(APPROOT.'application/datatable.class.inc.php'); +require_once(APPROOT.'sources/renderer/console/consoleformrenderer.class.inc.php'); +require_once(APPROOT.'sources/application/search/searchform.class.inc.php'); +require_once(APPROOT.'sources/application/search/criterionparser.class.inc.php'); +require_once(APPROOT.'sources/application/search/criterionconversionabstract.class.inc.php'); +require_once(APPROOT.'sources/application/search/criterionconversion/criteriontooql.class.inc.php'); +require_once(APPROOT.'sources/application/search/criterionconversion/criteriontosearchform.class.inc.php'); abstract class cmdbAbstractObject extends CMDBObject implements iDisplay { protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !) static $iGlobalFormId = 1; protected $aFieldsMap; - + /** * If true, bypass IsActionAllowedOnAttribute when writing this object + * * @var bool */ protected $bAllowWrite; /** * Constructor from a row of data (as a hash 'attcode' => value) - * @param hash $aRow + * + * @param array $aRow * @param string $sClassAlias - * @param hash $aAttToLoad - * @param hash $aExtendedDataSpec + * @param array $aAttToLoad + * @param array $aExtendedDataSpec */ public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null) { parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec); $this->bAllowWrite = false; } - + /** * returns what will be the next ID for the forms */ @@ -83,11 +85,12 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay { return 1 + self::$iGlobalFormId; } + public static function GetUIPage() { return 'UI.php'; } - + public static function ReloadAndDisplay($oPage, $oObj, $aParams) { $oAppContext = new ApplicationContext(); @@ -104,7 +107,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay } $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1'; $oPage->add_script( -<<IsPrintableVersion()) { // Is there a message for this object ?? @@ -168,18 +173,21 @@ EOF if ($aLockInfo['locked']) { $aRanks[] = 0; - $sName = $aLockInfo['owner']->GetName(); + $sName = $aLockInfo['owner']->GetName(); if ($aLockInfo['owner']->Get('contactid') != 0) { $sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')'; } - $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); $aMessages[] = "
".Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName)."
"; + $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); + $aMessages[] = "
".Dict::Format('UI:CurrentObjectIsLockedBy_User', + $sName)."
"; } } $sMessageKey = get_class($this).'::'.$this->GetKey(); - if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages'])) + if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, + $_SESSION['obj_messages'])) { - foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) + foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) { $sMsgClass = 'message_'.$aMessageData['severity']; $aMessages[] = "
".$aMessageData['message']."
"; @@ -188,12 +196,12 @@ EOF unset($_SESSION['obj_messages'][$sMessageKey]); } array_multisort($aRanks, $aMessages); - foreach ($aMessages as $sMessage) + foreach($aMessages as $sMessage) { $oPage->add($sMessage); } } - + if (!$oPage->IsPrintableVersion()) { // action menu @@ -204,7 +212,6 @@ EOF } // Master data sources - $bSynchronized = false; $aIcons = array(); if (!$oPage->IsPrintableVersion()) { @@ -215,12 +222,11 @@ EOF $aSyncData = $this->GetSynchroData(); if (count($aSyncData) > 0) { - $bSynchronized = true; - foreach ($aSyncData as $iSourceId => $aSourceData) + foreach($aSyncData as $iSourceId => $aSourceData) { $oDataSource = $aSourceData['source']; $oReplica = reset($aSourceData['replica']); // Take the first one! - + $sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica); $sLink = $oDataSource->GetName(); if (!empty($sApplicationURL)) @@ -252,21 +258,19 @@ EOF { $bCanBeDeletedByUser = false; } - else // everybody... - { - } } $aMasterSources[$iSourceId]['datasource'] = $oDataSource; $aMasterSources[$iSourceId]['url'] = $sLink; $aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen'); } - + if (is_object($oCreatorTask)) { $sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url']; if (!$bCanBeDeletedByUser) { - $sTip = "

".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sTaskUrl)."

"; + $sTip = "

".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', + $sTaskUrl)."

"; } else { @@ -281,7 +285,7 @@ EOF { $sTip = "

".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."

"; } - + $sTip .= "

".Dict::S('Core:Synchro:ListOfDataSources')."

"; foreach($aMasterSources as $aStruct) { @@ -292,8 +296,9 @@ EOF $oDataSource = $aStruct['datasource']; $sLink = $aStruct['url']; - $sTip .= "

".$oDataSource->GetIcon(true, 'style="vertical-align:middle"')." $sLink
"; - $sTip .= Dict::S('Core:Synchro:LastSynchro') . '
' . $sLastSynchro . "

"; + $sTip .= "

".$oDataSource->GetIcon(true, + 'style="vertical-align:middle"')." $sLink
"; + $sTip .= Dict::S('Core:Synchro:LastSynchro').'
'.$sLastSynchro."

"; } $sLabel = htmlentities(Dict::S('Tag:Synchronized'), ENT_QUOTES, 'UTF-8'); $sSynchroTagId = 'synchro_icon-'.$this->GetKey(); @@ -329,7 +334,7 @@ EOF } $oPage->add( -<<
$sObjectIcon
@@ -356,12 +361,12 @@ EOF function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array()) { - $aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); + $aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); if (!isset($aExtraParams['disable_plugins']) || !$aExtraParams['disable_plugins']) { - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode); } @@ -381,9 +386,10 @@ EOF return $aFieldsMap; } - + /** * Add a field to the map: attcode => id used when building a form + * * @param string $sAttCode The attribute code of the field being edited * @param string $sInputId The unique ID of the control/widget in the page */ @@ -421,7 +427,10 @@ EOF { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); // Display mode - if (!$oAttDef->IsLinkset()) continue; // Process only linkset attributes... + if (!$oAttDef->IsLinkset()) + { + continue; + } // Process only linkset attributes... $sLinkedClass = $oAttDef->GetLinkedClass(); @@ -450,12 +459,14 @@ EOF $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); // n:n links => must be allowed to modify the linking class AND read the target class in order to edit the linkedset - if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) + if (!UserRights::IsActionAllowed($sLinkedClass, + UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) { $iFlags |= OPT_ATT_READONLY; } // n:n links => must be allowed to read the linking class AND the target class in order to display the linkedset - if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) + if (!UserRights::IsActionAllowed($sLinkedClass, + UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) { $iFlags |= OPT_ATT_HIDDEN; } @@ -474,10 +485,13 @@ EOF } } // Non-readable/hidden linkedset... don't display anything - if ($iFlags & OPT_ATT_HIDDEN) continue; - + if ($iFlags & OPT_ATT_HIDDEN) + { + continue; + } + $aArgs = array('this' => $this); - $bReadOnly = ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)); + $bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); if ($bEditMode && (!$bReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sAttCode; @@ -494,8 +508,9 @@ EOF $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription().''); $sDisplayValue = ''; // not used - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; - $this->AddToFieldsMap($sAttCode, $sInputId); + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, + $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $this->AddToFieldsMap($sAttCode, $sInputId); $oPage->add($sHTMLValue); } else @@ -520,7 +535,7 @@ EOF 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), 'menu' => MetaModel::GetConfig()->Get('allow_menu_on_linkset'), - //'menu_actions_target' => '_blank', + //'menu_actions_target' => '_blank', 'default' => $aDefaults, 'table_id' => $sClass.'_'.$sAttCode, ); @@ -531,15 +546,15 @@ EOF $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); $aParams = array( - 'link_attr' => $oAttDef->GetExtKeyToMe(), - 'object_id' => $this->GetKey(), - 'target_attr' => $oAttDef->GetExtKeyToRemote(), - 'view_link' => false, - 'menu' => false, - //'menu_actions_target' => '_blank', - 'display_limit' => true, // By default limit the list to speed up the initial load & display - 'table_id' => $sClass.'_'.$sAttCode, - ); + 'link_attr' => $oAttDef->GetExtKeyToMe(), + 'object_id' => $this->GetKey(), + 'target_attr' => $oAttDef->GetExtKeyToRemote(), + 'view_link' => false, + 'menu' => false, + //'menu_actions_target' => '_blank', + 'display_limit' => true, // By default limit the list to speed up the initial load & display + 'table_id' => $sClass.'_'.$sAttCode, + ); } $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); $oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false); @@ -547,19 +562,21 @@ EOF } if (array_key_exists($sAttCode, $aRedundancySettings)) { - foreach ($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) + foreach($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) { $sRedundancyAttCode = $oRedundancyAttDef->GetCode(); $sValue = $this->Get($sRedundancyAttCode); $iRedundancyFlags = $this->GetFormAttributeFlags($sRedundancyAttCode); - $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)); + $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); $oPage->add('
'); $oPage->add(''.$oRedundancyAttDef->GetLabel().''); if ($bEditMode && (!$bRedundancyReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sRedundancyAttCode; - $oPage->add("".self::GetFormElementForField($oPage, $sClass, $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, $aArgs).''); + $oPage->add("".self::GetFormElementForField($oPage, $sClass, + $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, + $aArgs).''); } else { @@ -571,7 +588,7 @@ EOF } $oPage->SetCurrentTab(''); - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayRelations($this, $oPage, $bEditMode); } @@ -584,9 +601,9 @@ EOF // $oTriggerSet = new CMDBObjectSet(new DBObjectSearch('Trigger')); $aTriggers = array(); - while($oTrigger = $oTriggerSet->Fetch()) + while ($oTrigger = $oTriggerSet->Fetch()) { - if($oTrigger->IsInScope($this)) + if ($oTrigger->IsInScope($this)) { $aTriggers[] = $oTrigger->GetKey(); } @@ -602,12 +619,12 @@ EOF { $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"); $oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass]); - $iNotifsCount += $oNotifSet->Count(); + $iNotifsCount += $oNotifSet->Count(); } // Display notifications regarding the object: on block per subclass to have the intersting columns $sCount = ($iNotifsCount > 0) ? ' ('.$iNotifsCount.')' : ''; $oPage->SetCurrentTab(Dict::S('UI:NotificationsTab').$sCount); - + foreach($aNotificationClasses as $sNotifClass) { $oPage->p(MetaModel::GetClassIcon($sNotifClass, true).' '.MetaModel::GetName($sNotifClass)); @@ -618,7 +635,7 @@ EOF } } - function GetBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix, $aExtraParams = array()) + function GetBareProperties(WebPage $oPage, $bEditMode, $sPrefix, $aExtraParams = array()) { $sHtml = ''; $oAppContext = new ApplicationContext(); @@ -626,7 +643,8 @@ EOF $aDetails = array(); $sClass = get_class($this); $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); - $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', 'col1', ''); + $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', + 'col1', ''); // Compute the list of properties to display, first the attributes in the 'details' list, then // all the remaining attributes that are not external fields $sHtml = ''; @@ -661,7 +679,8 @@ EOF } $oPage->SetCurrentTab(Dict::S($sTab)); - $oPage->add(''); + $oPage->add('
'); foreach($aCols as $sColIndex => $aFieldsets) { $oPage->add(''; // Format kept as-is for 100% backward compatibility of the exports - $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports + $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports + $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports } } - else if($oAttDef instanceof AttributeCaseLog) - { - $rawValue = $oObj->Get($sAttCodeEx); - $outputValue = str_replace("\n", "
", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); - // Trick for Excel: treat the content as text even if it begins with an equal sign - $aRow[] = ''; - } else { - $rawValue = $oObj->Get($sAttCodeEx); - // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings - // let's fix this and make sure we render an empty string if the key == 0 - if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) + if ($oAttDef instanceof AttributeCaseLog) { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if ($oObj->Get($sKeyAttCode) == 0) - { - $rawValue = ''; - } - } - if ($bLocalize) - { - $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8'); + $rawValue = $oObj->Get($sAttCodeEx); + $outputValue = str_replace("\n", "
", + htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); + // Trick for Excel: treat the content as text even if it begins with an equal sign + $aRow[] = ''; } else { - $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + $rawValue = $oObj->Get($sAttCodeEx); + // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings + // let's fix this and make sure we render an empty string if the key == 0 + if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if ($oObj->Get($sKeyAttCode) == 0) + { + $rawValue = ''; + } + } + if ($bLocalize) + { + $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, + 'UTF-8'); + } + else + { + $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + } + $aRow[] = ''; } - $aRow[] = ''; } } } @@ -1472,16 +1541,16 @@ EOF $sHtml .= "\n"; } $sHtml .= "
'); @@ -718,7 +737,13 @@ EOF { // State attribute is always read-only from the UI $sHTMLValue = $this->GetStateLabel(); - $val = array('label' => '', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode); + $val = array( + 'label' => '', + 'value' => $sHTMLValue, + 'comments' => $sComments, + 'infos' => $sInfos, + 'attcode' => $sAttCode + ); } else { @@ -733,8 +758,10 @@ EOF $sTip = ''; foreach($aReasons as $aRow) { - $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); - $sDescription = str_replace(array("\r\n", "\n"), "
", $sDescription); + $sDescription = htmlentities($aRow['description'], ENT_QUOTES, + 'UTF-8'); + $sDescription = str_replace(array("\r\n", "\n"), "
", + $sDescription); $sTip .= "
"; $sTip .= "
Synchronized with {$aRow['name']}
"; $sTip .= "
$sDescription
"; @@ -752,10 +779,18 @@ EOF $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, + $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, + $aArgs).''; } $aFieldsMap[$sAttCode] = $sInputId; - $val = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode); + $val = array( + 'label' => ''.$oAttDef->GetLabel().'', + 'value' => $sHTMLValue, + 'comments' => $sComments, + 'infos' => $sInfos, + 'attcode' => $sAttCode + ); } } else @@ -773,7 +808,8 @@ EOF // Checking how the field should be rendered // Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml() // Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain? - if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) + if (in_array($oAttDef->GetEditClass(), + array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) { $val['layout'] = 'large'; } @@ -820,7 +856,7 @@ EOF return $aFieldsMap; } - + function DisplayDetails(WebPage $oPage, $bEditMode = false) { $sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate(get_class($this))); @@ -829,7 +865,13 @@ EOF $oTemplate = new DisplayTemplate($sTemplate); // Note: to preserve backward compatibility with home-made templates, the placeholder '$pkey$' has been preserved // but the preferred method is to use '$id$' - $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this), 'pkey'=> $this->GetKey(), 'id'=> $this->GetKey(), 'name' => $this->GetName())); + $oTemplate->Render($oPage, array( + 'class_name' => MetaModel::GetName(get_class($this)), + 'class' => get_class($this), + 'pkey' => $this->GetKey(), + 'id' => $this->GetKey(), + 'name' => $this->GetName() + )); } else { @@ -844,11 +886,12 @@ EOF $this->DisplayBareRelations($oPage, $bEditMode); //$oPage->SetCurrentTab(Dict::S('UI:HistoryTab')); //$this->DisplayBareHistory($oPage, $bEditMode); - $oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey()); + $oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), + utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey()); $oPage->add('
'); } } - + function DisplayPreview(WebPage $oPage) { $aDetails = array(); @@ -856,45 +899,52 @@ EOF $aList = MetaModel::GetZListItems($sClass, 'preview'); foreach($aList as $sAttCode) { - $aDetails[] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'value' =>$this->GetAsHTML($sAttCode)); + $aDetails[] = array( + 'label' => MetaModel::GetLabel($sClass, $sAttCode), + 'value' => $this->GetAsHTML($sAttCode) + ); } - $oPage->details($aDetails); + $oPage->details($aDetails); } - + public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); } - + /** * Simplifed version of GetDisplaySet() with less "decoration" around the table (and no paging) * that fits better into a printed document (like a PDF or a printable view) + * * @param WebPage $oPage * @param DBObjectSet $oSet - * @param hash $aExtraParams + * @param array $aExtraParams + * * @return string The HTML representation of the table + * @throws \CoreException */ public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) { $iListId = empty($aExtraParams['currentId']) ? $oPage->GetUniqueId() : $aExtraParams['currentId']; $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - + $bViewLink = true; $sSelectMode = 'none'; $iListId = $sTableId; $sClassAlias = $oSet->GetClassAlias(); $sClassName = $oSet->GetClass(); $sZListName = 'list'; - $aClassAliases = array( $sClassAlias => $sClassName); + $aClassAliases = array($sClassAlias => $sClassName); $aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - + $oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId); $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); $oSettings->iDefaultPageSize = 0; $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - - return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, $aExtraParams); - + + return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, + $aExtraParams); + } /** @@ -904,10 +954,13 @@ EOF * @param CMDBObjectSet The set of objects to display * @param array $aExtraParams Some extra configuration parameters to tweak the behavior of the display * - * @return String The HTML fragment representing the table of objects. Warning : no JS added to handled pagination or table sorting ! + * @return String The HTML fragment representing the table of objects. Warning : no JS added to handled + * pagination or table sorting ! * + * @throws \ApplicationException + * @throws \CoreException * @see DisplayBlock to get a similar table but with the JS for pagination & sorting - */ + */ public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) @@ -923,7 +976,7 @@ EOF { $iListId = $aExtraParams['currentId']; } - + // Initialize and check the parameters $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; @@ -931,12 +984,12 @@ EOF $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; if (!empty($sLinkageAttribute)) { - if($iLinkedObjectId == 0) + if ($iLinkedObjectId == 0) { // if 'links' mode is requested the id of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); } - if($sTargetAttr == '') + if ($sTargetAttr == '') { // if 'links' mode is requested the d of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); @@ -947,9 +1000,10 @@ EOF $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); $aExtraFields = array(); - foreach ($aExtraFieldsRaw as $sFieldName) + foreach($aExtraFieldsRaw as $sFieldName) { // Ignore attributes not of the main queried class if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) @@ -1007,7 +1061,7 @@ EOF foreach($aList as $sLinkAttCode) { $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if ( (!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField()) ) + if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) { $aDisplayList[] = $sLinkAttCode; } @@ -1017,7 +1071,7 @@ EOF { $oLinkAttDef = $aAttDefs[$sLinkAttCode]; if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) - || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode()!=$sLinkageAttribute)) ) + || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) { $aDisplayList[] = $sLinkAttCode; } @@ -1026,24 +1080,25 @@ EOF // Then display all the attributes linked to the other end of the relationship $aList = $aDisplayList; } - + $sSelectMode = 'none'; if ($bSelectMode) { $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; } - + $sClassAlias = $oSet->GetClassAlias(); $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; - + $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - $aClassAliases = array( $sClassAlias => $sClassName); + $aClassAliases = array($sClassAlias => $sClassName); $oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId); $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); - + if ($bDisplayLimit) { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', + MetaModel::GetConfig()->GetMinDisplayLimit()); $oSettings->iDefaultPageSize = $iDefaultPageSize; } else @@ -1051,10 +1106,10 @@ EOF $oSettings->iDefaultPageSize = 0; } $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - + return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); } - + public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { if (empty($aExtraParams['currentId'])) @@ -1066,18 +1121,20 @@ EOF $iListId = $aExtraParams['currentId']; } $aList = array(); - + // Initialize and check the parameters $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; // Check if there is a list of aliases to limit the display to... - $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : array(); + $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', + $aExtraParams['display_aliases']) : array(); $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); $aExtraFields = array(); $sAttCode = ''; - foreach ($aExtraFieldsRaw as $sFieldName) + foreach($aExtraFieldsRaw as $sFieldName) { // Ignore attributes not of the main queried class if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) @@ -1101,8 +1158,8 @@ EOF $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if ( (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && - ( (count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))) ) + if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && + ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) { $aAuthorizedClasses[$sAlias] = $sClassName; } @@ -1121,10 +1178,10 @@ EOF if ($sZListName !== false) { $aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - + $aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]); } - + // Filter the list to removed linked set since we are not able to display them here foreach($aList[$sAlias] as $index => $sAttCode) { @@ -1134,33 +1191,34 @@ EOF // Removed from the display list unset($aList[$sAlias][$index]); } - } + } } $sSelectMode = 'none'; - + $sClassAlias = $oSet->GetClassAlias(); $oDataTable = new DataTable($iListId, $oSet, $aAuthorizedClasses); $oSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList); - + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; if ($bDisplayLimit) { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', + MetaModel::GetConfig()->GetMinDisplayLimit()); $oSettings->iDefaultPageSize = $iDefaultPageSize; } - + $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - + return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); } - + static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') { $oPage->add(self::GetSetAsCSV($oSet, $aParams, $sCharset)); } - + static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') { $sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma @@ -1174,13 +1232,13 @@ EOF $bFieldsAdvanced = false; if (isset($aParams['fields_advanced'])) { - $bFieldsAdvanced = (bool) $aParams['fields_advanced']; + $bFieldsAdvanced = (bool)$aParams['fields_advanced']; } $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $aList = array(); @@ -1209,7 +1267,7 @@ EOF if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; - + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { if ($bFieldsAdvanced) @@ -1218,11 +1276,12 @@ EOF if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { - $sRemoteClass = $oAttDef->GetTargetClass(); + $sRemoteClass = $oAttDef->GetTargetClass(); foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) - { - $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); - } + { + $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, + $sRemoteAttCode); + } } } } @@ -1248,7 +1307,8 @@ EOF } foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) { - $aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx; + $aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, + isset($aParams['showMandatoryFields'])) : $sAttCodeEx; } } $sHtml = implode($sSeparator, $aHeader)."\n"; @@ -1286,19 +1346,19 @@ EOF } $sHtml .= implode($sSeparator, $aRow)."\n"; } - + return $sHtml; } - + static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams)); } - + /** * Spreadsheet output: designed for end users doing some reporting * Then the ids are excluded and replaced by the corresponding friendlyname - */ + */ static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array()) { $aFields = null; @@ -1310,13 +1370,13 @@ EOF $bFieldsAdvanced = false; if (isset($aParams['fields_advanced'])) { - $bFieldsAdvanced = (bool) $aParams['fields_advanced']; + $bFieldsAdvanced = (bool)$aParams['fields_advanced']; } $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $aList = array(); @@ -1345,16 +1405,17 @@ EOF if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; - + $aList[$sAlias][$sAttCodeEx] = $oAttDef; - if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) - { - $sRemoteClass = $oAttDef->GetTargetClass(); + if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) + { + $sRemoteClass = $oAttDef->GetTargetClass(); foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) - { - $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); - } + { + $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, + $sRemoteAttCode); + } } } } @@ -1374,7 +1435,8 @@ EOF { unset($aList[$sAlias][$sAttCode]); $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; - if (!array_key_exists($sFriendlyNameAttCode, $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) + if (!array_key_exists($sFriendlyNameAttCode, + $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) { $oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode); $aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt; @@ -1431,39 +1493,46 @@ EOF else { $iDate = AttributeDateTime::GetAsUnixSeconds($sDate); - $aRow[] = '
'.date('Y-m-d', $iDate).''.date('H:i:s', $iDate).''.date('Y-m-d', + $iDate).''.date('H:i:s', + $iDate).''.$outputValue.''.$outputValue.''.$outputValue.''.$outputValue.'
\n"; - + return $sHtml; } - + static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $oAppContext = new ApplicationContext(); @@ -1503,7 +1572,7 @@ EOF { if (count($aAuthorizedClasses) > 1) { - $oPage->add("\n"); + $oPage->add("\n"); } foreach($aAuthorizedClasses as $sAlias => $sClassName) { @@ -1517,7 +1586,7 @@ EOF $sClassName = get_class($oObj); $oPage->add("<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n"); } - foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) { if (is_null($oObj)) { @@ -1539,7 +1608,7 @@ EOF } if (count($aAuthorizedClasses) > 1) { - $oPage->add("\n"); + $oPage->add("\n"); } } $oPage->add("\n"); @@ -1568,10 +1637,10 @@ EOF } /** - * @param $oPage - * @param $sClass - * @param $sAttCode - * @param $oAttDef + * @param \iTopWebPage $oPage + * @param string $sClass + * @param string $sAttCode + * @param \AttributeDefinition $oAttDef * @param string $value * @param string $sDisplayValue * @param string $iId @@ -1579,12 +1648,14 @@ EOF * @param int $iFlags * @param array $aArgs * @param bool $bPreserveCurrentValue Preserve the current value even if not allowed + * * @return string + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \DictExceptionMissingString */ - public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) + public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) { - static $iInputId = 0; - $sFieldPrefix = ''; $sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : ''; $sFieldPrefix = isset($aArgs['prefix']) ? $sFormPrefix.$aArgs['prefix'] : $sFormPrefix; if ($sDisplayValue == '') @@ -1611,7 +1682,7 @@ EOF if (!$oAttDef->IsExternalField()) { $bMandatory = 'false'; - if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) + if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { $bMandatory = 'true'; } @@ -1619,55 +1690,62 @@ EOF $sReloadSpan = ""; $sHelpText = htmlentities($oAttDef->GetHelpOnEdition(), ENT_QUOTES, 'UTF-8'); $aEventsList = array(); - switch($oAttDef->GetEditClass()) + switch ($oAttDef->GetEditClass()) { case 'Date': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; + $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), + ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; case 'DateTime': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; + $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), + ENT_QUOTES, 'UTF-8').'"'; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; case 'Duration': - $aEventsList[] ='validate'; - $aEventsList[] ='change'; - $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $aVal = AttributeDuration::SplitDuration($value); - $sDays = ""; - $sHours = ""; - $sMinutes = ""; - $sSeconds = ""; - $sHidden = ""; - $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; - $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); - break; - + $aEventsList[] = 'validate'; + $aEventsList[] = 'change'; + $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $aVal = AttributeDuration::SplitDuration($value); + $sDays = ""; + $sHours = ""; + $sMinutes = ""; + $sSeconds = ""; + $sHidden = ""; + $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, + $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; + $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); + break; + case 'Password': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; - + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; + case 'OQLExpression': case 'Text': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; $sEditValue = $oAttDef->GetEditValue($value); $aStyles = array(); @@ -1700,10 +1778,11 @@ EOF $sAdditionalStuff = ""; } // Ok, the text area is drawn here - $sHTMLValue = "
$sAdditionalStuff
{$sValidationSpan}{$sReloadSpan}"; + $sHTMLValue = "
$sAdditionalStuff
{$sValidationSpan}{$sReloadSpan}"; - $oPage->add_ready_script( -<<add_ready_script( + <<
{$sValidationSpan}{$sReloadSpan}\n"; + break; } } else { - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; // Adding tooltip so we can read the whole value when its very long (eg. URL) - if(!empty($sDisplayValue)) + if (!empty($sDisplayValue)) { $oPage->add_ready_script( -<<GetValidationPattern()); //'^([0-9]+)$'; if (!empty($aEventsList)) @@ -2043,18 +2138,22 @@ EOF $sNullValue = "'$sNullValue'"; // Add quotes to turn this into a JS string if it's not a number } $sOriginalValue = ($iFlags & OPT_ATT_MUSTCHANGE) ? json_encode($value) : 'undefined'; - $oPage->add_ready_script("$('#$iId').bind('".implode(' ', $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );\n"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$iId').bind('".implode(' ', + $aEventsList)."', function(evt, sFormId) { return ValidateField('$iId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );\n"); // Bind to a custom event: validate } - $aDependencies = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that depend on the current one + $aDependencies = MetaModel::GetDependentAttributes($sClass, + $sAttCode); // List of attributes that depend on the current one if (count($aDependencies) > 0) { // Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form - $oPage->add_ready_script("$('#$iId').unbind('change.dependencies').bind('change.dependencies', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", $aDependencies)."']) } );\n"); // Bind to a custom event: validate + $oPage->add_ready_script("$('#$iId').unbind('change.dependencies').bind('change.dependencies', function(evt, sFormId) { return oWizardHelper{$sFormPrefix}.UpdateDependentFields(['".implode("','", + $aDependencies)."']) } );\n"); // Bind to a custom event: validate } } $oPage->add_dict_entry('UI:ValueMustBeSet'); $oPage->add_dict_entry('UI:ValueMustBeChanged'); $oPage->add_dict_entry('UI:ValueInvalidFormat'); + return "
{$sHTMLValue}
"; } @@ -2067,7 +2166,7 @@ EOF { // The concurrent access lock makes sense only for already existing objects $LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($LockEnabled) + if ($LockEnabled) { $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); if ($sOwnershipToken !== null) @@ -2092,18 +2191,21 @@ EOF // the lock may be released by 'onunload' which is called AFTER loading the current page. //$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId(); self::ReloadAndDisplay($oPage, $this, array('operation' => 'modify')); + return; } } } } - + if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) - { + { $sClassLabel = MetaModel::GetName($sClass); - $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $this->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $this->GetRawName(), + $sClassLabel)); // Set title will take care of the encoding $oPage->add("
\n"); - $oPage->add("

".$this->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, $this->GetName())."

\n"); + $oPage->add("

".$this->GetIcon()." ".Dict::Format('UI:ModificationTitle_Class_Object', $sClassLabel, + $this->GetName())."

\n"); $oPage->add("
\n"); $oPage->add("
\n"); } @@ -2115,7 +2217,7 @@ EOF $sPrefix = $aExtraParams['formPrefix']; } $aFieldsComments = (isset($aExtraParams['fieldsComments'])) ? $aExtraParams['fieldsComments'] : array(); - + $this->m_iFormId = $sPrefix.self::$iGlobalFormId; $oAppContext = new ApplicationContext(); $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); @@ -2132,41 +2234,47 @@ EOF // Custom label for the apply button ? if (isset($aExtraParams['custom_button'])) { - $sApplyButton = $aExtraParams['custom_button']; - } - else if ($iKey > 0) - { - $sApplyButton = Dict::S('UI:Button:Apply'); + $sApplyButton = $aExtraParams['custom_button']; } else { - $sApplyButton = Dict::S('UI:Button:Create'); + if ($iKey > 0) + { + $sApplyButton = Dict::S('UI:Button:Apply'); + } + else + { + $sApplyButton = Dict::S('UI:Button:Create'); + } } // Custom operation for the form ? if (isset($aExtraParams['custom_operation'])) { - $sOperation = $aExtraParams['custom_operation']; - } - else if ($iKey > 0) - { - $sOperation = 'apply_modify'; + $sOperation = $aExtraParams['custom_operation']; } else { - $sOperation = 'apply_new'; + if ($iKey > 0) + { + $sOperation = 'apply_modify'; + } + else + { + $sOperation = 'apply_new'; + } } if ($iKey > 0) { // The object already exists in the database, it's a modification $sButtons = "\n"; - $sButtons .= "\n"; + $sButtons .= "\n"; $sButtons .= "    \n"; $sButtons .= "\n"; } else { // The object does not exist in the database it's a creation - $sButtons = "\n"; + $sButtons = "\n"; $sButtons .= "    \n"; $sButtons .= "\n"; } @@ -2179,19 +2287,20 @@ EOF $aStimuli = Metamodel::EnumStimuli($sClass); foreach($aTransitions as $sStimulusCode => $aTransitionDef) { - $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO; - switch($iActionAllowed) + $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, + $sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO; + switch ($iActionAllowed) { case UR_ALLOWED_YES: - $sButtons .= "\n"; - break; - + $sButtons .= "\n"; + break; + default: - // Do nothing + // Do nothing } } } - + $sButtonsPosition = MetaModel::GetConfig()->Get('buttons_position'); $iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId(); $oPage->SetTransactionId($iTransactionId); @@ -2203,7 +2312,8 @@ EOF //$aInitialStates = array('new' => 'foo', 'closed' => 'bar'); if (count($aInitialStates) > 1) { - $sStatesSelection = Dict::Format('UI:Create_Class_InState', MetaModel::GetName($sClass)).''; foreach($aInitialStates as $sStateCode => $sStateData) { $sSelected = ''; @@ -2211,7 +2321,8 @@ EOF { $sSelected = ' selected'; } - $sStatesSelection .= ''; + $sStatesSelection .= ''; } $sStatesSelection .= ''; $oPage->add_ready_script("$('.state_select_{$this->m_iFormId}').change( function() { oWizardHelper$sPrefix.ReloadObjectCreationForm('form_{$this->m_iFormId}', $(this).val()); } );"); @@ -2221,7 +2332,7 @@ EOF $sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage')); $sJSToken = json_encode($sOwnershipToken); $oPage->add_ready_script( -<<add("\n"); + $oPage->add("\n"); } $oPage->add($oAppContext->GetForForm()); if ($sButtonsPosition != 'top') @@ -2282,21 +2394,21 @@ EOF $sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink(); $oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').click( function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );"); $oPage->add("\n"); - + if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) { $oPage->add("
\n"); } - + $iFieldsCount = count($aFieldsMap); $sJsonFieldsMap = json_encode($aFieldsMap); $sState = $this->GetState(); $sSessionStorageKey = $sClass.'_'.$iKey; $sTempId = session_id().'_'.$iTransactionId; $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); - + $oPage->add_script( -<<add_ready_script( -<<m_iFormId}', false); @@ -2323,26 +2435,28 @@ EOF $iInterval = MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay') * 1000 / 2; if ($iInterval > 0) { - $iInterval = max(MIN_WATCHDOG_INTERVAL*1000, $iInterval); // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL + $iInterval = max(MIN_WATCHDOG_INTERVAL * 1000, + $iInterval); // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL $oPage->add_ready_script( -<<Set($sAttCode, $aArgs['default'][$sAttCode]); + $oObj->Set($sAttCode, $aArgs['default'][$sAttCode]); } else { @@ -2401,9 +2515,10 @@ EOF } } } - return $oObj->DisplayModifyForm( $oPage, $aExtraParams); + + return $oObj->DisplayModifyForm($oPage, $aExtraParams); } - + public function DisplayStimulusForm(WebPage $oPage, $sStimulus, $aPrefillFormParam = null) { $sClass = get_class($this); @@ -2413,12 +2528,13 @@ EOF if (!isset($aTransitions[$sStimulus])) { // Invalid stimulus - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $this->GetName(), $this->GetStateLabel())); + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, + $this->GetName(), $this->GetStateLabel())); } // Check for concurrent access lock $LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); $sOwnershipToken = null; - if ($LockEnabled) + if ($LockEnabled) { $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); $aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey); @@ -2434,18 +2550,19 @@ EOF // the lock may be released by 'onunload' which is called AFTER loading the current page. //$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId(); self::ReloadAndDisplay($oPage, $this, array('operation' => 'stimulus', 'stimulus' => $sStimulus)); + return; } } $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $oPage->add("
\n"); - $oPage->add("

$sActionLabel - {$this->GetName()}

\n"); - $oPage->set_title($sActionLabel); - $oPage->add("
\n"); - $oPage->add("

$sActionDetails

\n"); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/); + $oPage->add("
\n"); + $oPage->add("

$sActionLabel - {$this->GetName()}

\n"); + $oPage->set_title($sActionLabel); + $oPage->add("
\n"); + $oPage->add("

$sActionDetails

\n"); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/); if ($aPrefillFormParam != null) { $aPrefillFormParam['expected_attributes'] = $aExpectedAttributes; @@ -2468,10 +2585,10 @@ EOF // The list of candidate fields is made of the ordered list of "details" attributes + other attributes $aAttributes = array(); - foreach ($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) + foreach($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) { $aAttributes[$sAttCode] = true; - } + } foreach(MetaModel::GetAttributesList($sClass) as $sAttCode) { if (!array_key_exists($sAttCode, $aAttributes)) @@ -2497,8 +2614,8 @@ EOF // Prompt for an attribute if // - the attribute must be changed or must be displayed to the user for confirmation // - or the field is mandatory and currently empty - if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || - (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == '')) ) + if (($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) == ''))) { $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $aArgs = array('this' => $this); @@ -2508,7 +2625,8 @@ EOF if ($oAttDef->IsExternalKey()) { /** @var DBObjectSet $oAllowedValues */ - $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $this->Get($sAttCode)); + $oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', + $this->Get($sAttCode)); if ($oAllowedValues->CountWithLimit(2) == 1) { $oRemoteObj = $oAllowedValues->Fetch(); @@ -2525,8 +2643,13 @@ EOF } } } - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,$this->Get($sAttCode),$this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, $aArgs); - $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue"); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, + $this->Get($sAttCode), $this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode, + $aArgs); + $aDetails[] = array( + 'label' => ''.$oAttDef->GetLabel().'', + 'value' => "$sHTMLValue" + ); $aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex; $iFieldIndex++; } @@ -2545,7 +2668,8 @@ EOF $oPage->add("\n"); if ($sOwnershipToken !== null) { - $oPage->add("\n"); + $oPage->add("\n"); } $oAppContext = new ApplicationContext(); $oPage->add($oAppContext->GetForForm()); @@ -2565,7 +2689,7 @@ EOF $sJsonFieldsMap = json_encode($aFieldsMap); $oPage->add_script( -<<GetState()}', '$sStimulus'); oWizardHelper.SetFieldsMap($sJsonFieldsMap); @@ -2574,13 +2698,13 @@ EOF ); $sJSToken = json_encode($sOwnershipToken); $oPage->add_ready_script( -<<GetOwnershipJSHandler($oPage, $sOwnershipToken); @@ -2605,35 +2729,35 @@ EOF { $sCode = $aMatches[1]; $sName = $aMatches[2]; - switch($sCode) + switch ($sCode) { case 'tab': - //echo "

Found a tab: $sName ($sKey)

\n"; - if(!isset($aDetails[$sName])) - { - $aDetails[$sName] = array('col1' => array()); - } - $aDetails = self::ProcessZlist($value, $aDetails, $sName, 'col1', ''); - break; - + //echo "

Found a tab: $sName ($sKey)

\n"; + if (!isset($aDetails[$sName])) + { + $aDetails[$sName] = array('col1' => array()); + } + $aDetails = self::ProcessZlist($value, $aDetails, $sName, 'col1', ''); + break; + case 'fieldset': - //echo "

Found a fieldset: $sName ($sKey)

\n"; - if(!isset($aDetailsStruct[$sCurrentTab][$sCurrentCol][$sName])) - { - $aDetails[$sCurrentTab][$sCurrentCol][$sName] = array(); - } - $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sCurrentCol, $sName); - break; + //echo "

Found a fieldset: $sName ($sKey)

\n"; + if (!isset($aDetailsStruct[$sCurrentTab][$sCurrentCol][$sName])) + { + $aDetails[$sCurrentTab][$sCurrentCol][$sName] = array(); + } + $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sCurrentCol, $sName); + break; default: case 'col': - //echo "

Found a column: $sName ($sKey)

\n"; - if(!isset($aDetails[$sCurrentTab][$sName])) - { - $aDetails[$sCurrentTab][$sName] = array(); - } - $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sName, ''); - break; + //echo "

Found a column: $sName ($sKey)

\n"; + if (!isset($aDetails[$sCurrentTab][$sName])) + { + $aDetails[$sCurrentTab][$sName] = array(); + } + $aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sName, ''); + break; } } } @@ -2651,6 +2775,7 @@ EOF } $index++; } + return $aDetails; } @@ -2665,12 +2790,13 @@ EOF } else { - $aResult = array_merge($aResult,self::FlattenZList($value)); + $aResult = array_merge($aResult, self::FlattenZList($value)); } } + return $aResult; } - + protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode) { $retVal = null; @@ -2683,7 +2809,7 @@ EOF $iFlags = $this->GetAttributeFlags($sAttCode); } $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) + if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0)) { // The field is visible in the current state of the object if ($sStateAttCode == $sAttCode) @@ -2691,38 +2817,54 @@ EOF // Special display for the 'state' attribute itself $sDisplayValue = $this->GetStateLabel(); } - else if ($oAttDef->GetEditClass() == 'Document') + else { - $oDocument = $this->Get($sAttCode); - $sDisplayValue = $this->GetAsHTML($sAttCode); - $sDisplayValue .= "
".Dict::Format('UI:OpenDocumentInNewWindow_', $oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; - $sDisplayValue .= "
".Dict::Format('UI:DownloadDocument_', $oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; + if ($oAttDef->GetEditClass() == 'Document') + { + $oDocument = $this->Get($sAttCode); + $sDisplayValue = $this->GetAsHTML($sAttCode); + $sDisplayValue .= "
".Dict::Format('UI:OpenDocumentInNewWindow_', + $oDocument->GetDisplayLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; + $sDisplayValue .= "
".Dict::Format('UI:DownloadDocument_', + $oDocument->GetDownloadLink(get_class($this), $this->GetKey(), $sAttCode)).", \n"; + } + else + { + $sDisplayValue = $this->GetAsHTML($sAttCode); + } + } + $retVal = array( + 'label' => ''.MetaModel::GetLabel($sClass, $sAttCode).'', + 'value' => $sDisplayValue, + 'attcode' => $sAttCode + ); + + // Checking how the field should be rendered + // Note: For edit mode, this is done in self::GetBareProperties() + // Note 2: Shouldn't this be a AttDef property instead of an array to maintain? + if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) + { + $retVal['layout'] = 'large'; } else { - $sDisplayValue = $this->GetAsHTML($sAttCode); + $retVal['layout'] = 'small'; } - $retVal = array('label' => ''.MetaModel::GetLabel($sClass, $sAttCode).'', 'value' => $sDisplayValue, 'attcode' => $sAttCode); - - // Checking how the field should be rendered - // Note: For edit mode, this is done in self::GetBareProperties() - // Note 2: Shouldn't this be a AttDef property instead of an array to maintain? - if(in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) - { - $retVal['layout'] = 'large'; - } - else - { - $retVal['layout'] = 'small'; - } } + return $retVal; } - + /** * Displays a blob document *inline* (if possible, depending on the type of the document) + * + * @param \WebPage $oPage + * @param $sAttCode + * * @return string - */ + * @throws \CoreException + */ public function DisplayDocumentInline(WebPage $oPage, $sAttCode) { $oDoc = $this->Get($sAttCode); @@ -2732,40 +2874,42 @@ EOF { case 'text': case 'html': - $data = $oDoc->GetData(); - switch($oDoc->GetMimeType()) - { - case 'text/html': - case 'text/xml': - $oPage->add("\n"); + $data = $oDoc->GetData(); + switch ($oDoc->GetMimeType()) + { + case 'text/html': + case 'text/xml': + $oPage->add("\n"); + break; + + default: + $oPage->add("
".htmlentities(MyHelpers::beautifulstr($data, 1000, true), ENT_QUOTES,
+								'UTF-8')."
\n"); + } break; - - default: - $oPage->add("
".htmlentities(MyHelpers::beautifulstr($data, 1000, true), ENT_QUOTES, 'UTF-8')."
\n"); - } - break; case 'application': - switch($oDoc->GetMimeType()) - { - case 'application/pdf': - $oPage->add("\n"); + switch ($oDoc->GetMimeType()) + { + case 'application/pdf': + $oPage->add("\n"); + break; + + default: + $oPage->add(Dict::S('UI:Document:NoPreview')); + } break; - default: - $oPage->add(Dict::S('UI:Document:NoPreview')); - } - break; - case 'image': - $oPage->add("\n"); - break; - + $oPage->add("\n"); + break; + default: - $oPage->add(Dict::S('UI:Document:NoPreview')); + $oPage->add(Dict::S('UI:Document:NoPreview')); } + return ''; } - + // $m_highlightComparison[previous][new] => next value protected static $m_highlightComparison = array( HILIGHT_CLASS_CRITICAL => array( @@ -2793,13 +2937,15 @@ EOF HILIGHT_CLASS_NONE => HILIGHT_CLASS_NONE, ), ); - + /** * This function returns a 'hilight' CSS class, used to hilight a given row in a table * There are currently (i.e defined in the CSS) 4 possible values HILIGHT_CLASS_CRITICAL, * HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE * To Be overridden by derived classes + * * @param void + * * @return String The desired higlight class for the object/row */ public function GetHilightClass() @@ -2809,18 +2955,23 @@ EOF $current = parent::GetHilightClass(); // Default computation // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $new = $oExtensionInstance->GetHilightClass($this); @$current = self::$m_highlightComparison[$current][$new]; } + return $current; } - + /** * Re-order the fields based on their inter-dependencies + * * @params hash @aFields field_code => array_of_depencies + * + * @param $aFields * @return array Ordered array of fields or throws an exception + * @throws \Exception */ public static function OrderDependentFields($aFields) { @@ -2840,11 +2991,14 @@ EOF // Dependency is resolved, remove it unset($aFields[$sFieldCode][$key]); } - else if (!array_key_exists($sDependency, $aFields)) + else { - // The current fields depends on a field not present in the form - // let's ignore it (since it cannot change) - unset($aFields[$sFieldCode][$key]); + if (!array_key_exists($sDependency, $aFields)) + { + // The current fields depends on a field not present in the form + // let's ignore it (since it cannot change) + unset($aFields[$sFieldCode][$key]); + } } } if (count($aFields[$sFieldCode]) == 0) @@ -2855,32 +3009,38 @@ EOF $bSet = true; } } - } - while($bSet && (count($aFields) > 0)); - + } while ($bSet && (count($aFields) > 0)); + if (count($aFields) > 0) { - $sMessage = "Error: Circular dependencies between the fields!
".print_r($aFields, true)."
"; + $sMessage = "Error: Circular dependencies between the fields!
".print_r($aFields, true)."
"; throw(new Exception($sMessage)); } + return $aResult; } - + /** * Get the list of actions to be displayed as 'shortcuts' (i.e buttons) instead of inside the Actions popup menu + * * @param $sFinalClass string The actual class of the objects for which to display the menu - * @return Array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the actions menu + * + * @return array the list of menu codes (i.e dictionary entries) that can be displayed as shortcuts next to the + * actions menu */ - public static function GetShortcutActions($sFinalClass) - { - $sShortcutActions = MetaModel::GetConfig()->Get('shortcut_actions'); - $aShortcutActions = explode(',', $sShortcutActions); - return $aShortcutActions; - } - + public static function GetShortcutActions($sFinalClass) + { + $sShortcutActions = MetaModel::GetConfig()->Get('shortcut_actions'); + $aShortcutActions = explode(',', $sShortcutActions); + + return $aShortcutActions; + } + /** * Maps the given context parameter name to the appropriate filter/search code for this class + * * @param string $sContextParam Name of the context parameter, i.e. 'org_id' + * * @return string Filter code, i.e. 'customer_id' */ public static function MapContextParam($sContextParam) @@ -2894,13 +3054,16 @@ EOF return $sContextParam; } } - + /** * Updates the object from a flat array of values + * * @param $aAttList array $aAttList array of attcode * @param $aErrors array Returns information about slave attributes * @param $aAttFlags array Attribute codes => Flags to use instead of those from the MetaModel + * * @return array of attcodes that can be used for writing on the current object + * @throws \CoreException */ public function GetWriteableAttList($aAttList, &$aErrors, $aAttFlags = array()) { @@ -2912,10 +3075,10 @@ EOF foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) { - if(array_key_exists($sAttCode, $aAttFlags)) - { - $iFlags = $aAttFlags[$sAttCode]; - } + if (array_key_exists($sAttCode, $aAttFlags)) + { + $iFlags = $aAttFlags[$sAttCode]; + } elseif ($this->IsNew()) { $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); @@ -2927,7 +3090,7 @@ EOF } if ($oAttDef instanceof AttributeCaseLog) { - if (!($iFlags & (OPT_ATT_HIDDEN|OPT_ATT_SLAVE|OPT_ATT_READONLY))) + if (!($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_SLAVE | OPT_ATT_READONLY))) { // The case log is editable, append it to the list of fields to retrieve $aAttList[] = $sAttCode; @@ -2940,11 +3103,11 @@ EOF { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - if(array_key_exists($sAttCode, $aAttFlags)) - { - $iFlags = $aAttFlags[$sAttCode]; - } - elseif ($this->IsNew()) + if (array_key_exists($sAttCode, $aAttFlags)) + { + $iFlags = $aAttFlags[$sAttCode]; + } + elseif ($this->IsNew()) { $iFlags = $this->GetInitialStateAttributeFlags($sAttCode); } @@ -2955,11 +3118,11 @@ EOF } if ($oAttDef->IsWritable()) { - if ( $iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) + if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) { // Non-visible, or read-only attribute, do nothing } - elseif($iFlags & OPT_ATT_SLAVE) + elseif ($iFlags & OPT_ATT_SLAVE) { $aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel()); } @@ -2969,12 +3132,13 @@ EOF } } } + return $aWriteableAttList; } /** * Compute the attribute flags depending on the object state - */ + */ public function GetFormAttributeFlags($sAttCode) { if ($this->IsNew()) @@ -2989,146 +3153,173 @@ EOF { $iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object } + return $iFlags; } /** * Updates the object from a flat array of values - * @param string $aValues array of attcode => scalar or array (N-N links) + * + * @param $aValues array of attcode => scalar or array (N-N links) + * * @return void + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue */ public function UpdateObjectFromArray($aValues) { foreach($aValues as $sAttCode => $value) { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); - if ($oAttDef->GetEditClass() == 'Document') + switch ($oAttDef->GetEditClass()) { - // There should be an uploaded file with the named attr_ - $oDocument = $value['fcontents']; - if (!$oDocument->IsEmpty()) - { - // A new file has been uploaded - $this->Set($sAttCode, $oDocument); - } - } - elseif ($oAttDef->GetEditClass() == 'Image') - { - // There should be an uploaded file with the named attr_ - if ($value['remove']) - { - $this->Set($sAttCode, null); - } - else - { + case 'Document': + // There should be an uploaded file with the named attr_ $oDocument = $value['fcontents']; if (!$oDocument->IsEmpty()) { // A new file has been uploaded $this->Set($sAttCode, $oDocument); } - } - } - elseif ($oAttDef->GetEditClass() == 'One Way Password') - { - // Check if the password was typed/changed - $aPwdData = $value; - if (!is_null($aPwdData) && $aPwdData['changed']) - { - // The password has been changed or set - $this->Set($sAttCode, $aPwdData['value']); - } - } - elseif ($oAttDef->GetEditClass() == 'Duration') - { - $aDurationData = $value; - if (!is_array($aDurationData)) continue; + break; + case 'Image': + // There should be an uploaded file with the named attr_ + if ($value['remove']) + { + $this->Set($sAttCode, null); + } + else + { + $oDocument = $value['fcontents']; + if (!$oDocument->IsEmpty()) + { + // A new file has been uploaded + $this->Set($sAttCode, $oDocument); + } + } + break; + case 'One Way Password': + // Check if the password was typed/changed + $aPwdData = $value; + if (!is_null($aPwdData) && $aPwdData['changed']) + { + // The password has been changed or set + $this->Set($sAttCode, $aPwdData['value']); + } + break; + case 'Duration': + $aDurationData = $value; + if (!is_array($aDurationData)) + { + continue; + } - $iValue = (((24*$aDurationData['d'])+$aDurationData['h'])*60 +$aDurationData['m'])*60 + $aDurationData['s']; - $this->Set($sAttCode, $iValue); - $previousValue = $this->Get($sAttCode); - if ($previousValue !== $iValue) - { + $iValue = (((24 * $aDurationData['d']) + $aDurationData['h']) * 60 + $aDurationData['m']) * 60 + $aDurationData['s']; $this->Set($sAttCode, $iValue); - } - } - elseif ($oAttDef->GetEditClass() == 'CustomFields') - { - $this->Set($sAttCode, $value); - } - else if ($oAttDef->GetEditClass() == 'LinkedSet') - { - $oLinkSet = $this->Get($sAttCode); - $sLinkedClass = $oAttDef->GetLinkedClass(); - if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0)) - { - // Now handle the links to be created - foreach($value['to_be_created'] as $aData) - { - $sSubClass = $aData['class']; - if ( ($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass)) ) - { - $aObjData = $aData['data']; - - $oLink = MetaModel::NewObject($sSubClass); - $oLink->UpdateObjectFromArray($aObjData); - $oLinkSet->AddItem($oLink); - } - } - } - if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0)) - { - // Now handle the links to be added by making the remote object point to self - foreach($value['to_be_added'] as $iObjKey) - { - $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); - if ($oLink) - { - $oLinkSet->AddItem($oLink); - } - } - } - if (array_key_exists('to_be_modified', $value) && (count($value['to_be_modified']) > 0)) - { - // Now handle the links to be added by making the remote object point to self - foreach($value['to_be_modified'] as $iObjKey => $aData) - { - $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); - if ($oLink) - { - $aObjData = $aData['data']; - $oLink->UpdateObjectFromArray($aObjData); - $oLinkSet->ModifyItem($oLink); - } - } - } - if (array_key_exists('to_be_removed', $value) && (count($value['to_be_removed']) > 0)) - { - foreach($value['to_be_removed'] as $iObjKey) - { - $oLinkSet->RemoveItem($iObjKey); - } - } - if (array_key_exists('to_be_deleted', $value) && (count($value['to_be_deleted']) > 0)) - { - foreach($value['to_be_deleted'] as $iObjKey) - { - $oLinkSet->RemoveItem($iObjKey); - } - } - $this->Set($sAttCode, $oLinkSet); - } - else - { - if (!is_null($value)) - { - $aAttributes[$sAttCode] = trim($value); $previousValue = $this->Get($sAttCode); - if ($previousValue !== $aAttributes[$sAttCode]) + if ($previousValue !== $iValue) { - $this->Set($sAttCode, $aAttributes[$sAttCode]); + $this->Set($sAttCode, $iValue); + } + break; + case 'CustomFields': + $this->Set($sAttCode, $value); + break; + case 'LinkedSet': + $oLinkSet = $this->Get($sAttCode); + $sLinkedClass = $oAttDef->GetLinkedClass(); + if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0)) + { + // Now handle the links to be created + foreach($value['to_be_created'] as $aData) + { + $sSubClass = $aData['class']; + if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass))) + { + $aObjData = $aData['data']; + + $oLink = MetaModel::NewObject($sSubClass); + $oLink->UpdateObjectFromArray($aObjData); + $oLinkSet->AddItem($oLink); + } + } + } + if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0)) + { + // Now handle the links to be added by making the remote object point to self + foreach($value['to_be_added'] as $iObjKey) + { + $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); + if ($oLink) + { + $oLinkSet->AddItem($oLink); + } + } + } + if (array_key_exists('to_be_modified', $value) && (count($value['to_be_modified']) > 0)) + { + // Now handle the links to be added by making the remote object point to self + foreach($value['to_be_modified'] as $iObjKey => $aData) + { + $oLink = MetaModel::GetObject($sLinkedClass, $iObjKey, false); + if ($oLink) + { + $aObjData = $aData['data']; + $oLink->UpdateObjectFromArray($aObjData); + $oLinkSet->ModifyItem($oLink); + } + } + } + if (array_key_exists('to_be_removed', $value) && (count($value['to_be_removed']) > 0)) + { + foreach($value['to_be_removed'] as $iObjKey) + { + $oLinkSet->RemoveItem($iObjKey); + } + } + if (array_key_exists('to_be_deleted', $value) && (count($value['to_be_deleted']) > 0)) + { + foreach($value['to_be_deleted'] as $iObjKey) + { + $oLinkSet->RemoveItem($iObjKey); + } + } + $this->Set($sAttCode, $oLinkSet); + break; + + case 'TagSet': + /** @var ormTagSet $oTagSet */ + $oTagSet = $this->Get($sAttCode); + if (is_null($oTagSet)) + { + $oTagSet = new ormTagSet(get_class($this), $sAttCode); + } + $oTagSet->ApplyDelta($value); + $this->Set($sAttCode, $oTagSet); + break; + + case 'Set': + /** @var ormSet $oSet */ + $oSet = $this->Get($sAttCode); + if (is_null($oSet)) + { + $oSet = new ormSet(get_class($this), $sAttCode); + } + $oSet->ApplyDelta($value); + $this->Set($sAttCode, $oSet); + break; + + default: + if (!is_null($value)) + { + $aAttributes[$sAttCode] = trim($value); + $previousValue = $this->Get($sAttCode); + if ($previousValue !== $aAttributes[$sAttCode]) + { + $this->Set($sAttCode, $aAttributes[$sAttCode]); + } } - } } } } @@ -3145,7 +3336,7 @@ EOF $aValues = array(); foreach($aAttList as $sAttCode) { - $value = $this->PrepareValueFromPostedForm($sFormPrefix, $sAttCode); + $value = $this->PrepareValueFromPostedForm($sFormPrefix, $sAttCode); if (!is_null($value)) { $aValues[$sAttCode] = $value; @@ -3163,180 +3354,201 @@ EOF { InlineImage::FinalizeInlineImages($this); } - + // Invoke extensions after the update of the object from the form - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnFormSubmit($this, $sFormPrefix); } - + return $aErrors; } - /** - * @param string $sFormPrefix - * @param string $sAttCode - * @param string $sClass Optional parameter, host object's class for the $sAttCode - * @param array $aPostedData Optional parameter, used through recursive calls - * @return array|null - */ + /** + * @param string $sFormPrefix + * @param string $sAttCode + * @param string $sClass Optional parameter, host object's class for the $sAttCode + * @param array $aPostedData Optional parameter, used through recursive calls + * + * @return array|null + * @throws \FileUploadException + */ protected function PrepareValueFromPostedForm($sFormPrefix, $sAttCode, $sClass = null, $aPostedData = null) - { - if($sClass === null) - { - $sClass = get_class($this); - } + { + if ($sClass === null) + { + $sClass = get_class($this); + } - $value = null; + $value = null; - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef->GetEditClass() == 'Document') - { - $value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents')); - } - elseif ($oAttDef->GetEditClass() == 'Image') - { - $oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'); - $aSize = utils::GetImageSize($oImage->GetData()); - $oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'), $oAttDef->Get('storage_max_height')); - $aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); - if (is_array($aOtherData)) - { - $value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']); - } - else - { - $value = null; - } - } - elseif ($oAttDef->GetEditClass() == 'RedundancySetting') - { - $value = $oAttDef->ReadValueFromPostedForm($sFormPrefix); - } - elseif ($oAttDef->GetEditClass() == 'CustomFields') - { - $value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix); - } - else if ($oAttDef->GetEditClass() == 'LinkedSet') - { - /** @var AttributeLinkedSet $oAttDef */ - $aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 'raw_data'), true); - $aToBeCreated = array(); - foreach($aRawToBeCreated as $aData) - { - $sSubFormPrefix = $aData['formPrefix']; - $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); - $aObjData = array(); - foreach($aData as $sKey => $value) - { - if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) - { - $sLinkClass = $oAttDef->GetLinkedClass(); - if($oAttDef->IsIndirect()) - { - $oLinkAttDef = MetaModel::GetAttributeDef($sLinkClass, $aMatches[1]); - // Recursing over n:n link datetime attributes - // Note: We might need to do it with other attribute types, like Document or redundancy setting. - if($oLinkAttDef instanceof AttributeDateTime) - { - $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aMatches[1], $sLinkClass, $aData); - } - else - { - $aObjData[$aMatches[1]] = $value; - } - } - else - { - $aObjData[$aMatches[1]] = $value; - } - } - } - $aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData); - } + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + switch ($oAttDef->GetEditClass()) + { + case 'Document': + $value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents')); + break; - $aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}', 'raw_data'), true); - $aToBeModified = array(); - foreach($aRawToBeModified as $iObjKey => $aData) - { - $sSubFormPrefix = $aData['formPrefix']; - $aObjData = array(); - foreach($aData as $sKey => $value) - { - if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) - { - $sLinkClass = $oAttDef->GetLinkedClass(); - if($oAttDef->IsIndirect()) - { - $oLinkAttDef = MetaModel::GetAttributeDef($sLinkClass, $aMatches[1]); - // Recursing over n:n link datetime attributes - // Note: We might need to do it with other attribute types, like Document or redundancy setting. - if($oLinkAttDef instanceof AttributeDateTime) - { - $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aMatches[1], $sLinkClass, $aData); - } - else - { - $aObjData[$aMatches[1]] = $value; - } - } - else - { - $aObjData[$aMatches[1]] = $value; - } - } - } - $aToBeModified[$iObjKey] = array('data' => $aObjData); - } + case 'Image': + $oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'); + $aSize = utils::GetImageSize($oImage->GetData()); + $oImage = utils::ResizeImageToFit($oImage, $aSize[0], $aSize[1], $oAttDef->Get('storage_max_width'), + $oAttDef->Get('storage_max_height')); + $aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); + if (is_array($aOtherData)) + { + $value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']); + } + else + { + $value = null; + } + break; - $value = array( - 'to_be_created' => $aToBeCreated, - 'to_be_modified' => $aToBeModified, - 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true), - 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true), - 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true) - ); - } - else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime - { - // Retrieving value from array when present (means what we are in a recursion) - if($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) - { - $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; - } - else - { - $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); - } + case 'RedundancySetting': + $value = $oAttDef->ReadValueFromPostedForm($sFormPrefix); + break; - if ($value != null) - { - $oDate = $oAttDef->GetFormat()->Parse($value); - if ($oDate instanceof DateTime) - { - $value = $oDate->format($oAttDef->GetInternalFormat()); - } - else - { - $value = null; - } - } - } - else - { - // Retrieving value from array when present (means what we are in a recursion) - if($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) - { - $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; - } - else - { - $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); - } - } + case 'CustomFields': + $value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix); + break; - return $value; - } + case 'LinkedSet': + /** @var AttributeLinkedSet $oAttDef */ + $aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', + 'raw_data'), true); + $aToBeCreated = array(); + foreach($aRawToBeCreated as $aData) + { + $sSubFormPrefix = $aData['formPrefix']; + $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); + $aObjData = array(); + foreach($aData as $sKey => $value) + { + if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) + { + $sLinkClass = $oAttDef->GetLinkedClass(); + if ($oAttDef->IsIndirect()) + { + $oLinkAttDef = MetaModel::GetAttributeDef($sLinkClass, $aMatches[1]); + // Recursing over n:n link datetime attributes + // Note: We might need to do it with other attribute types, like Document or redundancy setting. + if ($oLinkAttDef instanceof AttributeDateTime) + { + $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, + $aMatches[1], $sLinkClass, $aData); + } + else + { + $aObjData[$aMatches[1]] = $value; + } + } + else + { + $aObjData[$aMatches[1]] = $value; + } + } + } + $aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData); + } + + $aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}', + 'raw_data'), true); + $aToBeModified = array(); + foreach($aRawToBeModified as $iObjKey => $aData) + { + $sSubFormPrefix = $aData['formPrefix']; + $aObjData = array(); + foreach($aData as $sKey => $value) + { + if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) + { + $sLinkClass = $oAttDef->GetLinkedClass(); + if ($oAttDef->IsIndirect()) + { + $oLinkAttDef = MetaModel::GetAttributeDef($sLinkClass, $aMatches[1]); + // Recursing over n:n link datetime attributes + // Note: We might need to do it with other attribute types, like Document or redundancy setting. + if ($oLinkAttDef instanceof AttributeDateTime) + { + $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, + $aMatches[1], $sLinkClass, $aData); + } + else + { + $aObjData[$aMatches[1]] = $value; + } + } + else + { + $aObjData[$aMatches[1]] = $value; + } + } + } + $aToBeModified[$iObjKey] = array('data' => $aObjData); + } + + $value = array( + 'to_be_created' => $aToBeCreated, + 'to_be_modified' => $aToBeModified, + 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', + 'raw_data'), true), + 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', + 'raw_data'), true), + 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', + 'raw_data'), true) + ); + break; + + case 'Set': + case 'TagSet': + $sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); + $value = json_decode($sTagSetJson, true); + break; + + default: + if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime + { + // Retrieving value from array when present (means what we are in a recursion) + if ($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) + { + $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; + } + else + { + $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); + } + + if ($value != null) + { + $oDate = $oAttDef->GetFormat()->Parse($value); + if ($oDate instanceof DateTime) + { + $value = $oDate->format($oAttDef->GetInternalFormat()); + } + else + { + $value = null; + } + } + } + else + { + // Retrieving value from array when present (means what we are in a recursion) + if ($aPostedData !== null && isset($aPostedData['attr_'.$sFormPrefix.$sAttCode])) + { + $value = $aPostedData['attr_'.$sFormPrefix.$sAttCode]; + } + else + { + $value = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); + } + } + break; + } + + return $value; + } /** * Updates the object from a given page argument @@ -3371,6 +3583,7 @@ EOF } } $this->UpdateObjectFromArray($aFinalValues); + return $aErrors; } @@ -3379,7 +3592,7 @@ EOF $res = parent::DBInsertNoReload(); // Invoke extensions after insertion (the object must exist, have an id, etc.) - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { $oExtensionInstance->OnDBInsert($this, self::GetCurrentChange()); } @@ -3387,56 +3600,57 @@ EOF return $res; } - /** - * Attaches InlineImages to the current object - */ + /** + * Attaches InlineImages to the current object + */ protected function OnObjectKeyReady() - { - InlineImage::FinalizeInlineImages($this); - } + { + InlineImage::FinalizeInlineImages($this); + } protected function DBCloneTracked_Internal($newKey = null) { $oNewObj = parent::DBCloneTracked_Internal($newKey); // Invoke extensions after insertion (the object must exist, have an id, etc.) - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { $oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange()); } + return $oNewObj; } public function DBUpdate() { - $res = parent::DBUpdate(); + $res = parent::DBUpdate(); - // Protection against reentrance (e.g. cascading the update of ticket logs) - // Note: This is based on the fix made on r 3190 in DBObject::DBUpdate() - static $aUpdateReentrance = array(); - $sKey = get_class($this).'::'.$this->GetKey(); - if(array_key_exists($sKey, $aUpdateReentrance)) - { - return $res; - } - $aUpdateReentrance[$sKey] = true; + // Protection against reentrance (e.g. cascading the update of ticket logs) + // Note: This is based on the fix made on r 3190 in DBObject::DBUpdate() + static $aUpdateReentrance = array(); + $sKey = get_class($this).'::'.$this->GetKey(); + if (array_key_exists($sKey, $aUpdateReentrance)) + { + return $res; + } + $aUpdateReentrance[$sKey] = true; - try - { - // Invoke extensions after the update (could be before) - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) - { - $oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange()); - } - } - catch(Exception $e) - { - unset($aUpdateReentrance[$sKey]); - throw $e; - } + try + { + // Invoke extensions after the update (could be before) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + { + $oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange()); + } + } catch (Exception $e) + { + unset($aUpdateReentrance[$sKey]); + throw $e; + } - unset($aUpdateReentrance[$sKey]); - return $res; + unset($aUpdateReentrance[$sKey]); + + return $res; } protected static function BulkUpdateTracked_Internal(DBSearch $oFilter, array $aValues) @@ -3448,7 +3662,7 @@ EOF protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) { // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { $oExtensionInstance->OnDBDelete($this, self::GetCurrentChange()); } @@ -3465,32 +3679,34 @@ EOF // Plugins // - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { if ($oExtensionInstance->OnIsModified($this)) { return true; } } + return false; } - + /** * Bypass the check of the user rights when writing this object + * * @param bool $bAllow True to bypass the checks, false to restore the default behavior */ public function AllowWrite($bAllow = true) { $this->bAllowWrite = $bAllow; } - + public function DoCheckToWrite() { parent::DoCheckToWrite(); // Plugins // - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { $aNewIssues = $oExtensionInstance->OnCheckToWrite($this); if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array @@ -3507,9 +3723,10 @@ EOF if (count($aChanges) > 0) { $aForbiddenFields = array(); - foreach ($this->ListChanges() as $sAttCode => $value) + foreach($this->ListChanges() as $sAttCode => $value) { - $bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, UR_ACTION_MODIFY, DBObjectSet::FromObject($this)); + $bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode, + UR_ACTION_MODIFY, DBObjectSet::FromObject($this)); if (!$bUpdateAllowed) { $oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode); @@ -3520,7 +3737,8 @@ EOF { // Security issue $this->m_bSecurityIssue = true; - $this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',implode(', ', $aForbiddenFields)); + $this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields', + implode(', ', $aForbiddenFields)); } } } @@ -3532,7 +3750,7 @@ EOF // Plugins // - foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) { $aNewIssues = $oExtensionInstance->OnCheckToDelete($this); if (is_array($aNewIssues) && count($aNewIssues) > 0) @@ -3543,7 +3761,8 @@ EOF // User rights // - $bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, DBObjectSet::FromObject($this)); + $bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, + DBObjectSet::FromObject($this)); if (!$bDeleteAllowed) { // Security issue @@ -3567,7 +3786,7 @@ EOF { $iFlags = $this->GetAttributeFlags($sAttCode); } - if ( $iFlags & OPT_ATT_HIDDEN) + if ($iFlags & OPT_ATT_HIDDEN) { // The case log is hidden do nothing } @@ -3575,8 +3794,8 @@ EOF { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); $sInputId = $this->m_iFormId.'_'.$sAttCode; - - if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))) + + if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE))) { // Check if the attribute is not read-only because of a synchro... $sSynchroIcon = ''; @@ -3599,7 +3818,8 @@ EOF // Attribute is read-only $sHTMLValue = $this->GetAsHTML($sAttCode); - $sHTMLValue .= ''; + $sHTMLValue .= ''; $aFieldsMap[$sAttCode] = $sInputId; $sComment .= $sSynchroIcon; } @@ -3613,7 +3833,9 @@ EOF { $sHTMLValue = ''.$sComment.'
'; } - $sHTMLValue .= "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $sHTMLValue .= "".self::GetFormElementForField($oPage, + $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, + $aArgs).''; $aFieldsMap[$sAttCode] = $sInputId; } //$aVal = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos); @@ -3623,14 +3845,16 @@ EOF } } - /** - * @param $sCurrentState - * @param $sStimulus - * @param $bOnlyNewOnes - * @return array - * @throws ApplicationException - * @deprecated Since iTop 2.4, use DBObject::GetTransitionAttributes() instead. - */ + /** + * @param $sCurrentState + * @param $sStimulus + * @param $bOnlyNewOnes + * + * @return array + * @throws \ApplicationException + * @throws \CoreException + * @deprecated Since iTop 2.4, use DBObject::GetTransitionAttributes() instead. + */ public function GetExpectedAttributes($sCurrentState, $sStimulus, $bOnlyNewOnes) { $aTransitions = $this->EnumTransitions(); @@ -3638,7 +3862,8 @@ EOF if (!isset($aTransitions[$sStimulus])) { // Invalid stimulus - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus,$this->GetName(),$this->GetStateLabel())); + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, + $this->GetName(), $this->GetStateLabel())); } $aTransition = $aTransitions[$sStimulus]; $sTargetState = $aTransition['target_state']; @@ -3657,35 +3882,51 @@ EOF } else { - if ( !($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN|OPT_ATT_READONLY)) ) + if (!($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))) { - $iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT|OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags + $iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT | OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags } //TODO: better check if the attribute is not *null* - if ( ($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) != '')) + if (($iExpectCode & OPT_ATT_MANDATORY) && ($this->Get($sAttCode) != '')) { $iExpectCode = $iExpectCode & ~(OPT_ATT_MANDATORY); // If the attribute is present, then no need to request its presence } - $aComputedAttributes[$sAttCode] = $iExpectCode; + $aComputedAttributes[$sAttCode] = $iExpectCode; } - $aComputedAttributes[$sAttCode] = $aComputedAttributes[$sAttCode] & ~(OPT_ATT_READONLY|OPT_ATT_HIDDEN); // Don't care about this form now + $aComputedAttributes[$sAttCode] = $aComputedAttributes[$sAttCode] & ~(OPT_ATT_READONLY | OPT_ATT_HIDDEN); // Don't care about this form now if ($aComputedAttributes[$sAttCode] == 0) { unset($aComputedAttributes[$sAttCode]); } } + return $aComputedAttributes; } /** * Display a form for modifying several objects at once - * The form will be submitted to the current page, with the specified additional values - */ - public static function DisplayBulkModifyForm($oP, $sClass, $aSelectedObj, $sCustomOperation, $sCancelUrl, $aExcludeAttributes = array(), $aContextData = array()) - { + * The form will be submitted to the current page, with the specified additional values + * + * @param \iTopWebPage $oP + * @param string $sClass + * @param array $aSelectedObj + * @param string $sCustomOperation + * @param string $sCancelUrl + * @param array $aExcludeAttributes + * @param array $aContextData + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException + * @throws \OQLException + */ + public static function DisplayBulkModifyForm( + $oP, $sClass, $aSelectedObj, $sCustomOperation, $sCancelUrl, $aExcludeAttributes = array(), + $aContextData = array() + ) { if (count($aSelectedObj) > 0) { $iAllowedCount = count($aSelectedObj); @@ -3693,8 +3934,8 @@ EOF $sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")"; $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL)); - - // Compute the distribution of the values for each field to determine which of the "scalar" fields are homogenous + + // Compute the distribution of the values for each field to determine which of the "scalar" fields are homogeneous $aList = MetaModel::ListAttributeDefs($sClass); $aValues = array(); foreach($aList as $sAttCode => $oAttDef) @@ -3704,7 +3945,7 @@ EOF $aValues[$sAttCode] = array(); } } - while($oObj = $oSet->Fetch()) + while ($oObj = $oSet->Fetch()) { foreach($aList as $sAttCode => $oAttDef) { @@ -3715,44 +3956,57 @@ EOF { $currValue = ' '; // Don't put an empty string, in case the field would be considered as mandatory... } - if (is_object($currValue)) continue; // Skip non scalar values... - if(!array_key_exists($currValue, $aValues[$sAttCode])) + elseif ($currValue instanceof ormSet) { - $aValues[$sAttCode][$currValue] = array('count' => 1, 'display' => $oObj->GetAsHTML($sAttCode)); + $currValue = $oAttDef->GetEditValue($currValue, $oObj); + } + if (is_object($currValue)) + { + continue; + } // Skip non scalar values... + if (!array_key_exists($currValue, $aValues[$sAttCode])) + { + $aValues[$sAttCode][$currValue] = array( + 'count' => 1, + 'display' => $oObj->GetAsHTML($sAttCode) + ); } else { - $aValues[$sAttCode][$currValue]['count']++; + $aValues[$sAttCode][$currValue]['count']++; } } } } - // Now create an object that has values for the homogenous values only + // Now create an object that has values for the homogeneous values only + /** @var \cmdbAbstractObject $oDummyObj */ $oDummyObj = new $sClass(); // @@ What if the class is abstract ? $aComments = array(); function MyComparison($a, $b) // Sort descending { - if ($a['count'] == $b['count']) - { - return 0; - } - return ($a['count'] > $b['count']) ? -1 : 1; + if ($a['count'] == $b['count']) + { + return 0; + } + + return ($a['count'] > $b['count']) ? -1 : 1; } $iFormId = cmdbAbstractObject::GetNextFormId(); // Identifier that prefixes all the form fields $sReadyScript = ''; - $aDependsOn = array(); $sFormPrefix = '2_'; foreach($aList as $sAttCode => $oAttDef) { - $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, + $sAttCode); // List of attributes that are needed for the current one if (count($aPrerequisites) > 0) { // When 'enabling' a field, all its prerequisites must be enabled too $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']"; $oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').bind('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); } - $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + $aDependents = MetaModel::GetDependentAttributes($sClass, + $sAttCode); // List of attributes that are needed for the current one if (count($aDependents) > 0) { // When 'disabling' a field, all its dependent fields must be disabled too @@ -3763,21 +4017,21 @@ EOF { if ($oAttDef->GetEditClass() == 'One Way Password') { - + $sTip = "Unknown values"; $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"; $oDummyObj->Set($sAttCode, null); - $aComments[$sAttCode] = ''; + $aComments[$sAttCode] = ''; $aComments[$sAttCode] .= '
?
'; - $sReadyScript .= 'ToogleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n"; + $sReadyScript .= 'ToggleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n"; } else { $iCount = count($aValues[$sAttCode]); if ($iCount == 1) { - // Homogenous value + // Homogeneous value reset($aValues[$sAttCode]); $aKeys = array_keys($aValues[$sAttCode]); $currValue = $aKeys[0]; // The only value is the first key @@ -3786,13 +4040,13 @@ EOF $aComments[$sAttCode] = ''; if ($sAttCode != MetaModel::GetStateAttributeCode($sClass)) { - $aComments[$sAttCode] .= ''; + $aComments[$sAttCode] .= ''; } $aComments[$sAttCode] .= '
1
'; } else { - // Non-homogenous value + // Non-homogeneous value $aMultiValues = $aValues[$sAttCode]; uasort($aMultiValues, 'MyComparison'); $iMaxCount = 5; @@ -3800,32 +4054,60 @@ EOF $index = 0; foreach($aMultiValues as $sCurrValue => $aVal) { - $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']); - $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."
  • "; + $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array( + "\n", + "\r" + ), " ", $aVal['display']); + $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, + $aVal['count'])."
  • "; $index++; if ($iMaxCount == $index) { - $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', count($aMultiValues) - $iMaxCount)."
  • "; + $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', + count($aMultiValues) - $iMaxCount)."
  • "; break; - } + } } $sTip .= "

    "; $sTip = addslashes($sTip); $sReadyScript .= "$('#multi_values_$sAttCode').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"; - - $oDummyObj->Set($sAttCode, null); + + if (($oAttDef->GetEditClass() == 'TagSet') || ($oAttDef->GetEditClass() == 'Set')) + { + // Set the value by adding the values to the first one + reset($aMultiValues); + $aKeys = array_keys($aMultiValues); + $currValue = $aKeys[0]; + $oDummyObj->Set($sAttCode, $currValue); + /** @var ormTagSet $oTagSet */ + $oTagSet = $oDummyObj->Get($sAttCode); + foreach($aKeys as $iIndex => $sValues) + { + if ($iIndex == 0) + { + continue; + } + $aTagCodes = $oAttDef->FromStringToArray($sValues); + $oTagSet->GenerateDiffFromArray($aTagCodes); + } + $oDummyObj->Set($sAttCode, $oTagSet); + } + else + { + $oDummyObj->Set($sAttCode, null); + } $aComments[$sAttCode] = ''; if ($sAttCode != MetaModel::GetStateAttributeCode($sClass)) { - $aComments[$sAttCode] .= ''; + $aComments[$sAttCode] .= ''; } $aComments[$sAttCode] .= '
    '.$iCount.'
    '; } - $sReadyScript .= 'ToogleField('.(($iCount == 1) ? 'true': 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n"; + $sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n"; } } - } - + } + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); if (($sStateAttCode != '') && ($oDummyObj->GetState() == '')) { @@ -3837,12 +4119,13 @@ EOF { $oDummyObj->Set($sStateAttCode, $sCurrValue); break; - } + } //$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode); //$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue()); } $oP->add("
    \n"); - $oP->add("

    ".$oDummyObj->GetIcon()." ".Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', $iAllowedCount, $sClass, $iAllowedCount)."

    \n"); + $oP->add("

    ".$oDummyObj->GetIcon()." ".Dict::Format('UI:Modify_M_ObjectsOf_Class_OutOf_N', + $iAllowedCount, $sClass, $iAllowedCount)."

    \n"); $oP->add("
    \n"); $oP->add("
    \n"); @@ -3860,16 +4143,16 @@ EOF 'disable_plugins' => true ); $aParams = $aParams + $aContextData; // merge keeping associations - + $oDummyObj->DisplayModifyForm($oP, $aParams); $oP->add("
    \n"); $oP->add_ready_script($sReadyScript); $oP->add_ready_script( -<< array('label' => "", 'description' => Dict::S('UI:SelectAllToggle+')), + 'form::select' => array( + 'label' => "", + 'description' => Dict::S('UI:SelectAllToggle+') + ), 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), - 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), - 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), + 'status' => array( + 'label' => Dict::S('UI:BulkModifyStatus'), + 'description' => Dict::S('UI:BulkModifyStatus+') + ), + 'errors' => array( + 'label' => Dict::S('UI:BulkModifyErrors'), + 'description' => Dict::S('UI:BulkModifyErrors+') + ), ); $aRows = array(); $oP->add("
    \n"); - $oP->add("

    ".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), MetaModel::GetName($sClass))."

    \n"); + $oP->add("

    ".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Modify_N_ObjectsOf_Class', + count($aSelectedObj), MetaModel::GetName($sClass))."

    \n"); $oP->add("
    \n"); $oP->set_title(Dict::Format('UI:Modify_N_ObjectsOf_Class', count($aSelectedObj), $sClass)); if (!$bPreview) @@ -3910,6 +4204,7 @@ EOF foreach($aSelectedObj as $iId) { set_time_limit($iLoopTimeLimit); + /** @var \cmdbAbstractObject $oObj */ $oObj = MetaModel::GetObject($sClass, $iId); $aErrors = $oObj->UpdateObjectFromPostedForm(''); $bResult = (count($aErrors) == 0); @@ -3932,7 +4227,7 @@ EOF 'form::select' => "", 'object' => $oObj->GetHyperlink(), 'status' => $sStatus, - 'errors' => '

    '.($bResult ? '': implode('

    ', $aErrors)).'

    ', + 'errors' => '

    '.($bResult ? '' : implode('

    ', $aErrors)).'

    ', '@class' => $sCSSClass, ); if ($bResult && (!$bPreview)) @@ -3950,7 +4245,7 @@ EOF $aDefaults = utils::ReadParam('default', array()); $oAppContext = new ApplicationContext(); $oP->add($oAppContext->GetForForm()); - foreach ($aContextData as $sKey => $value) + foreach($aContextData as $sKey => $value) { $oP->add("\n"); } @@ -3969,12 +4264,14 @@ EOF { foreach($value as $vKey => $vValue) { - $oP->add("\n"); + $oP->add("\n"); } } else { - $oP->add("\n"); + $oP->add("\n"); } } } @@ -3988,11 +4285,22 @@ EOF /** * Perform all the needed checks to delete one (or more) objects + * + * @param \WebPage $oP + * @param $sClass + * @param $aObjects + * @param $bPreview + * @param $sCustomOperation + * @param array $aContextData + * + * @throws \CoreException + * @throws \DictExceptionMissingString */ - public static function DeleteObjects(WebPage $oP, $sClass, $aObjects, $bPreview, $sCustomOperation, $aContextData = array()) - { + public static function DeleteObjects( + WebPage $oP, $sClass, $aObjects, $bPreview, $sCustomOperation, $aContextData = array() + ) { $oDeletionPlan = new DeletionPlan(); - + foreach($aObjects as $oObj) { if ($bPreview) @@ -4004,7 +4312,7 @@ EOF $oObj->DBDeleteTracked(CMDBObject::GetCurrentChange(), null, $oDeletionPlan); } } - + if ($bPreview) { if (count($aObjects) == 1) @@ -4014,14 +4322,15 @@ EOF } else { - $oP->add("

    ".Dict::Format('UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))."

    \n"); + $oP->add("

    ".Dict::Format('UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class', count($aObjects), + MetaModel::GetName($sClass))."

    \n"); } // Explain what should be done // $aDisplayData = array(); - foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) + foreach($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) { - foreach ($aDeletes as $iId => $aData) + foreach($aDeletes as $iId => $aData) { $oToDelete = $aData['to_delete']; $bAutoDel = (($aData['mode'] == DEL_SILENT) || ($aData['mode'] == DEL_AUTO)); @@ -4035,12 +4344,14 @@ EOF } else { - $sConsequence = Dict::Format('UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible', $aData['issue']); + $sConsequence = Dict::Format('UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible', + $aData['issue']); } } else { - $sConsequence = Dict::Format('UI:Delete:MustBeDeletedManuallyButNotPossible', $aData['issue']); + $sConsequence = Dict::Format('UI:Delete:MustBeDeletedManuallyButNotPossible', + $aData['issue']); } } else @@ -4049,7 +4360,7 @@ EOF { if (isset($aData['requested_explicitely'])) { - $sConsequence = ''; // not applicable + $sConsequence = ''; // not applicable } else { @@ -4068,9 +4379,9 @@ EOF ); } } - foreach ($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate) + foreach($oDeletionPlan->ListUpdates() as $sRemoteClass => $aToUpdate) { - foreach ($aToUpdate as $iId => $aData) + foreach($aToUpdate as $iId => $aData) { $oToUpdate = $aData['to_reset']; if (array_key_exists('issue', $aData)) @@ -4079,7 +4390,8 @@ EOF } else { - $sConsequence = Dict::Format('UI:Delete:WillAutomaticallyUpdate_Fields', $aData['attributes_list']); + $sConsequence = Dict::Format('UI:Delete:WillAutomaticallyUpdate_Fields', + $aData['attributes_list']); } $aDisplayData[] = array( 'class' => MetaModel::GetName(get_class($oToUpdate)), @@ -4088,14 +4400,15 @@ EOF ); } } - - $iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects); + + $iImpactedIndirectly = $oDeletionPlan->GetTargetCount() - count($aObjects); if ($iImpactedIndirectly > 0) { if (count($aObjects) == 1) { $oObj = $aObjects[0]; - $oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly, $oObj->GetName())); + $oP->p(Dict::Format('UI:Delete:Count_Objects/LinksReferencing_Object', $iImpactedIndirectly, + $oObj->GetName())); } else { @@ -4103,16 +4416,19 @@ EOF } $oP->p(Dict::S('UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity')); } - + if (($iImpactedIndirectly > 0) || $oDeletionPlan->FoundStopper()) { $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); $aDisplayConfig['object'] = array('label' => 'Object', 'description' => ''); - $aDisplayConfig['consequence'] = array('label' => 'Consequence', 'description' => Dict::S('UI:Delete:Consequence+')); + $aDisplayConfig['consequence'] = array( + 'label' => 'Consequence', + 'description' => Dict::S('UI:Delete:Consequence+') + ); $oP->table($aDisplayConfig, $aDisplayData); } - + if ($oDeletionPlan->FoundStopper()) { if ($oDeletionPlan->FoundSecurityIssue()) @@ -4126,7 +4442,7 @@ EOF else // $bFoundManualOp { $oP->p(Dict::S('UI:Delete:PleaseDoTheManualOperations')); - } + } $oAppContext = new ApplicationContext(); $oP->add("
    \n"); $oP->add("\n"); @@ -4145,7 +4461,8 @@ EOF } else { - $oP->p('

    '.Dict::Format('UI:Delect:Confirm_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass)).'

    '); + $oP->p('

    '.Dict::Format('UI:Delect:Confirm_Count_ObjectsOf_Class', count($aObjects), + MetaModel::GetName($sClass)).'

    '); } foreach($aObjects as $oObj) { @@ -4158,7 +4475,7 @@ EOF CMDBAbstractObject::DisplaySet($oP, $oSet, array('display_limit' => false, 'menu' => false)); $oP->add("\n"); $oP->add("\n"); - foreach ($aContextData as $sKey => $value) + foreach($aContextData as $sKey => $value) { $oP->add("\n"); } @@ -4184,11 +4501,12 @@ EOF if (count($aObjects) == 1) { $oObj = $aObjects[0]; - $oP->add("

    ".Dict::Format('UI:Title:DeletionOf_Object', $oObj->GetName())."

    \n"); + $oP->add("

    ".Dict::Format('UI:Title:DeletionOf_Object', $oObj->GetName())."

    \n"); } else { - $oP->add("

    ".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))."

    \n"); + $oP->add("

    ".Dict::Format('UI:Title:BulkDeletionOf_Count_ObjectsOf_Class', count($aObjects), + MetaModel::GetName($sClass))."

    \n"); } // Security - do not allow the user to force a forbidden delete by the mean of page arguments... if ($oDeletionPlan->FoundSecurityIssue()) @@ -4203,16 +4521,16 @@ EOF { throw new CoreException(Dict::S('UI:Error:CannotDeleteBecauseOfDepencies')); } - + // Report deletions // $aDisplayData = array(); - foreach ($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) + foreach($oDeletionPlan->ListDeletes() as $sTargetClass => $aDeletes) { - foreach ($aDeletes as $iId => $aData) + foreach($aDeletes as $iId => $aData) { $oToDelete = $aData['to_delete']; - + if (isset($aData['requested_explicitely'])) { $sMessage = Dict::S('UI:Delete:Deleted'); @@ -4228,12 +4546,12 @@ EOF ); } } - + // Report updates // - foreach ($oDeletionPlan->ListUpdates() as $sTargetClass => $aToUpdate) + foreach($oDeletionPlan->ListUpdates() as $sTargetClass => $aToUpdate) { - foreach ($aToUpdate as $iId => $aData) + foreach($aToUpdate as $iId => $aData) { $oToUpdate = $aData['to_reset']; $aDisplayData[] = array( @@ -4243,7 +4561,7 @@ EOF ); } } - + // Report automatic jobs // if ($oDeletionPlan->GetTargetCount() > 0) @@ -4255,7 +4573,8 @@ EOF } else { - $oP->p(Dict::Format('UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class', count($aObjects), MetaModel::GetName($sClass))); + $oP->p(Dict::Format('UI:Delete:CleaningUpRefencesTo_Several_ObjectsOf_Class', count($aObjects), + MetaModel::GetName($sClass))); } $aDisplayConfig = array(); $aDisplayConfig['class'] = array('label' => 'Class', 'description' => ''); @@ -4268,12 +4587,12 @@ EOF /** * Find redundancy settings that can be viewed and modified in a tab - * Settings are distributed to the corresponding link set attribute so as to be shown in the relevant tab - */ + * Settings are distributed to the corresponding link set attribute so as to be shown in the relevant tab + */ protected function FindVisibleRedundancySettings() { $aRet = array(); - foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) + foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) { if ($oAttDef instanceof AttributeRedundancySettings) { @@ -4282,7 +4601,8 @@ EOF $aQueryInfo = $oAttDef->GetRelationQueryData(); if (isset($aQueryInfo['sAttribute'])) { - $oUpperAttDef = MetaModel::GetAttributeDef($aQueryInfo['sFromClass'], $aQueryInfo['sAttribute']); + $oUpperAttDef = MetaModel::GetAttributeDef($aQueryInfo['sFromClass'], + $aQueryInfo['sAttribute']); $oHostAttDef = $oUpperAttDef->GetMirrorLinkAttribute(); if ($oHostAttDef) { @@ -4292,25 +4612,28 @@ EOF } } } - } + } + return $aRet; } /** * Generates the javascript code handle the "watchdog" associated with the concurrent access locking mechanism + * * @param Webpage $oPage * @param string $sOwnershipToken */ protected function GetOwnershipJSHandler($oPage, $sOwnershipToken) { - $iInterval = max(MIN_WATCHDOG_INTERVAL, MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')) * 1000 / 2; // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL + $iInterval = max(MIN_WATCHDOG_INTERVAL, + MetaModel::GetConfig()->Get('concurrent_lock_expiration_delay')) * 1000 / 2; // Minimum interval for the watchdog is MIN_WATCHDOG_INTERVAL $sJSClass = json_encode(get_class($this)); - $iKey = (int) $this->GetKey(); + $iKey = (int)$this->GetKey(); $sJSToken = json_encode($sOwnershipToken); $sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle')); $sJSOk = json_encode(Dict::S('UI:Button:Ok')); $oPage->add_ready_script( -<< - + portal/index.php diff --git a/application/itopwebpage.class.inc.php b/application/itopwebpage.class.inc.php index 9b018c5256..ecba75b4b7 100644 --- a/application/itopwebpage.class.inc.php +++ b/application/itopwebpage.class.inc.php @@ -259,6 +259,36 @@ EOF; EOF ); + // Attribute set tooltip on items + $this->add_ready_script( +<<').text($(this).attr('data-label')).html(); + var sDescription = $(this).attr('data-description'); + + // Make nice tooltip if item has a description, otherwise just make a title attribute so the truncated label can be read. + if(sDescription !== '') + { + $(this).qtip({ + content: { + // Encoding only title as the content is already sanitized by the HTML attribute. + text: sDescription, + title: { text: sLabel}, + }, + show: { delay: 300, when: 'mouseover' }, + hide: { delay: 140, when: 'mouseout', fixed: true }, + style: { name: 'dark', tip: 'bottomLeft' }, + position: { corner: { target: 'topMiddle', tooltip: 'bottomLeft' }} + }); + } + else + { + $(this).attr('title', sLabel); + } + }); +EOF + ); + $this->add_init_script( <<< EOF try diff --git a/application/nicewebpage.class.inc.php b/application/nicewebpage.class.inc.php index bc7056ea7f..e1c0d8bf3f 100644 --- a/application/nicewebpage.class.inc.php +++ b/application/nicewebpage.class.inc.php @@ -62,6 +62,7 @@ class NiceWebPage extends WebPage $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_field.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_numeric.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_tag_set.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_key.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_hierarchical_key.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_abstract.js'); diff --git a/application/query.class.inc.php b/application/query.class.inc.php index cfe8f640b1..38d5ca7a16 100644 --- a/application/query.class.inc.php +++ b/application/query.class.inc.php @@ -47,7 +47,7 @@ abstract class Query extends cmdbAbstractObject MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array()))); - MetaModel::Init_AddAttribute(new AttributeText("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeQueryAttCodeSet("fields", array("allowed_values"=>null,"max_items" => 1000, "query_field" => "oql", "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array('oql')))); // Display lists MetaModel::Init_SetZListItems('details', array('name', 'description', 'fields')); // Attributes to be displayed for the complete details @@ -136,6 +136,39 @@ class QueryOQL extends Query } return $aFieldsMap; } + + public function ComputeValues() + { + parent::ComputeValues(); + + // Remove unwanted attribute codes + $aChanges = $this->ListChanges(); + if (isset($aChanges['fields'])) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), 'fields'); + $aArgs = array('this' => $this); + $aAllowedValues = $oAttDef->GetAllowedValues($aArgs); + + /** @var \ormSet $oValue */ + $oValue = $this->Get('fields'); + $aValues = $oValue->GetValues(); + $bChanged = false; + foreach($aValues as $key => $sValue) + { + if (!isset($aAllowedValues[$sValue])) + { + unset($aValues[$key]); + $bChanged = true; + } + } + if ($bChanged) + { + $oValue->SetValues($aValues); + $this->Set('fields', $oValue); + } + } + } + } ?> diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index b7224ca4b8..d06c5c06e9 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -311,7 +311,10 @@ class WebPage implements Page } /** - * Add a script (as an include, i.e. link) to the header of the page + * Add a script (as an include, i.e. link) to the header of the page.
    + * Handles duplicates : calling twice with the same script will add the script only once + * + * @param string $s_linked_script */ public function add_linked_script($s_linked_script) { diff --git a/application/wizardhelper.class.inc.php b/application/wizardhelper.class.inc.php index 379cd58a2f..0f6def8050 100644 --- a/application/wizardhelper.class.inc.php +++ b/application/wizardhelper.class.inc.php @@ -174,6 +174,14 @@ class WizardHelper } $oObj->Set($sAttCode, $value); } + else if ($oAttDef instanceof AttributeSet) // AttributeDate is derived from AttributeDateTime + { + $value = json_decode($value, true); + $oTagSet = new ormTagSet(get_class($oObj), $sAttCode); + $oTagSet->SetValues($value['orig_value']); + $oTagSet->ApplyDelta($value); + $oObj->Set($sAttCode, $oTagSet); + } else { $oObj->Set($sAttCode, $value); diff --git a/composer.json b/composer.json index 8bd34a0a76..dbbe9b0013 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "ext-soap": "*", "ext-json": "*", "ext-zip": "*", - "ext-mysqli": "*" + "ext-mysqli": "*", + "ext-dom": "*" }, "config": { "platform": { diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index f81777ed82..cbb72ffb40 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -21,7 +21,7 @@ * Typology for the attributes * * @copyright Copyright (C) 2010-2018 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 + * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -31,48 +31,51 @@ require_once('ormstopwatch.class.inc.php'); require_once('ormpassword.class.inc.php'); require_once('ormcaselog.class.inc.php'); require_once('ormlinkset.class.inc.php'); +require_once('ormset.class.inc.php'); +require_once('ormtagset.class.inc.php'); require_once('htmlsanitizer.class.inc.php'); require_once(APPROOT.'sources/autoload.php'); require_once('customfieldshandler.class.inc.php'); require_once('ormcustomfieldsvalue.class.inc.php'); require_once('datetimeformat.class.inc.php'); // This should be changed to a use when we go full-namespace -require_once(APPROOT . 'sources/form/validator/validator.class.inc.php'); -require_once(APPROOT . 'sources/form/validator/notemptyextkeyvalidator.class.inc.php'); +require_once(APPROOT.'sources/form/validator/validator.class.inc.php'); +require_once(APPROOT.'sources/form/validator/notemptyextkeyvalidator.class.inc.php'); /** - * MissingColumnException - sent if an attribute is being created but the column is missing in the row + * MissingColumnException - sent if an attribute is being created but the column is missing in the row * - * @package iTopORM + * @package iTopORM */ class MissingColumnException extends Exception -{} +{ +} /** - * add some description here... + * add some description here... * - * @package iTopORM + * @package iTopORM */ define('EXTKEY_RELATIVE', 1); /** - * add some description here... + * add some description here... * - * @package iTopORM + * @package iTopORM */ define('EXTKEY_ABSOLUTE', 2); /** - * Propagation of the deletion through an external key - ask the user to delete the referencing object + * Propagation of the deletion through an external key - ask the user to delete the referencing object * - * @package iTopORM + * @package iTopORM */ define('DEL_MANUAL', 1); /** - * Propagation of the deletion through an external key - ask the user to delete the referencing object + * Propagation of the deletion through an external key - ask the user to delete the referencing object * - * @package iTopORM + * @package iTopORM */ define('DEL_AUTO', 2); /** @@ -88,7 +91,7 @@ define('DEL_MOVEUP', 3); /** * For Link sets: tracking_level * - * @package iTopORM + * @package iTopORM */ define('ATTRIBUTE_TRACKING_NONE', 0); // Do not track changes of the attribute define('ATTRIBUTE_TRACKING_ALL', 3); // Do track all changes of the attribute @@ -105,9 +108,9 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re /** - * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) + * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) * - * @package iTopORM + * @package iTopORM */ abstract class AttributeDefinition { @@ -120,15 +123,21 @@ abstract class AttributeDefinition const SEARCH_WIDGET_TYPE_EXTERNAL_FIELD = 'external_field'; const SEARCH_WIDGET_TYPE_DATE_TIME = 'date_time'; const SEARCH_WIDGET_TYPE_DATE = 'date'; + const SEARCH_WIDGET_TYPE_SET = 'set'; + const SEARCH_WIDGET_TYPE_TAG_SET = 'tag_set'; + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; const INDEX_LENGTH = 95; + protected $aCSSClasses; + public function GetType() { return Dict::S('Core:'.get_class($this)); } + public function GetTypeDesc() { return Dict::S('Core:'.get_class($this).'+'); @@ -157,9 +166,14 @@ abstract class AttributeDefinition protected $m_sCode; private $m_aParams = array(); protected $m_sHostClass = '!undefined!'; - public function Get($sParamName) {return $this->m_aParams[$sParamName];} - public function GetIndexLength() { + public function Get($sParamName) + { + return $this->m_aParams[$sParamName]; + } + + public function GetIndexLength() + { $iMaxLength = $this->GetMaxSize(); if (is_null($iMaxLength)) { @@ -169,10 +183,14 @@ abstract class AttributeDefinition { return static::INDEX_LENGTH; } + return $iMaxLength; } - public function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} + public function IsParam($sParamName) + { + return (array_key_exists($sParamName, $this->m_aParams)); + } protected function GetOptional($sParamName, $default) { @@ -186,19 +204,20 @@ abstract class AttributeDefinition } } - /** - * AttributeDefinition constructor. - * - * @param string $sCode - * @param array $aParams - * - * @throws \Exception - */ - public function __construct($sCode, $aParams) + /** + * AttributeDefinition constructor. + * + * @param string $sCode + * @param array $aParams + * + * @throws \Exception + */ + public function __construct($sCode, $aParams) { $this->m_sCode = $sCode; $this->m_aParams = $aParams; $this->ConsistencyCheck(); + $this->aCSSClasses = array('attribute'); } public function GetParams() @@ -215,17 +234,18 @@ abstract class AttributeDefinition { $this->m_sHostClass = $sHostClass; } + public function GetHostClass() { return $this->m_sHostClass; } - /** - * @return array - * - * @throws \CoreException - */ - public function ListSubItems() + /** + * @return array + * + * @throws \CoreException + */ + public function ListSubItems() { $aSubItems = array(); foreach(MetaModel::ListAttributeDefs($this->m_sHostClass) as $sAttCode => $oAttDef) @@ -238,6 +258,7 @@ abstract class AttributeDefinition } } } + return $aSubItems; } @@ -248,10 +269,10 @@ abstract class AttributeDefinition return array(); } - /** - * @throws \Exception - */ - private function ConsistencyCheck() + /** + * @throws \Exception + */ + private function ConsistencyCheck() { // Check that any mandatory param has been specified // @@ -272,17 +293,22 @@ abstract class AttributeDefinition * Check the validity of the given value * * @param \DBObject $oHostObject - * @param $value An error if any, null otherwise + * @param $value Object error if any, null otherwise * * @return bool */ public function CheckValue(DBObject $oHostObject, $value) { - // todo: factorize here the cases implemented into DBObject + // later: factorize here the cases implemented into DBObject return true; } // table, key field, name field + + /** + * @return string + * @deprecated never used + */ public function ListDBJoins() { return ""; @@ -296,69 +322,122 @@ abstract class AttributeDefinition /** * Deprecated - use IsBasedOnDBColumns instead + * * @return bool */ - public function IsDirectField() {return static::IsBasedOnDBColumns();} + public function IsDirectField() + { + return static::IsBasedOnDBColumns(); + } /** * Returns true if the attribute value is built after DB columns + * * @return bool */ - static public function IsBasedOnDBColumns() {return false;} + static public function IsBasedOnDBColumns() + { + return false; + } + /** - * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) + * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via + * GetOQLExpression) + * * @return bool */ - static public function IsBasedOnOQLExpression() {return false;} + static public function IsBasedOnOQLExpression() + { + return false; + } + /** * Returns true if the attribute value can be shown as a string + * * @return bool */ - static public function IsScalar() {return false;} + static public function IsScalar() + { + return false; + } + /** * Returns true if the attribute value is a set of related objects (1-N or N-N) + * * @return bool */ - static public function IsLinkSet() {return false;} + static public function IsLinkSet() + { + return false; + } /** * @param int $iType * - * @return bool true if the attribute is an external key, either directly (RELATIVE to the host class), or indirectly (ABSOLUTELY) + * @return bool true if the attribute is an external key, either directly (RELATIVE to the host class), or + * indirectly (ABSOLUTELY) */ public function IsExternalKey($iType = EXTKEY_RELATIVE) { return false; } + /** * @return bool true if the attribute value is an external key, pointing to the host class */ - static public function IsHierarchicalKey() {return false;} + static public function IsHierarchicalKey() + { + return false; + } + /** * @return bool true if the attribute value is stored on an object pointed to be an external key */ - static public function IsExternalField() {return false;} + static public function IsExternalField() + { + return false; + } + /** * @return bool true if the attribute can be written (by essence : metamodel field option) * @see \DBObject::IsAttributeReadOnlyForCurrentState() for a specific object instance (depending on its workflow) */ - public function IsWritable() {return false;} + public function IsWritable() + { + return false; + } + /** * @return bool true if the attribute has been added automatically by the framework */ - public function IsMagic() {return $this->GetOptional('magic', false);} + public function IsMagic() + { + return $this->GetOptional('magic', false); + } + /** * @return bool true if the attribute value is kept in the loaded object (in memory) */ - static public function LoadInObject() {return true;} + static public function LoadInObject() + { + return true; + } + /** * @return bool true if the attribute value comes from the database in one way or another */ - static public function LoadFromDB() {return true;} + static public function LoadFromDB() + { + return true; + } + /** * @return bool true if the attribute should be loaded anytime (in addition to the column selected by the user) */ - public function AlwaysLoadInTables() {return $this->GetOptional('always_load_in_tables', false);} + public function AlwaysLoadInTables() + { + return $this->GetOptional('always_load_in_tables', false); + } /** * @param \DBObject $oHostObject @@ -372,20 +451,33 @@ abstract class AttributeDefinition /** * Returns true if the attribute must not be stored if its current value is "null" (Cf. IsNull()) + * * @return bool */ - public function IsNullAllowed() {return true;} + public function IsNullAllowed() + { + return true; + } + /** * Returns the attribute code (identifies the attribute in the host class) + * * @return string */ - public function GetCode() {return $this->m_sCode;} + public function GetCode() + { + return $this->m_sCode; + } /** * Find the corresponding "link" attribute on the target class, if any + * * @return null | AttributeDefinition */ - public function GetMirrorLinkAttribute() {return null;} + public function GetMirrorLinkAttribute() + { + return null; + } /** * Helper to browse the hierarchy of classes, searching for a label @@ -415,17 +507,18 @@ abstract class AttributeDefinition } } } + return $sLabel; } - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetLabel($sDefault = null) + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetLabel($sDefault = null) { $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, null, true /*user lang*/); if (is_null($sLabel)) @@ -438,6 +531,7 @@ abstract class AttributeDefinition // Browse the hierarchy again, accepting default (english) translations $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, $sDefault, false); } + return $sLabel; } @@ -472,14 +566,15 @@ abstract class AttributeDefinition $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null - ) - { + ) { return $this->MakeRealValue($sProposedValue, null); } /** * Parses a search string coming from user input + * * @param string $sSearchString + * * @return string */ public function ParseSearchString($sSearchString) @@ -487,12 +582,12 @@ abstract class AttributeDefinition return $sSearchString; } - /** - * @return string - * - * @throws \Exception - */ - public function GetLabel_Obsolete() + /** + * @return string + * + * @throws \Exception + */ + public function GetLabel_Obsolete() { // Written for compatibility with a data model written prior to version 0.9.1 if (array_key_exists('label', $this->m_aParams)) @@ -505,14 +600,14 @@ abstract class AttributeDefinition } } - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetDescription($sDefault = null) + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetDescription($sDefault = null) { $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', null, true /*user lang*/); if (is_null($sLabel)) @@ -525,17 +620,18 @@ abstract class AttributeDefinition // Browse the hierarchy again, accepting default (english) translations $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', $sDefault, false); } + return $sLabel; } - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetHelpOnEdition($sDefault = null) + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetHelpOnEdition($sDefault = null) { $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', null, true /*user lang*/); if (is_null($sLabel)) @@ -548,29 +644,31 @@ abstract class AttributeDefinition // Browse the hierarchy again, accepting default (english) translations $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', $sDefault, false); } + return $sLabel; - } + } public function GetHelpOnSmartSearch() { $aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this)); - foreach ($aParents as $sClass) + foreach($aParents as $sClass) { $sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-'); if ($sHelp != '-missing-') { return $sHelp; } - } + } + return ''; } - /** - * @return string - * - * @throws \Exception - */ - public function GetDescription_Obsolete() + /** + * @return string + * + * @throws \Exception + */ + public function GetDescription_Obsolete() { // Written for compatibility with a data model written prior to version 0.9.1 if (array_key_exists('description', $this->m_aParams)) @@ -591,14 +689,20 @@ abstract class AttributeDefinition /** * @return \ValueSetObjects */ - public function GetValuesDef() {return null;} + public function GetValuesDef() + { + return null; + } public function GetPrerequisiteAttributes($sClass = null) { return array(); } - public function GetNullValue() {return null;} + public function GetNullValue() + { + return null; + } public function IsNull($proposedValue) { @@ -618,7 +722,10 @@ abstract class AttributeDefinition return $proposedValue; } - public function Equals($val1, $val2) {return ($val1 == $val2);} + public function Equals($val1, $val2) + { + return ($val1 == $val2); + } /** * @param string $sPrefix @@ -661,37 +768,50 @@ abstract class AttributeDefinition { return array(); } - public function RequiresIndex() {return false;} + + public function RequiresIndex() + { + return false; + } public function RequiresFullTextIndex() { return false; } - public function CopyOnAllTables() {return false;} + + public function CopyOnAllTables() + { + return false; + } public function GetOrderBySQLExpressions($sClassAlias) { // Note: This is the responsibility of this function to place backticks around column aliases return array('`'.$sClassAlias.$this->GetCode().'`'); } - + public function GetOrderByHint() { return ''; } - // Import - differs slightly from SQL input, but identical in most cases - // - public function GetImportColumns() {return $this->GetSQLColumns();} + // Import - differs slightly from SQL input, but identical in most cases + // + public function GetImportColumns() + { + return $this->GetSQLColumns(); + } + public function FromImportToValue($aCols, $sPrefix = '') { $aValues = array(); - foreach ($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr) + foreach($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr) { // This is working, based on the assumption that importable fields // are not computed fields => the expression is the name of a column $aValues[$sPrefix.$sAlias] = $aCols[$sExpr]; } + return $this->FromSQLToValue($aValues, $sPrefix); } @@ -699,35 +819,44 @@ abstract class AttributeDefinition { return ''; } - + public function CheckFormat($value) { return true; } - + public function GetMaxSize() { return null; } - + + /** + * @return mixed|null + * @deprecated never used + */ public function MakeValue() { $sComputeFunc = $this->Get("compute_func"); - if (empty($sComputeFunc)) return null; + if (empty($sComputeFunc)) + { + return null; + } return call_user_func($sComputeFunc); } - + abstract public function GetDefaultValue(DBObject $oHostObject = null); // // To be overloaded in subclasses // - + abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description" + abstract public function GetBasicFilterLooseOperator(); // returns an "opCode" + //abstract protected GetBasicFilterHTMLInput(); - abstract public function GetBasicFilterSQLExpr($sOpCode, $value); + abstract public function GetBasicFilterSQLExpr($sOpCode, $value); public function GetFilterDefinitions() { @@ -749,7 +878,7 @@ abstract class AttributeDefinition */ public function GetAsPlainText($sValue, $oHostObj = null) { - return (string) $this->GetEditValue($sValue, $oHostObj); + return (string)$this->GetEditValue($sValue, $oHostObj); } /** @@ -820,8 +949,10 @@ abstract class AttributeDefinition * * @return string */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { return (string)$sValue; } @@ -847,8 +978,8 @@ abstract class AttributeDefinition /** * Override to specify Field class * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is - * passed, MakeFormField behave more like a Prepare. + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the + * $oFormField is passed, MakeFormField behave more like a Prepare. * * @param \DBObject $oObject * @param \Combodo\iTop\Form\Field\Field $oFormField @@ -889,7 +1020,7 @@ abstract class AttributeDefinition { $oFormField->SetReadOnly(true); } - + // CurrentValue $oFormField->SetCurrentValue($oObject->Get($this->GetCode())); @@ -904,7 +1035,7 @@ abstract class AttributeDefinition /** * List the available verbs for 'GetForTemplate' - */ + */ public function EnumTemplateVerbs() { return array( @@ -924,7 +1055,7 @@ abstract class AttributeDefinition * @param bool $bLocalize Whether or not to localize the value * * @return mixed|null|string - * + * * @throws \Exception */ public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) @@ -934,22 +1065,23 @@ abstract class AttributeDefinition switch ($sVerb) { case '': - return $value; - + return $value; + case 'html': - return $this->GetAsHtml($value, $oHostObject, $bLocalize); - + return $this->GetAsHtml($value, $oHostObject, $bLocalize); + case 'label': - return $this->GetEditValue($value); - + return $this->GetEditValue($value); + case 'text': - return $this->GetAsPlainText($value); - break; - + return $this->GetAsPlainText($value); + break; + default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); } } + return null; } @@ -964,7 +1096,11 @@ abstract class AttributeDefinition public function GetAllowedValues($aArgs = array(), $sContains = '') { $oValSetDef = $this->GetValuesDef(); - if (!$oValSetDef) return null; + if (!$oValSetDef) + { + return null; + } + return $oValSetDef->GetValues($aArgs, $sContains); } @@ -992,40 +1128,44 @@ abstract class AttributeDefinition $sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue); $sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue); - if($this->IsExternalKey()) + if ($this->IsExternalKey()) { /** @var \AttributeExternalKey $this */ $sTargetClass = $this->GetTargetClass(); $sOldValueHtml = (int)$sOldValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sOldValue) : null; $sNewValueHtml = (int)$sNewValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sNewValue) : null; } - if ( (($this->GetType() == 'String') || ($this->GetType() == 'Text')) && - (strlen($sNewValue) > strlen($sOldValue)) ) + if ((($this->GetType() == 'String') || ($this->GetType() == 'Text')) && + (strlen($sNewValue) > strlen($sOldValue))) { // Check if some text was not appended to the field - if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end + if (substr($sNewValue, 0, strlen($sOldValue)) == $sOldValue) // Text added at the end { $sDelta = $this->GetAsHTML(substr($sNewValue, strlen($sOldValue))); $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); } - else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning - { - $sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue))); - $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); - } else { - if (strlen($sOldValue) == 0) + if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning { - $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); + $sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue))); + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); } else { - if (is_null($sNewValue)) + if (strlen($sOldValue) == 0) { - $sNewValueHtml = Dict::S('UI:UndefinedObject'); + $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); + } + else + { + if (is_null($sNewValue)) + { + $sNewValueHtml = Dict::S('UI:UndefinedObject'); + } + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, + $sNewValueHtml, $sOldValueHtml); } - $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); } } } @@ -1041,26 +1181,28 @@ abstract class AttributeDefinition { $sNewValueHtml = Dict::S('UI:UndefinedObject'); } - $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, + $sOldValueHtml); } } + return $sResult; } - /** - * Parses a string to find some smart search patterns and build the corresponding search/OQL condition - * Each derived class is reponsible for defining and processing their own smart patterns, the base class - * does nothing special, and just calls the default (loose) operator - * - * @param string $sSearchText The search string to analyze for smart patterns - * @param \FieldExpression $oField - * @param array $aParams Values of the query parameters - * - * @return \Expression The search condition to be added (AND) to the current search - * - * @throws \CoreException - */ + /** + * Parses a string to find some smart search patterns and build the corresponding search/OQL condition + * Each derived class is reponsible for defining and processing their own smart patterns, the base class + * does nothing special, and just calls the default (loose) operator + * + * @param string $sSearchText The search string to analyze for smart patterns + * @param \FieldExpression $oField + * @param array $aParams Values of the query parameters + * + * @return \Expression The search condition to be added (AND) to the current search + * + * @throws \CoreException + */ public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams) { $sParamName = $oField->GetParent().'_'.$oField->GetName(); @@ -1069,32 +1211,36 @@ abstract class AttributeDefinition switch ($sOperator) { case 'Contains': - $aParams[$sParamName] = "%$sSearchText%"; - $sSQLOperator = 'LIKE'; - break; - + $aParams[$sParamName] = "%$sSearchText%"; + $sSQLOperator = 'LIKE'; + break; + default: - $sSQLOperator = $sOperator; - $aParams[$sParamName] = $sSearchText; + $sSQLOperator = $sOperator; + $aParams[$sParamName] = $sSearchText; } $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); + return $oNewCondition; } - + /** * Tells if an attribute is part of the unique fingerprint of the object (used for comparing two objects) * All attributes which value is not based on a value from the object itself (like ExternalFields or LinkedSet) * must be excluded from the object's signature + * * @return boolean */ public function IsPartOfFingerprint() { return true; } - + /** * The part of the current attribute in the object's signature, for the supplied value + * * @param mixed $value The value of this attribute for the object + * * @return string The "signature" for this field/attribute */ public function Fingerprint($value) @@ -1104,36 +1250,58 @@ abstract class AttributeDefinition } /** - * Set of objects directly linked to an object, and being part of its definition + * Set of objects directly linked to an object, and being part of its definition * - * @package iTopORM + * @package iTopORM */ class AttributeLinkedSet extends AttributeDefinition { static public function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); + return array_merge(parent::ListExpectedParams(), + array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); } - public function GetEditClass() {return "LinkedSet";} + public function GetEditClass() + { + return "LinkedSet"; + } - public function IsWritable() {return true;} - static public function IsLinkSet() {return true;} - public function IsIndirect() {return false;} + public function IsWritable() + { + return true; + } - public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} + static public function IsLinkSet() + { + return true; + } - /** - * @param \DBObject|null $oHostObject - * - * @return \ormLinkSet - * - * @throws \Exception - * @throws \CoreException - * @throws \CoreWarning - */ - public function GetDefaultValue(DBObject $oHostObject = null) + public function IsIndirect() + { + return false; + } + + public function GetValuesDef() + { + return $this->Get("allowed_values"); + } + + public function GetPrerequisiteAttributes($sClass = null) + { + return $this->Get("depends_on"); + } + + /** + * @param \DBObject|null $oHostObject + * + * @return \ormLinkSet + * + * @throws \Exception + * @throws \CoreException + * @throws \CoreWarning + */ + public function GetDefaultValue(DBObject $oHostObject = null) { $sLinkClass = $this->GetLinkedClass(); $sExtKeyToMe = $this->GetExtKeyToMe(); @@ -1169,6 +1337,7 @@ class AttributeLinkedSet extends AttributeDefinition } $oLinks = new DBObjectSet($oLinkSearch); $oLinkSet = new ormLinkSet($this->GetHostClass(), $this->GetCode(), $oLinks); + return $oLinkSet; } @@ -1181,24 +1350,42 @@ class AttributeLinkedSet extends AttributeDefinition { return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS); } - - public function GetLinkedClass() {return $this->Get('linked_class');} - public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} - public function GetBasicFilterOperators() {return array();} - public function GetBasicFilterLooseOperator() {return '';} - public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} + public function GetLinkedClass() + { + return $this->Get('linked_class'); + } - /** - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string|null - * - * @throws \CoreException - */ - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + public function GetExtKeyToMe() + { + return $this->Get('ext_key_to_me'); + } + + public function GetBasicFilterOperators() + { + return array(); + } + + public function GetBasicFilterLooseOperator() + { + return ''; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return ''; + } + + /** + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + */ + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) { if (is_object($sValue) && ($sValue instanceof ormLinkSet)) { @@ -1210,8 +1397,14 @@ class AttributeLinkedSet extends AttributeDefinition $aAttributes = array(); foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef) { - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; + if ($sAttCode == $this->GetExtKeyToMe()) + { + continue; + } + if ($oAttDef->IsExternalField()) + { + continue; + } $sAttValue = $oObj->GetAsHTML($sAttCode); if (strlen($sAttValue) > 0) { @@ -1221,21 +1414,23 @@ class AttributeLinkedSet extends AttributeDefinition $sAttributes = implode(', ', $aAttributes); $aItems[] = $sAttributes; } + return implode('
    ', $aItems); } + return null; } - /** - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - * - * @throws \CoreException - */ - public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) + /** + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * + * @throws \CoreException + */ + public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) { if (is_object($sValue) && ($sValue instanceof ormLinkSet)) { @@ -1256,16 +1451,31 @@ class AttributeLinkedSet extends AttributeDefinition continue; } } - if ($sAttCode == $this->GetExtKeyToMe()) continue; + if ($sAttCode == $this->GetExtKeyToMe()) + { + continue; + } if ($oAttDef->IsExternalField()) { /** @var \AttributeExternalField $oAttDef */ - if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe()) continue; + if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe()) + { + continue; + } /** @var AttributeExternalField $oAttDef */ - if ($oAttDef->IsFriendlyName()) continue; + if ($oAttDef->IsFriendlyName()) + { + continue; + } + } + if ($oAttDef instanceof AttributeFriendlyName) + { + continue; + } + if (!$oAttDef->IsScalar()) + { + continue; } - if ($oAttDef instanceof AttributeFriendlyName) continue; - if (!$oAttDef->IsScalar()) continue; $sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize); $sRes .= "<$sAttCode>$sAttValue\n"; } @@ -1277,6 +1487,7 @@ class AttributeLinkedSet extends AttributeDefinition { $sRes = ''; } + return $sRes; } @@ -1291,8 +1502,10 @@ class AttributeLinkedSet extends AttributeDefinition * @return mixed|string * @throws \CoreException */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); @@ -1317,14 +1530,27 @@ class AttributeLinkedSet extends AttributeDefinition continue; } } - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; - if (!$oAttDef->IsBasedOnDBColumns()) continue; - if (!$oAttDef->IsScalar()) continue; + if ($sAttCode == $this->GetExtKeyToMe()) + { + continue; + } + if ($oAttDef->IsExternalField()) + { + continue; + } + if (!$oAttDef->IsBasedOnDBColumns()) + { + continue; + } + if (!$oAttDef->IsScalar()) + { + continue; + } $sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize); if (strlen($sAttValue) > 0) { - $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, $sAttCode.$sSepValue.$sAttValue); + $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, + $sAttCode.$sSepValue.$sAttValue); $aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier; } } @@ -1339,12 +1565,13 @@ class AttributeLinkedSet extends AttributeDefinition } $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); $sRes = $sTextQualifier.$sRes.$sTextQualifier; + return $sRes; } /** * List the available verbs for 'GetForTemplate' - */ + */ public function EnumTemplateVerbs() { return array( @@ -1386,25 +1613,29 @@ class AttributeLinkedSet extends AttributeDefinition $iCount++; } - switch($sVerb) + switch ($sVerb) { case '': - return implode("\n", $aNames); - + return implode("\n", $aNames); + case 'html': - return '
    • '.implode("
    • ", $aNames).'
    '; - + return '
    • '.implode("
    • ", $aNames).'
    '; + default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); } } - public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never + public function DuplicatesAllowed() + { + return false; + } // No duplicates for 1:n links, never public function GetImportColumns() { $aColumns = array(); $aColumns[$this->GetCode()] = 'TEXT'; + return $aColumns; } @@ -1425,8 +1656,10 @@ class AttributeLinkedSet extends AttributeDefinition * @throws \MySQLHasGoneAwayException * @throws \Exception */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { + public function MakeValueFromString( + $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, + $sAttributeQualifier = null + ) { if (is_null($sSepItem) || empty($sSepItem)) { $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); @@ -1476,24 +1709,28 @@ class AttributeLinkedSet extends AttributeDefinition $aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue; if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode)) { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sKeyAttCode)); + throw new CoreException('Wrong attribute code for link attribute specification', + array('class' => $sTargetClass, 'attcode' => $sKeyAttCode)); } /** @var \AttributeExternalKey $oKeyAttDef */ $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); $sRemoteClass = $oKeyAttDef->GetTargetClass(); if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode)) { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode)); + throw new CoreException('Wrong attribute code for link attribute specification', + array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode)); } } else { - if(!MetaModel::IsValidAttCode($sTargetClass, $sAttCode)) + if (!MetaModel::IsValidAttCode($sTargetClass, $sAttCode)) { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode)); + throw new CoreException('Wrong attribute code for link attribute specification', + array('class' => $sTargetClass, 'attcode' => $sAttCode)); } $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode); - $aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + $aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, + $sSepAttribute, $sSepValue, $sAttributeQualifier); } } @@ -1503,12 +1740,13 @@ class AttributeLinkedSet extends AttributeDefinition $sLinkClass = $aValues['finalclass']; if (!is_subclass_of($sLinkClass, $sTargetClass)) { - throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); + throw new CoreException('Wrong class for link attribute specification', + array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); } } elseif (MetaModel::IsAbstract($sTargetClass)) { - throw new CoreException('Missing finalclass for link attribute specification'); + throw new CoreException('Missing finalclass for link attribute specification'); } else { @@ -1516,13 +1754,13 @@ class AttributeLinkedSet extends AttributeDefinition } $oLink = MetaModel::NewObject($sLinkClass); - foreach ($aValues as $sAttCode => $sValue) + foreach($aValues as $sAttCode => $sValue) { $oLink->Set($sAttCode, $sValue); } // 3rd - Set external keys from search conditions - foreach ($aExtKeys as $sKeyAttCode => $aReconciliation) + foreach($aExtKeys as $sKeyAttCode => $aReconciliation) { $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); $sKeyClass = $oKeyAttDef->GetTargetClass(); @@ -1534,19 +1772,21 @@ class AttributeLinkedSet extends AttributeDefinition $aReconciliationDesc[] = "$sRemoteAttCode=$sValue"; } $oExtKeySet = new DBObjectSet($oExtKeyFilter); - switch($oExtKeySet->Count()) + switch ($oExtKeySet->Count()) { - case 0: - $sReconciliationDesc = implode(', ', $aReconciliationDesc); - throw new CoreException("Found no match", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); - break; - case 1: - $oRemoteObj = $oExtKeySet->Fetch(); - $oLink->Set($sKeyAttCode, $oRemoteObj->GetKey()); - break; - default: - $sReconciliationDesc = implode(', ', $aReconciliationDesc); - throw new CoreException("Found several matches", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); + case 0: + $sReconciliationDesc = implode(', ', $aReconciliationDesc); + throw new CoreException("Found no match", + array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); + break; + case 1: + $oRemoteObj = $oExtKeySet->Fetch(); + $oLink->Set($sKeyAttCode, $oRemoteObj->GetKey()); + break; + default: + $sReconciliationDesc = implode(', ', $aReconciliationDesc); + throw new CoreException("Found several matches", + array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); // Found several matches, ambiguous } } @@ -1558,12 +1798,13 @@ class AttributeLinkedSet extends AttributeDefinition if ($oAttDef->IsExternalKey()) { /** @var \AttributeExternalKey $oAttDef */ - if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) + if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), + $oAttDef->GetTargetClass()))) { continue; // Don't check the key to self } } - + if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) { $aErrors[] = $sAttCode; @@ -1577,6 +1818,7 @@ class AttributeLinkedSet extends AttributeDefinition $aLinks[] = $oLink; } $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); + return $oSet; } @@ -1610,16 +1852,29 @@ class AttributeLinkedSet extends AttributeDefinition continue; } } - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; - if (!$oAttDef->IsBasedOnDBColumns()) continue; - if (!$oAttDef->IsScalar()) continue; + if ($sAttCode == $this->GetExtKeyToMe()) + { + continue; + } + if ($oAttDef->IsExternalField()) + { + continue; + } + if (!$oAttDef->IsBasedOnDBColumns()) + { + continue; + } + if (!$oAttDef->IsScalar()) + { + continue; + } $attValue = $oObj->Get($sAttCode); $aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue); } $aRet[] = $aAttributes; } } + return $aRet; } @@ -1646,12 +1901,13 @@ class AttributeLinkedSet extends AttributeDefinition $sLinkClass = $aValues['finalclass']; if (!is_subclass_of($sLinkClass, $sTargetClass)) { - throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); + throw new CoreException('Wrong class for link attribute specification', + array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); } } elseif (MetaModel::IsAbstract($sTargetClass)) { - throw new CoreException('Missing finalclass for link attribute specification'); + throw new CoreException('Missing finalclass for link attribute specification'); } else { @@ -1659,7 +1915,7 @@ class AttributeLinkedSet extends AttributeDefinition } $oLink = MetaModel::NewObject($sLinkClass); - foreach ($aValues as $sAttCode => $sValue) + foreach($aValues as $sAttCode => $sValue) { $oLink->Set($sAttCode, $sValue); } @@ -1671,12 +1927,13 @@ class AttributeLinkedSet extends AttributeDefinition if ($oAttDef->IsExternalKey()) { /** @var AttributeExternalKey $oAttDef */ - if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) + if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), + $oAttDef->GetTargetClass()))) { continue; // Don't check the key to self } } - + if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) { $aErrors[] = $sAttCode; @@ -1690,6 +1947,7 @@ class AttributeLinkedSet extends AttributeDefinition $aLinks[] = $oLink; } $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); + return $oSet; } @@ -1700,26 +1958,28 @@ class AttributeLinkedSet extends AttributeDefinition * @return mixed * @throws \Exception */ - public function MakeRealValue($proposedValue, $oHostObj){ - if($proposedValue === null) - { - $sLinkedClass = $this->GetLinkedClass(); - $aLinkedObjectsArray = array(); - $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); + public function MakeRealValue($proposedValue, $oHostObj) + { + if ($proposedValue === null) + { + $sLinkedClass = $this->GetLinkedClass(); + $aLinkedObjectsArray = array(); + $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); - return new ormLinkSet( - get_class($oHostObj), - $this->GetCode(), - $oSet - ); - } + return new ormLinkSet( + get_class($oHostObj), + $this->GetCode(), + $oSet + ); + } - return $proposedValue; - } + return $proposedValue; + } /** * @param ormLinkSet $val1 * @param ormLinkSet $val2 + * * @return bool */ public function Equals($val1, $val2) @@ -1730,8 +1990,9 @@ class AttributeLinkedSet extends AttributeDefinition } else { - $bAreEquivalent = ($val2->HasDelta() === false); + $bAreEquivalent = ($val2->HasDelta() === false); } + return $bAreEquivalent; } @@ -1744,6 +2005,7 @@ class AttributeLinkedSet extends AttributeDefinition public function GetMirrorLinkAttribute() { $oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe()); + return $oRemoteAtt; } @@ -1768,7 +2030,7 @@ class AttributeLinkedSet extends AttributeDefinition $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } - + // Setting target class if (!$this->IsIndirect()) { @@ -1796,25 +2058,28 @@ class AttributeLinkedSet extends AttributeDefinition } // - Adding attribute labels $aAttributesToDisplay = array(); - foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) + foreach($aAttCodesToDisplay as $sAttCodeToDisplay) { $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay); $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel(); } $oFormField->SetAttributesToDisplay($aAttributesToDisplay); - + parent::MakeFormField($oObject, $oFormField); - + return $oFormField; } - public function IsPartOfFingerprint() { return false; } + public function IsPartOfFingerprint() + { + return false; + } } /** - * Set of objects linked to an object (n-n), and being part of its definition + * Set of objects linked to an object (n-n), and being part of its definition * - * @package iTopORM + * @package iTopORM */ class AttributeLinkedSetIndirect extends AttributeLinkedSet { @@ -1828,17 +2093,30 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet return true; } - public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); } - public function GetEditClass() {return "LinkedSet";} - public function DuplicatesAllowed() {return $this->GetOptional("duplicates", false);} // The same object may be linked several times... or not... + public function GetExtKeyToRemote() + { + return $this->Get('ext_key_to_remote'); + } + + public function GetEditClass() + { + return "LinkedSet"; + } + + public function DuplicatesAllowed() + { + return $this->GetOptional("duplicates", false); + } // The same object may be linked several times... or not... public function GetTrackingLevel() { - return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default')); + return $this->GetOptional('tracking_level', + MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default')); } /** * Find the corresponding "link" attribute on the target class, if any + * * @return null | AttributeDefinition * @throws \CoreException */ @@ -1848,26 +2126,39 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet /** @var \AttributeExternalKey $oExtKeyToRemote */ $oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) + foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { - if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) continue; - if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) continue; - if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) continue; - if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) continue; + if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) + { + continue; + } + if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) + { + continue; + } + if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) + { + continue; + } + if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) + { + continue; + } $oRet = $oRemoteAttDef; break; } + return $oRet; } } /** - * Abstract class implementing default filters for a DB column + * Abstract class implementing default filters for a DB column * - * @package iTopORM + * @package iTopORM */ class AttributeDBFieldVoid extends AttributeDefinition -{ +{ static public function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); @@ -1880,6 +2171,7 @@ class AttributeDBFieldVoid extends AttributeDefinition .CMDBSource::GetSqlStringColumnDefinition() .($bFullSpec ? $this->GetSQLColSpec() : ''); } + protected function GetSQLColSpec() { $default = $this->ScalarToSQL($this->GetDefaultValue()); @@ -1900,45 +2192,82 @@ class AttributeDBFieldVoid extends AttributeDefinition $sRet = " DEFAULT ".CMDBSource::Quote($default); } } + return $sRet; } - public function GetEditClass() {return "String";} - - public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} + public function GetEditClass() + { + return "String"; + } + + public function GetValuesDef() + { + return $this->Get("allowed_values"); + } + + public function GetPrerequisiteAttributes($sClass = null) + { + return $this->Get("depends_on"); + } + + static public function IsBasedOnDBColumns() + { + return true; + } + + static public function IsScalar() + { + return true; + } + + public function IsWritable() + { + return !$this->IsMagic(); + } - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return !$this->IsMagic();} public function GetSQLExpr() { return $this->Get("sql"); } - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} - public function IsNullAllowed() {return false;} + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->MakeRealValue("", $oHostObject); + } + + public function IsNullAllowed() + { + return false; + } // - protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) + protected function ScalarToSQL($value) + { + return $value; + } // format value as a valuable SQL literal (quoted outside) public function GetSQLExpressions($sPrefix = '') { $aColumns = array(); // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix $aColumns[''] = $this->Get("sql"); + return $aColumns; } public function FromSQLToValue($aCols, $sPrefix = '') { $value = $this->MakeRealValue($aCols[$sPrefix.''], null); + return $value; } + public function GetSQLValues($value) { $aValues = array(); $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); + return $aValues; } @@ -1946,6 +2275,7 @@ class AttributeDBFieldVoid extends AttributeDefinition { $aColumns = array(); $aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec); + return $aColumns; } @@ -1956,8 +2286,9 @@ class AttributeDBFieldVoid extends AttributeDefinition public function GetBasicFilterOperators() { - return array("="=>"equals", "!="=>"differs from"); + return array("=" => "equals", "!=" => "differs from"); } + public function GetBasicFilterLooseOperator() { return "="; @@ -1968,35 +2299,43 @@ class AttributeDBFieldVoid extends AttributeDefinition $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '=': - default: - return $this->GetSQLExpr()." = $sQValue"; + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '=': + default: + return $this->GetSQLExpr()." = $sQValue"; } - } + } } /** - * Base class for all kind of DB attributes, with the exception of external keys + * Base class for all kind of DB attributes, with the exception of external keys * - * @package iTopORM + * @package iTopORM */ class AttributeDBField extends AttributeDBFieldVoid -{ +{ static public function ListExpectedParams() { return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); } - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue($this->Get("default_value"), $oHostObject);} - public function IsNullAllowed() {return $this->Get("is_null_allowed");} + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->MakeRealValue($this->Get("default_value"), $oHostObject); + } + + public function IsNullAllowed() + { + return $this->Get("is_null_allowed"); + } } /** - * Map an integer column to an attribute + * Map an integer column to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeInteger extends AttributeDBField { @@ -2008,9 +2347,16 @@ class AttributeInteger extends AttributeDBField //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');} - + public function GetEditClass() + { + return "String"; + } + + protected function GetSQLCol($bFullSpec = false) + { + return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : ''); + } + public function GetValidationPattern() { return "^[0-9]+$"; @@ -2019,15 +2365,16 @@ class AttributeInteger extends AttributeDBField public function GetBasicFilterOperators() { return array( - "!="=>"differs from", - "="=>"equals", - ">"=>"greater (strict) than", - ">="=>"greater than", - "<"=>"less (strict) than", - "<="=>"less than", - "in"=>"in" + "!=" => "differs from", + "=" => "equals", + ">" => "greater (strict) than", + ">=" => "greater than", + "<" => "less (strict) than", + "<=" => "less than", + "in" => "in" ); } + public function GetBasicFilterLooseOperator() { // Unless we implement an "equals approximately..." or "same order of magnitude" @@ -2039,59 +2386,72 @@ class AttributeInteger extends AttributeDBField $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '>': - return $this->GetSQLExpr()." > $sQValue"; - break; - case '>=': - return $this->GetSQLExpr()." >= $sQValue"; - break; - case '<': - return $this->GetSQLExpr()." < $sQValue"; - break; - case '<=': - return $this->GetSQLExpr()." <= $sQValue"; - break; - case 'in': - if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; - break; + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '>': + return $this->GetSQLExpr()." > $sQValue"; + break; + case '>=': + return $this->GetSQLExpr()." >= $sQValue"; + break; + case '<': + return $this->GetSQLExpr()." < $sQValue"; + break; + case '<=': + return $this->GetSQLExpr()." <= $sQValue"; + break; + case 'in': + if (!is_array($value)) + { + throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + } - case '=': - default: - return $this->GetSQLExpr()." = \"$value\""; + return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; + break; + + case '=': + default: + return $this->GetSQLExpr()." = \"$value\""; } - } + } public function GetNullValue() { return null; - } + } + public function IsNull($proposedValue) { return is_null($proposedValue); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; // 0 is transformed into '' ! + if (is_null($proposedValue)) + { + return null; + } + if ($proposedValue === '') + { + return null; + } // 0 is transformed into '' ! + return (int)$proposedValue; } public function ScalarToSQL($value) { assert(is_numeric($value) || is_null($value)); + return $value; // supposed to be an int } } /** - * An external key for which the class is defined as the value of another attribute + * An external key for which the class is defined as the value of another attribute * - * @package iTopORM + * @package iTopORM */ class AttributeObjectKey extends AttributeDBFieldVoid { @@ -2102,10 +2462,21 @@ class AttributeObjectKey extends AttributeDBFieldVoid return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed')); } - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} + public function GetEditClass() + { + return "String"; + } + + protected function GetSQLCol($bFullSpec = false) + { + return "INT(11)".($bFullSpec ? " DEFAULT 0" : ""); + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return 0; + } - public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} public function IsNullAllowed() { return $this->Get("is_null_allowed"); @@ -2116,6 +2487,7 @@ class AttributeObjectKey extends AttributeDBFieldVoid { return parent::GetBasicFilterOperators(); } + public function GetBasicFilterLooseOperator() { return parent::GetBasicFilterLooseOperator(); @@ -2124,34 +2496,42 @@ class AttributeObjectKey extends AttributeDBFieldVoid public function GetBasicFilterSQLExpr($sOpCode, $value) { return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } + } public function GetNullValue() { return 0; - } + } public function IsNull($proposedValue) { return ($proposedValue == 0); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return 0; - if ($proposedValue === '') return 0; - if (MetaModel::IsValidObject($proposedValue)) { + if (is_null($proposedValue)) + { + return 0; + } + if ($proposedValue === '') + { + return 0; + } + if (MetaModel::IsValidObject($proposedValue)) + { /** @var \DBObject $proposedValue */ return $proposedValue->GetKey(); } + return (int)$proposedValue; } } /** - * Display an integer between 0 and 100 as a percentage / horizontal bar graph + * Display an integer between 0 and 100 as a percentage / horizontal bar graph * - * @package iTopORM + * @package iTopORM */ class AttributePercentage extends AttributeInteger { @@ -2165,23 +2545,30 @@ class AttributePercentage extends AttributeInteger { $iValue = 100; } - else if ($iValue < 0) + else { - $iValue = 0; + if ($iValue < 0) + { + $iValue = 0; + } } if ($iValue > 90) { $sColor = "#cc3300"; } - else if ($iValue > 50) - { - $sColor = "#cccc00"; - } else { - $sColor = "#33cc00"; + if ($iValue > 50) + { + $sColor = "#cccc00"; + } + else + { + $sColor = "#33cc00"; + } } $iPercentWidth = ($iWidth * $iValue) / 100; + return "
     
     $sValue %"; } } @@ -2192,7 +2579,7 @@ class AttributePercentage extends AttributeInteger * a calculation on them, it is recommended to use the BC Math functions in order to * retain the precision * - * @package iTopORM + * @package iTopORM */ class AttributeDecimal extends AttributeDBField { @@ -2203,32 +2590,38 @@ class AttributeDecimal extends AttributeDBField return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */)); } - public function GetEditClass() {return "String";} + public function GetEditClass() + { + return "String"; + } + protected function GetSQLCol($bFullSpec = false) { return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : ''); } - + public function GetValidationPattern() { $iNbDigits = $this->Get('digits'); $iPrecision = $this->Get('decimals'); $iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below + return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$"; } public function GetBasicFilterOperators() { return array( - "!="=>"differs from", - "="=>"equals", - ">"=>"greater (strict) than", - ">="=>"greater than", - "<"=>"less (strict) than", - "<="=>"less than", - "in"=>"in" + "!=" => "differs from", + "=" => "equals", + ">" => "greater (strict) than", + ">=" => "greater than", + "<" => "less (strict) than", + "<=" => "less than", + "in" => "in" ); } + public function GetBasicFilterLooseOperator() { // Unless we implement an "equals approximately..." or "same order of magnitude" @@ -2240,59 +2633,72 @@ class AttributeDecimal extends AttributeDBField $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '>': - return $this->GetSQLExpr()." > $sQValue"; - break; - case '>=': - return $this->GetSQLExpr()." >= $sQValue"; - break; - case '<': - return $this->GetSQLExpr()." < $sQValue"; - break; - case '<=': - return $this->GetSQLExpr()." <= $sQValue"; - break; - case 'in': - if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; - break; + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '>': + return $this->GetSQLExpr()." > $sQValue"; + break; + case '>=': + return $this->GetSQLExpr()." >= $sQValue"; + break; + case '<': + return $this->GetSQLExpr()." < $sQValue"; + break; + case '<=': + return $this->GetSQLExpr()." <= $sQValue"; + break; + case 'in': + if (!is_array($value)) + { + throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + } - case '=': - default: - return $this->GetSQLExpr()." = \"$value\""; + return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; + break; + + case '=': + default: + return $this->GetSQLExpr()." = \"$value\""; } - } + } public function GetNullValue() { return null; - } + } + public function IsNull($proposedValue) { return is_null($proposedValue); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; + if (is_null($proposedValue)) + { + return null; + } + if ($proposedValue === '') + { + return null; + } + return (string)$proposedValue; } public function ScalarToSQL($value) { assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value)); + return $value; // null or string } } /** - * Map a boolean column to an attribute + * Map a boolean column to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeBoolean extends AttributeInteger { @@ -2304,20 +2710,41 @@ class AttributeBoolean extends AttributeInteger //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "Integer";} - protected function GetSQLCol($bFullSpec = false) {return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');} + public function GetEditClass() + { + return "Integer"; + } + + protected function GetSQLCol($bFullSpec = false) + { + return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : ''); + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; - if ((int)$proposedValue) return true; + if (is_null($proposedValue)) + { + return null; + } + if ($proposedValue === '') + { + return null; + } + if ((int)$proposedValue) + { + return true; + } + return false; } public function ScalarToSQL($value) { - if ($value) return 1; + if ($value) + { + return 1; + } + return 0; } @@ -2333,6 +2760,7 @@ class AttributeBoolean extends AttributeInteger $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue); $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, true /*user lang*/); } + return $sLabel; } @@ -2346,8 +2774,10 @@ class AttributeBoolean extends AttributeInteger { $sValue = $bValue ? 'yes' : 'no'; $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue.'+'); - $sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault, true /*user lang*/); + $sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault, + true /*user lang*/); } + return $sDescription; } @@ -2368,6 +2798,7 @@ class AttributeBoolean extends AttributeInteger { $sRes = $bValue ? 'yes' : 'no'; } + return $sRes; } @@ -2386,11 +2817,14 @@ class AttributeBoolean extends AttributeInteger $sFinalValue = $bValue ? 'yes' : 'no'; } $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); + return $sRes; } - public function GetAsCSV($bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { if (is_null($bValue)) { $sFinalValue = ''; @@ -2404,6 +2838,7 @@ class AttributeBoolean extends AttributeInteger $sFinalValue = $bValue ? 'yes' : 'no'; } $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); + return $sRes; } @@ -2458,8 +2893,10 @@ class AttributeBoolean extends AttributeInteger return (bool)$value; } - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { + public function MakeValueFromString( + $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, + $sAttributeQualifier = null + ) { $sInput = strtolower(trim($sProposedValue)); if ($bLocalizedValue) { @@ -2494,14 +2931,15 @@ class AttributeBoolean extends AttributeInteger $value = null; } } + return $value; } } /** - * Map a varchar column (size < ?) to an attribute + * Map a varchar column (size < ?) to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeString extends AttributeDBField { @@ -2513,7 +2951,10 @@ class AttributeString extends AttributeDBField //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "String";} + public function GetEditClass() + { + return "String"; + } protected function GetSQLCol($bFullSpec = false) { @@ -2545,6 +2986,7 @@ class AttributeString extends AttributeDBField else { $sRegExp = str_replace('/', '\\/', $sRegExp); + return preg_match("/$sRegExp/", $value); } } @@ -2557,15 +2999,16 @@ class AttributeString extends AttributeDBField public function GetBasicFilterOperators() { return array( - "="=>"equals", - "!="=>"differs from", - "Like"=>"equals (no case)", - "NotLike"=>"differs from (no case)", - "Contains"=>"contains", - "Begins with"=>"begins with", - "Finishes with"=>"finishes with" + "=" => "equals", + "!=" => "differs from", + "Like" => "equals (no case)", + "NotLike" => "differs from (no case)", + "Contains" => "contains", + "Begins with" => "begins with", + "Finishes with" => "finishes with" ); } + public function GetBasicFilterLooseOperator() { return "Contains"; @@ -2576,36 +3019,40 @@ class AttributeString extends AttributeDBField $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '=': - case '!=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'Begins with': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%"); - case 'Finishes with': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value"); - case 'Contains': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); - case 'NotLike': - return $this->GetSQLExpr()." NOT LIKE $sQValue"; - case 'Like': - default: - return $this->GetSQLExpr()." LIKE $sQValue"; + case '=': + case '!=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'Begins with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%"); + case 'Finishes with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value"); + case 'Contains': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); + case 'NotLike': + return $this->GetSQLExpr()." NOT LIKE $sQValue"; + case 'Like': + default: + return $this->GetSQLExpr()." LIKE $sQValue"; } - } + } public function GetNullValue() { return ''; - } + } public function IsNull($proposedValue) { return ($proposedValue == ''); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return ''; + if (is_null($proposedValue)) + { + return ''; + } + return (string)$proposedValue; } @@ -2613,16 +3060,25 @@ class AttributeString extends AttributeDBField { if (!is_string($value) && !is_null($value)) { - throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + throw new CoreWarning('Expected the attribute value to be a string', array( + 'found_type' => gettype($value), + 'value' => $value, + 'class' => $this->GetHostClass(), + 'attribute' => $this->GetCode() + )); } + return $value; } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; } @@ -2651,9 +3107,9 @@ class AttributeString extends AttributeDBField } /** - * An attibute that matches an object class + * An attribute that matches an object class * - * @package iTopORM + * @package iTopORM */ class AttributeClass extends AttributeString { @@ -2678,17 +3134,22 @@ class AttributeClass extends AttributeString { // For this kind of attribute specifying null as default value // is authorized even if null is not allowed - + // Pick the first one... $aClasses = $this->GetAllowedValues(); $sDefault = key($aClasses); } + return $sDefault; } public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) { - if (empty($sValue)) return ''; + if (empty($sValue)) + { + return ''; + } + return MetaModel::GetName($sValue); } @@ -2696,18 +3157,73 @@ class AttributeClass extends AttributeString { return true; } - + public function GetBasicFilterLooseOperator() { return '='; } - + +} + + +/** + * An attribute that matches a class state + * + * @package iTopORM + */ +class AttributeClassState extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM; + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array('class_field')); + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + if (isset($aArgs['this'])) + { + $oHostObj = $aArgs['this']; + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObj->Get($sTargetClass); + + $aAllowedStates = array(); + $aValues = MetaModel::EnumStates($sClass); + foreach(array_keys($aValues) as $sState) + { + $aAllowedStates[$sState] = MetaModel::GetStateLabel($sClass, $sState); + } + return $aAllowedStates; + } + + return null; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) + { + return ''; + } + + if (!empty($oHostObject)) + { + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObject->Get($sTargetClass); + + return MetaModel::GetStateLabel($sClass, $sValue); + } + + return $sValue; + } + } /** - * An attibute that matches one of the language codes availables in the dictionnary + * An attibute that matches one of the language codes availables in the dictionnary * - * @package iTopORM + * @package iTopORM */ class AttributeApplicationLanguage extends AttributeString { @@ -2735,7 +3251,7 @@ class AttributeApplicationLanguage extends AttributeString { return true; } - + public function GetBasicFilterLooseOperator() { return '='; @@ -2743,9 +3259,9 @@ class AttributeApplicationLanguage extends AttributeString } /** - * The attribute dedicated to the finalclass automatic attribute + * The attribute dedicated to the finalclass automatic attribute * - * @package iTopORM + * @package iTopORM */ class AttributeFinalClass extends AttributeString { @@ -2765,6 +3281,7 @@ class AttributeFinalClass extends AttributeString { return false; } + public function IsMagic() { return true; @@ -2779,6 +3296,7 @@ class AttributeFinalClass extends AttributeString { $this->m_sValue = $sValue; } + public function GetDefaultValue(DBObject $oHostObject = null) { return $this->m_sValue; @@ -2786,7 +3304,10 @@ class AttributeFinalClass extends AttributeString public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) { - if (empty($sValue)) return ''; + if (empty($sValue)) + { + return ''; + } if ($bLocalize) { return MetaModel::GetName($sValue); @@ -2811,8 +3332,10 @@ class AttributeFinalClass extends AttributeString * @throws \CoreException * @throws \OQLException */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { + public function MakeValueFromString( + $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, + $sAttributeQualifier = null + ) { if ($bLocalizedValue) { // Lookup for the value matching the input @@ -2821,7 +3344,7 @@ class AttributeFinalClass extends AttributeString $aRawValues = self::GetAllowedValues(); if (!is_null($aRawValues)) { - foreach ($aRawValues as $sKey => $sValue) + foreach($aRawValues as $sKey => $sValue) { if ($sProposedValue == $sValue) { @@ -2834,11 +3357,13 @@ class AttributeFinalClass extends AttributeString { return null; } + return $this->MakeRealValue($sFoundValue, null); } else { - return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, + $sAttributeQualifier); } } @@ -2846,7 +3371,11 @@ class AttributeFinalClass extends AttributeString // Because this is sometimes used to get a localized/string version of an attribute... public function GetEditValue($sValue, $oHostObj = null) { - if (empty($sValue)) return ''; + if (empty($sValue)) + { + return ''; + } + return MetaModel::GetName($sValue); } @@ -2877,9 +3406,9 @@ class AttributeFinalClass extends AttributeString * @throws \DictExceptionMissingString */ public function GetAsCSV( - $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false - ) - { + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { if ($bLocalize && $value != '') { $sRawValue = MetaModel::GetName($value); @@ -2888,12 +3417,16 @@ class AttributeFinalClass extends AttributeString { $sRawValue = $value; } + return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText); } public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - if (empty($value)) return ''; + if (empty($value)) + { + return ''; + } if ($bLocalize) { $sRawValue = MetaModel::GetName($value); @@ -2902,6 +3435,7 @@ class AttributeFinalClass extends AttributeString { $sRawValue = $value; } + return Str::pure2xml($sRawValue); } @@ -2909,10 +3443,14 @@ class AttributeFinalClass extends AttributeString { return '='; } - + public function GetValueLabel($sValue) { - if (empty($sValue)) return ''; + if (empty($sValue)) + { + return ''; + } + return MetaModel::GetName($sValue); } @@ -2920,19 +3458,20 @@ class AttributeFinalClass extends AttributeString { $aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL); $aLocalizedValues = array(); - foreach ($aRawValues as $sClass) + foreach($aRawValues as $sClass) { $aLocalizedValues[$sClass] = MetaModel::GetName($sClass); } - return $aLocalizedValues; - } + + return $aLocalizedValues; + } } /** - * Map a varchar column (size < ?) to an attribute that must never be shown to the user + * Map a varchar column (size < ?) to an attribute that must never be shown to the user * - * @package iTopORM + * @package iTopORM */ class AttributePassword extends AttributeString { @@ -2944,7 +3483,10 @@ class AttributePassword extends AttributeString //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "Password";} + public function GetEditClass() + { + return "Password"; + } protected function GetSQLCol($bFullSpec = false) { @@ -2960,7 +3502,7 @@ class AttributePassword extends AttributeString public function GetFilterDefinitions() { - // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists) + // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists) // not allowed to search on passwords! return array(); } @@ -2976,8 +3518,11 @@ class AttributePassword extends AttributeString return '******'; } } - - public function IsPartOfFingerprint() { return false; } // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt' + + public function IsPartOfFingerprint() + { + return false; + } // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt' } /** @@ -2986,7 +3531,7 @@ class AttributePassword extends AttributeString * database (in SQL) to someone else without providing the key at the same time * the encrypted fields will remain encrypted * - * @package iTopORM + * @package iTopORM */ class AttributeEncryptedString extends AttributeString { @@ -2994,6 +3539,7 @@ class AttributeEncryptedString extends AttributeString static $sKey = null; // Encryption key used for all encrypted fields static $sLibrary = null; // Encryption library used for all encrypted fields + public function __construct($sCode, $aParams) { parent::__construct($sCode, $aParams); @@ -3001,17 +3547,18 @@ class AttributeEncryptedString extends AttributeString { self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); } - if(self::$sLibrary == null) + if (self::$sLibrary == null) { self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); } } + /** * When the attribute definitions are stored in APC cache: * 1) The static class variable $sKey is NOT serialized * 2) The object's constructor is NOT called upon wakeup * 3) mcrypt may crash the server if passed an empty key !! - * + * * So let's restore the key (if needed) when waking up **/ public function __wakeup() @@ -3020,14 +3567,17 @@ class AttributeEncryptedString extends AttributeString { self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); } - if(self::$sLibrary == null) + if (self::$sLibrary == null) { self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); } } - - protected function GetSQLCol($bFullSpec = false) {return "TINYBLOB";} + + protected function GetSQLCol($bFullSpec = false) + { + return "TINYBLOB"; + } public function GetMaxSize() { @@ -3043,7 +3593,11 @@ class AttributeEncryptedString extends AttributeString public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return null; + if (is_null($proposedValue)) + { + return null; + } + return (string)$proposedValue; } @@ -3058,8 +3612,9 @@ class AttributeEncryptedString extends AttributeString */ public function FromSQLToValue($aCols, $sPrefix = '') { - $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); - $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); + $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); + $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); + return $sValue; } @@ -3073,11 +3628,12 @@ class AttributeEncryptedString extends AttributeString */ public function GetSQLValues($value) { - $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); - $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); + $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); + $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); $aValues = array(); $aValues[$this->Get("sql")] = $encryptedValue; + return $aValues; } } @@ -3091,13 +3647,16 @@ define('WIKI_OBJECT_REGEXP', '/\[\[(.+):(.+)\]\]/U'); /** - * Map a text column (size > ?) to an attribute + * Map a text column (size > ?) to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeText extends AttributeString { - public function GetEditClass() {return ($this->GetFormat() == 'text') ? 'Text' : "HTML";} + public function GetEditClass() + { + return ($this->GetFormat() == 'text') ? 'Text' : "HTML"; + } protected function GetSQLCol($bFullSpec = false) { @@ -3108,15 +3667,16 @@ class AttributeText extends AttributeString { $aColumns = array(); $aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec); - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Add the extra column only if the property 'format' is specified for the attribute $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::GetSqlStringColumnDefinition(); if ($bFullSpec) { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records + $aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'text'"; // default 'text' is for migrating old records } } + return $aColumns; } @@ -3129,14 +3689,15 @@ class AttributeText extends AttributeString $aColumns = array(); // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix $aColumns[''] = $sPrefix; - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Add the extra column only if the property 'format' is specified for the attribute $aColumns['_format'] = $sPrefix.'_format'; } + return $aColumns; } - + public function GetMaxSize() { // Is there a way to know the current limitation for mysql? @@ -3149,20 +3710,21 @@ class AttributeText extends AttributeString if (!$bWikiOnly) { $sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i'; - if (preg_match_all($sPattern, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */)) + if (preg_match_all($sPattern, $sText, $aAllMatches, + PREG_SET_ORDER /* important !*/ | PREG_OFFSET_CAPTURE /* important ! */)) { $i = count($aAllMatches); // Replace the URLs by an actual hyperlink ... // Let's do it backwards so that the initial positions are not modified by the replacement // This works if the matches are captured: in the order they occur in the string AND // with their offset (i.e. position) inside the string - while($i > 0) + while ($i > 0) { $i--; $sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern $iPos = $aAllMatches[$i][0][1]; // Position of the main pattern $sText = substr_replace($sText, "$sUrl", $iPos, strlen($sUrl)); - + } } } @@ -3172,7 +3734,7 @@ class AttributeText extends AttributeString { $sClass = trim($aMatches[1]); $sName = trim($aMatches[2]); - + if (MetaModel::IsValidClass($sClass)) { $oObj = MetaModel::GetObjectByName($sClass, $sName, false /* MustBeFound */); @@ -3185,7 +3747,8 @@ class AttributeText extends AttributeString { // Propose a std link to the object $sClassLabel = MetaModel::GetName($sClass); - $sText = str_replace($aMatches[0], "$sClassLabel:$sName", $sText); + $sText = str_replace($aMatches[0], + "$sClassLabel:$sName", $sText); // Later: propose a link to create a new object // Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name //$sText = preg_replace('/\[\[(.+):(.+)\]\]/', ''.$sName.'', $sText); @@ -3193,6 +3756,7 @@ class AttributeText extends AttributeString } } } + return $sText; } @@ -3213,19 +3777,21 @@ class AttributeText extends AttributeString $aStyles[] = 'overflow:auto'; $sStyle = 'style="'.implode(';', $aStyles).'"'; } - + if ($this->GetFormat() == 'text') { $sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize); $sValue = self::RenderWikiHtml($sValue); - return "
    ".str_replace("\n", "
    \n", $sValue).'
    '; + + return "
    ".str_replace("\n", "
    \n", $sValue).'
    '; } else { $sValue = self::RenderWikiHtml($sValue, true /* wiki only */); + return "
    ".InlineImage::FixUrls($sValue).'
    '; } - + } public function GetEditValue($sValue, $oHostObj = null) @@ -3251,6 +3817,7 @@ class AttributeText extends AttributeString { $sValue = str_replace('&', '&', $sValue); } + return $sValue; } @@ -3266,46 +3833,47 @@ class AttributeText extends AttributeString { if ($this->GetFormat() == 'html') { - return (string) utils::HtmlToText($this->GetEditValue($sValue, $oHostObj)); + return (string)utils::HtmlToText($this->GetEditValue($sValue, $oHostObj)); } else { return parent::GetAsPlainText($sValue, $oHostObj); } } - + public function MakeRealValue($proposedValue, $oHostObj) { $sValue = $proposedValue; switch ($this->GetFormat()) { case 'html': - if (($sValue !== null) && ($sValue !== '')) - { - $sValue = HTMLSanitizer::Sanitize($sValue); - } - break; - + if (($sValue !== null) && ($sValue !== '')) + { + $sValue = HTMLSanitizer::Sanitize($sValue); + } + break; + case 'text': default: - if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) + if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) { - $sClassLabel = $aMatches[1]; - $sName = $aMatches[2]; - - if (!MetaModel::IsValidClass($sClassLabel)) + foreach($aAllMatches as $iPos => $aMatches) { - $sClass = MetaModel::GetClassFromLabel($sClassLabel); - if ($sClass) + $sClassLabel = $aMatches[1]; + $sName = $aMatches[2]; + + if (!MetaModel::IsValidClass($sClassLabel)) { - $sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue); + $sClass = MetaModel::GetClassFromLabel($sClassLabel); + if ($sClass) + { + $sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue); + } } } } - } } + return $sValue; } @@ -3313,15 +3881,15 @@ class AttributeText extends AttributeString { return Str::pure2xml($value); } - + public function GetWidth() { - return $this->GetOptional('width', ''); + return $this->GetOptional('width', ''); } - + public function GetHeight() { - return $this->GetOptional('height', ''); + return $this->GetOptional('height', ''); } static public function GetFormFieldClass() @@ -3352,6 +3920,7 @@ class AttributeText extends AttributeString /** * The actual formatting of the field: either text (=plain text) or html (= text with HTML markup) + * * @return string */ public function GetFormat() @@ -3373,7 +3942,7 @@ class AttributeText extends AttributeString public function FromSQLToValue($aCols, $sPrefix = '') { $value = $aCols[$sPrefix.'']; - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Read from the extra column only if the property 'format' is specified for the attribute $sFormat = $aCols[$sPrefix.'_format']; @@ -3382,71 +3951,77 @@ class AttributeText extends AttributeString { $sFormat = $this->GetFormat(); } - - switch($sFormat) + + switch ($sFormat) { case 'text': - if ($this->GetFormat() == 'html') - { - $value = utils::TextToHtml($value); - } - break; - + if ($this->GetFormat() == 'html') + { + $value = utils::TextToHtml($value); + } + break; + case 'html': - if ($this->GetFormat() == 'text') - { - $value = utils::HtmlToText($value); - } - else - { - $value = InlineImage::FixUrls((string)$value); - } - break; - + if ($this->GetFormat() == 'text') + { + $value = utils::HtmlToText($value); + } + else + { + $value = InlineImage::FixUrls((string)$value); + } + break; + default: - // unknown format ?? + // unknown format ?? } + return $value; } - + public function GetSQLValues($value) { $aValues = array(); $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Add the extra column only if the property 'format' is specified for the attribute $aValues[$this->Get("sql").'_format'] = $this->GetFormat(); } + return $aValues; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - switch($this->GetFormat()) + } + + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { + switch ($this->GetFormat()) { case 'html': - if ($bConvertToPlainText) - { - $sValue = utils::HtmlToText((string)$sValue); - } - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - break; - + if ($bConvertToPlainText) + { + $sValue = utils::HtmlToText((string)$sValue); + } + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + + return $sTextQualifier.$sEscaped.$sTextQualifier; + break; + case 'text': default: - return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); + return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, + $bConvertToPlainText); } } } /** - * Map a log to an attribute + * Map a log to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeLongText extends AttributeText { @@ -3459,14 +4034,14 @@ class AttributeLongText extends AttributeText { // Is there a way to know the current limitation for mysql? // See mysql_field_len() - return 65535*1024; // Limited... still 64 Mb! + return 65535 * 1024; // Limited... still 64 Mb! } } /** - * An attibute that stores a case log (i.e journal) + * An attibute that stores a case log (i.e journal) * - * @package iTopORM + * @package iTopORM */ class AttributeCaseLog extends AttributeLongText { @@ -3475,7 +4050,7 @@ class AttributeCaseLog extends AttributeLongText public function GetNullValue() { return ''; - } + } public function IsNull($proposedValue) { @@ -3483,18 +4058,29 @@ class AttributeCaseLog extends AttributeLongText { return ($proposedValue == ''); } + return ($proposedValue->GetText() == ''); - } + } public function ScalarToSQL($value) { if (!is_string($value) && !is_null($value)) { - throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass())); + throw new CoreWarning('Expected the attribute value to be a string', array( + 'found_type' => gettype($value), + 'value' => $value, + 'class' => $this->GetCode(), + 'attribute' => $this->GetHostClass() + )); } + return $value; } - public function GetEditClass() {return "CaseLog";} + + public function GetEditClass() + { + return "CaseLog"; + } public function GetEditValue($sValue, $oHostObj = null) { @@ -3502,6 +4088,7 @@ class AttributeCaseLog extends AttributeLongText { return ''; } + return $sValue->GetModifiedEntry(); } @@ -3522,12 +4109,19 @@ class AttributeCaseLog extends AttributeLongText } else { - return (string) $value; + return (string)$value; } } - - public function GetDefaultValue(DBObject $oHostObject = null) {return new ormCaseLog();} - public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());} + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return new ormCaseLog(); + } + + public function Equals($val1, $val2) + { + return ($val1->GetText() == $val2->GetText()); + } /** @@ -3558,7 +4152,7 @@ class AttributeCaseLog extends AttributeLongText { $oPreviousLog = $oHostObj->GetOriginal($this->GetCode());; } - + } if (is_object($oPreviousLog)) { @@ -3582,6 +4176,7 @@ class AttributeCaseLog extends AttributeLongText } $ret = $oCaseLog; } + return $ret; } @@ -3595,6 +4190,7 @@ class AttributeCaseLog extends AttributeLongText // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix $aColumns[''] = $sPrefix; $aColumns['_index'] = $sPrefix.'_index'; + return $aColumns; } @@ -3611,10 +4207,10 @@ class AttributeCaseLog extends AttributeLongText { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } + } $sLog = $aCols[$sPrefix]; - if (isset($aCols[$sPrefix.'_index'])) + if (isset($aCols[$sPrefix.'_index'])) { $sIndex = $aCols[$sPrefix.'_index']; } @@ -3625,7 +4221,7 @@ class AttributeCaseLog extends AttributeLongText } if (strlen($sIndex) > 0) - { + { $aIndex = unserialize($sIndex); $value = new ormCaseLog($sLog, $aIndex); } @@ -3633,6 +4229,7 @@ class AttributeCaseLog extends AttributeLongText { $value = new ormCaseLog($sLog); } + return $value; } @@ -3655,6 +4252,7 @@ class AttributeCaseLog extends AttributeLongText $aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb) .CMDBSource::GetSqlStringColumnDefinition(); $aColumns[$this->GetCode().'_index'] = 'BLOB'; + return $aColumns; } @@ -3682,21 +4280,26 @@ class AttributeCaseLog extends AttributeLongText { $sStyle = 'style="'.implode(';', $aStyles).'"'; } - return "
    ".$sContent.'
    '; } + + return "
    ".$sContent.'
    '; + } - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { if ($value instanceOf ormCaseLog) { - return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); + return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, + $bLocalize, $bConvertToPlainText); } else { return ''; } } - + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { if ($value instanceOf ormCaseLog) @@ -3711,7 +4314,7 @@ class AttributeCaseLog extends AttributeLongText /** * List the available verbs for 'GetForTemplate' - */ + */ public function EnumTemplateVerbs() { return array( @@ -3735,29 +4338,29 @@ class AttributeCaseLog extends AttributeLongText */ public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) { - switch($sVerb) + switch ($sVerb) { case '': - return $value->GetText(true); - + return $value->GetText(true); + case 'head': - return $value->GetLatestEntry('text'); + return $value->GetLatestEntry('text'); case 'head_html': - return $value->GetLatestEntry('html'); - + return $value->GetLatestEntry('html'); + case 'html': - return $value->GetAsEmailHtml(); - + return $value->GetAsEmailHtml(); + default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); } } - + /** * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ + * The operation is the opposite to FromJSONToValue + */ public function GetForJSON($value) { return $value->GetForJSON(); @@ -3765,8 +4368,8 @@ class AttributeCaseLog extends AttributeLongText /** * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - */ + * The operation is the opposite to GetForJSON + */ public function FromJSONToValue($json) { if (is_string($json)) @@ -3790,9 +4393,10 @@ class AttributeCaseLog extends AttributeLongText $ret = ormCaseLog::FromJSON($json); } } + return $ret; } - + public function Fingerprint($value) { $sFingerprint = ''; @@ -3800,11 +4404,13 @@ class AttributeCaseLog extends AttributeLongText { $sFingerprint = $value->GetText(); } + return $sFingerprint; } - + /** * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * * @return string */ public function GetFormat() @@ -3831,9 +4437,9 @@ class AttributeCaseLog extends AttributeLongText } /** - * Map a text column (size > ?), containing HTML code, to an attribute + * Map a text column (size > ?), containing HTML code, to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeHTML extends AttributeLongText { @@ -3841,20 +4447,22 @@ class AttributeHTML extends AttributeLongText { $aColumns = array(); $aColumns[$this->Get('sql')] = $this->GetSQLCol(); - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Add the extra column only if the property 'format' is specified for the attribute $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; if ($bFullSpec) { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records + $aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'html'"; // default 'html' is for migrating old records } } + return $aColumns; } /** * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * * @return string */ public function GetFormat() @@ -3864,9 +4472,9 @@ class AttributeHTML extends AttributeLongText } /** - * Specialization of a string: email + * Specialization of a string: email * - * @package iTopORM + * @package iTopORM */ class AttributeEmailAddress extends AttributeString { @@ -3875,14 +4483,17 @@ class AttributeEmailAddress extends AttributeString return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('email_validation_pattern').'$'); } - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\EmailField'; - } + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\EmailField'; + } public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) { - if (empty($sValue)) return ''; + if (empty($sValue)) + { + return ''; + } $sUrlDecorationClass = utils::GetConfig()->Get('email_decoration_class'); @@ -3891,15 +4502,16 @@ class AttributeEmailAddress extends AttributeString } /** - * Specialization of a string: IP address + * Specialization of a string: IP address * - * @package iTopORM + * @package iTopORM */ class AttributeIPAddress extends AttributeString { public function GetValidationPattern() { $sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])'; + return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$"; } @@ -3913,48 +4525,55 @@ class AttributeIPAddress extends AttributeString /** * Specialization of a string: phone number * - * @package iTopORM + * @package iTopORM */ class AttributePhoneNumber extends AttributeString { - public function GetValidationPattern() - { - return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('phone_number_validation_pattern').'$'); - } + public function GetValidationPattern() + { + return $this->GetOptional('validation_pattern', + '^'.utils::GetConfig()->Get('phone_number_validation_pattern').'$'); + } - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\PhoneField'; - } + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\PhoneField'; + } - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (empty($sValue)) return ''; + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) + { + return ''; + } - $sUrlDecorationClass = utils::GetConfig()->Get('phone_number_decoration_class'); - $sUrlPattern = utils::GetConfig()->Get('phone_number_url_pattern'); - $sUrl = sprintf($sUrlPattern, $sValue); + $sUrlDecorationClass = utils::GetConfig()->Get('phone_number_decoration_class'); + $sUrlPattern = utils::GetConfig()->Get('phone_number_url_pattern'); + $sUrl = sprintf($sUrlPattern, $sValue); - return ''.parent::GetAsHTML($sValue).''; - } + return ''.parent::GetAsHTML($sValue).''; + } } /** - * Specialization of a string: OQL expression + * Specialization of a string: OQL expression * - * @package iTopORM + * @package iTopORM */ class AttributeOQL extends AttributeText { const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - public function GetEditClass() {return "OQLExpression";} + public function GetEditClass() + { + return "OQLExpression"; + } } /** - * Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$) + * Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$) * - * @package iTopORM + * @package iTopORM */ class AttributeTemplateString extends AttributeString { @@ -3964,7 +4583,7 @@ class AttributeTemplateString extends AttributeString /** * Specialization of a text: template (contains iTop placeholders like $current_contact_id$ or $this->name$) * - * @package iTopORM + * @package iTopORM */ class AttributeTemplateText extends AttributeText { @@ -3974,7 +4593,7 @@ class AttributeTemplateText extends AttributeText /** * Specialization of a HTML: template (contains iTop placeholders like $current_contact_id$ or $this->name$) * - * @package iTopORM + * @package iTopORM */ class AttributeTemplateHTML extends AttributeText { @@ -3984,20 +4603,22 @@ class AttributeTemplateHTML extends AttributeText { $aColumns = array(); $aColumns[$this->Get('sql')] = $this->GetSQLCol(); - if ($this->GetOptional('format', null) != null ) + if ($this->GetOptional('format', null) != null) { // Add the extra column only if the property 'format' is specified for the attribute $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; if ($bFullSpec) { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records + $aColumns[$this->Get('sql').'_format'] .= " DEFAULT 'html'"; // default 'html' is for migrating old records } } + return $aColumns; } /** * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * * @return string */ public function GetFormat() @@ -4008,9 +4629,9 @@ class AttributeTemplateHTML extends AttributeText /** - * Map a enum column to an attribute + * Map a enum column to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeEnum extends AttributeString { @@ -4022,7 +4643,11 @@ class AttributeEnum extends AttributeString //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "String";} + public function GetEditClass() + { + return "String"; + } + protected function GetSQLCol($bFullSpec = false) { $oValDef = $this->GetValuesDef(); @@ -4051,7 +4676,7 @@ class AttributeEnum extends AttributeString .($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax! } } - + protected function GetSQLColSpec() { $default = $this->ScalarToSQL($this->GetDefaultValue()); @@ -4065,6 +4690,7 @@ class AttributeEnum extends AttributeString // otherwise MySQL interprets the number as the zero-based index of the value in the list (i.e. the nth value in the list) $sRet = " DEFAULT ".CMDBSource::Quote($default); } + return $sRet; } @@ -4093,6 +4719,7 @@ class AttributeEnum extends AttributeString { return parent::GetBasicFilterOperators(); } + public function GetBasicFilterLooseOperator() { return '='; @@ -4101,14 +4728,15 @@ class AttributeEnum extends AttributeString public function GetBasicFilterSQLExpr($sOpCode, $value) { return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } + } public function GetValueLabel($sValue) { if (is_null($sValue)) { // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label - $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, Dict::S('Enum:Undefined')); + $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, + Dict::S('Enum:Undefined')); } else { @@ -4120,6 +4748,7 @@ class AttributeEnum extends AttributeString $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false); } } + return $sLabel; } @@ -4128,11 +4757,13 @@ class AttributeEnum extends AttributeString if (is_null($sValue)) { // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label - $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined')); + $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', + Dict::S('Enum:Undefined')); } else { - $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', '', true /* user language only */); + $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', + '', true /* user language only */); if (strlen($sDescription) == 0) { $sParentClass = MetaModel::GetParentClass($this->m_sHostClass); @@ -4146,6 +4777,7 @@ class AttributeEnum extends AttributeString } } } + return $sDescription; } @@ -4162,6 +4794,7 @@ class AttributeEnum extends AttributeString { $sRes = parent::GetAsHtml($sValue, $oHostObject, $bLocalize); } + return $sRes; } @@ -4180,11 +4813,14 @@ class AttributeEnum extends AttributeString $sFinalValue = $value; } $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); + return $sRes; } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { if (is_null($sValue)) { $sFinalValue = ''; @@ -4198,6 +4834,7 @@ class AttributeEnum extends AttributeString $sFinalValue = $sValue; } $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); + return $sRes; } @@ -4210,7 +4847,7 @@ class AttributeEnum extends AttributeString { if ($oFormField === null) { - // TODO : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } @@ -4235,8 +4872,8 @@ class AttributeEnum extends AttributeString /** * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ + * The operation is the opposite to FromJSONToValue + */ public function GetForJSON($value) { return $value; @@ -4245,25 +4882,31 @@ class AttributeEnum extends AttributeString public function GetAllowedValues($aArgs = array(), $sContains = '') { $aRawValues = parent::GetAllowedValues($aArgs, $sContains); - if (is_null($aRawValues)) return null; + if (is_null($aRawValues)) + { + return null; + } $aLocalizedValues = array(); - foreach ($aRawValues as $sKey => $sValue) + foreach($aRawValues as $sKey => $sValue) { $aLocalizedValues[$sKey] = $this->GetValueLabel($sKey); } - return $aLocalizedValues; - } - public function GetMaxSize() - { - return null; - } + return $aLocalizedValues; + } + + public function GetMaxSize() + { + return null; + } /** * An enum can be localized - */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { + */ + public function MakeValueFromString( + $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, + $sAttributeQualifier = null + ) { if ($bLocalizedValue) { // Lookup for the value matching the input @@ -4272,7 +4915,7 @@ class AttributeEnum extends AttributeString $aRawValues = parent::GetAllowedValues(); if (!is_null($aRawValues)) { - foreach ($aRawValues as $sKey => $sValue) + foreach($aRawValues as $sKey => $sValue) { $sRefValue = $this->GetValueLabel($sKey); if ($sProposedValue == $sRefValue) @@ -4286,30 +4929,38 @@ class AttributeEnum extends AttributeString { return null; } - return $this->MakeRealValue($sFoundValue, null); + + return $this->MakeRealValue($sFoundValue, null); } else { - return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, + $sAttributeQualifier); } } - /** - * Processes the input value to align it with the values supported - * by this type of attribute. In this case: turns empty strings into nulls - * @param mixed $proposedValue The value to be set for the attribute - * @return mixed The actual value that will be set - */ + /** + * Processes the input value to align it with the values supported + * by this type of attribute. In this case: turns empty strings into nulls + * + * @param mixed $proposedValue The value to be set for the attribute + * + * @return mixed The actual value that will be set + */ public function MakeRealValue($proposedValue, $oHostObj) { - if ($proposedValue == '') return null; + if ($proposedValue == '') + { + return null; + } + return parent::MakeRealValue($proposedValue, $oHostObj); } - + public function GetOrderByHint() { $aValues = $this->GetAllowedValues(); - + return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues)); } } @@ -4319,7 +4970,7 @@ class AttributeEnum extends AttributeString * It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to * have a superstatus available on the root classe(s) * - * @package iTopORM + * @package iTopORM */ class AttributeMetaEnum extends AttributeEnum { @@ -4358,6 +5009,7 @@ class AttributeMetaEnum extends AttributeEnum { $aRet = array($aMappingData['attcode']); } + return $aRet; } @@ -4366,20 +5018,28 @@ class AttributeMetaEnum extends AttributeEnum * * @param array $aArgs * @param string $sContains + * * @return array|null */ public function GetAllowedValues($aArgs = array(), $sContains = '') { $oValSetDef = $this->GetValuesDef(); - if (!$oValSetDef) return null; + if (!$oValSetDef) + { + return null; + } $aRawValues = $oValSetDef->GetValueList(); - if (is_null($aRawValues)) return null; + if (is_null($aRawValues)) + { + return null; + } $aLocalizedValues = array(); - foreach ($aRawValues as $sKey => $sValue) + foreach($aRawValues as $sKey => $sValue) { $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey)); } + return $aLocalizedValues; } @@ -4388,6 +5048,7 @@ class AttributeMetaEnum extends AttributeEnum * See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes * * @param $oObject + * * @return mixed * @throws Exception */ @@ -4412,9 +5073,11 @@ class AttributeMetaEnum extends AttributeEnum } else { - throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()).'::'.$this->GetCode()); + throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), + $this->GetCode()).'::'.$this->GetCode()); } } + return $sRet; } @@ -4441,10 +5104,11 @@ class AttributeMetaEnum extends AttributeEnum return $aMappingData; } } + /** - * Map a date+time column to an attribute + * Map a date+time column to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeDateTime extends AttributeDBField { @@ -4460,11 +5124,12 @@ class AttributeDateTime extends AttributeDBField { if (self::$oFormat == null) { - static::LoadFormatFromConfig(); + static::LoadFormatFromConfig(); } + return self::$oFormat; - } - + } + /** * Load the 3 settings: date format, time format and data_time format from the configuration */ @@ -4475,15 +5140,16 @@ class AttributeDateTime extends AttributeDBField $sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d'); $sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s'); $sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time'); - + $sFullFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat); - + self::SetFormat(new DateTimeFormat($sFullFormat)); - AttributeDate::SetFormat(new DateTimeFormat($sDateFormat)); + AttributeDate::SetFormat(new DateTimeFormat($sDateFormat)); } - + /** * Returns the format string used for the date & time stored in memory + * * @return string */ static public function GetInternalFormat() @@ -4493,26 +5159,29 @@ class AttributeDateTime extends AttributeDBField /** * Returns the format string used for the date & time written to MySQL + * * @return string */ static public function GetSQLFormat() { return 'Y-m-d H:i:s'; } - + static public function SetFormat(DateTimeFormat $oDateTimeFormat) { self::$oFormat = $oDateTimeFormat; } - + static public function GetSQLTimeFormat() { return 'H:i:s'; } - + /** * Parses a search string coming from user input + * * @param string $sSearchString + * * @return string */ public function ParseSearchString($sSearchString) @@ -4521,28 +5190,29 @@ class AttributeDateTime extends AttributeDBField { $oDateTime = $this->GetFormat()->Parse($sSearchString); $sSearchString = $oDateTime->format($this->GetInternalFormat()); - } - catch(Exception $e) + } catch (Exception $e) { $sFormatString = '!'.(string)AttributeDate::GetFormat(); // BEWARE: ! is needed to set non-parsed fields to zero !!! - $oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString); + $oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString); if ($oDateTime !== false) { $sSearchString = $oDateTime->format($this->GetInternalFormat()); } } + return $sSearchString; } - + static public function GetFormFieldClass() { return '\\Combodo\\iTop\\Form\\Field\\DateTimeField'; } - + /** * Override to specify Field class * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the + * $oFormField is passed, MakeFormField behave more like a Prepare. */ public function MakeFormField(DBObject $oObject, $oFormField = null) { @@ -4551,7 +5221,7 @@ class AttributeDateTime extends AttributeDBField $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } - $oFormField->SetPHPDateTimeFormat((string) $this->GetFormat()); + $oFormField->SetPHPDateTimeFormat((string)$this->GetFormat()); $oFormField->SetJSDateTimeFormat($this->GetFormat()->ToMomentJS()); $oFormField = parent::MakeFormField($oObject, $oFormField); @@ -4562,40 +5232,40 @@ class AttributeDateTime extends AttributeDBField return $oFormField; } - /** - * @inheritdoc - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Formatted representation', - 'raw' => 'Not formatted representation', - ); - } + /** + * @inheritdoc + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Formatted representation', + 'raw' => 'Not formatted representation', + ); + } - /** - * @inheritdoc - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - switch ($sVerb) - { - case '': - case 'text': - return static::GetFormat()->format($value); - break; - case 'html': - // Note: Not passing formatted value as the method will format it. - return $this->GetAsHTML($value); - break; - case 'raw': - return $value; - break; - default: - return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize); - break; - } - } + /** + * @inheritdoc + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + switch ($sVerb) + { + case '': + case 'text': + return static::GetFormat()->format($value); + break; + case 'html': + // Note: Not passing formatted value as the method will format it. + return $this->GetAsHTML($value); + break; + case 'raw': + return $value; + break; + default: + return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize); + break; + } + } static public function ListExpectedParams() { @@ -4603,25 +5273,33 @@ class AttributeDateTime extends AttributeDBField //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "DateTime";} + public function GetEditClass() + { + return "DateTime"; + } public function GetEditValue($sValue, $oHostObj = null) { return (string)static::GetFormat()->format($sValue); - } + } + public function GetValueLabel($sValue, $oHostObj = null) { return (string)static::GetFormat()->format($sValue); - } - - protected function GetSQLCol($bFullSpec = false) {return "DATETIME";} + } + + protected function GetSQLCol($bFullSpec = false) + { + return "DATETIME"; + } public function GetImportColumns() { // Allow an empty string to be a valid value (synonym for "reset") $aColumns = array(); $aColumns[$this->GetCode()] = 'VARCHAR(19)'; + return $aColumns; } @@ -4629,6 +5307,7 @@ class AttributeDateTime extends AttributeDBField { $oDeadlineDateTime = new DateTime($value); $iUnixSeconds = $oDeadlineDateTime->format('U'); + return $iUnixSeconds; } @@ -4646,21 +5325,22 @@ class AttributeDateTime extends AttributeDBField public function GetBasicFilterOperators() { return array( - "="=>"equals", - "!="=>"differs from", - "<"=>"before", - "<="=>"before", - ">"=>"after (strictly)", - ">="=>"after", - "SameDay"=>"same day (strip time)", - "SameMonth"=>"same year/month", - "SameYear"=>"same year", - "Today"=>"today", - ">|"=>"after today + N days", - "<|"=>"before today + N days", - "=|"=>"equals today + N days", + "=" => "equals", + "!=" => "differs from", + "<" => "before", + "<=" => "before", + ">" => "after (strictly)", + ">=" => "after", + "SameDay" => "same day (strip time)", + "SameMonth" => "same year/month", + "SameYear" => "same year", + "Today" => "today", + ">|" => "after today + N days", + "<|" => "before today + N days", + "=|" => "equals today + N days", ); } + public function GetBasicFilterLooseOperator() { // Unless we implement a "same xxx, depending on given precision" ! @@ -4673,32 +5353,32 @@ class AttributeDateTime extends AttributeDBField switch ($sOpCode) { - case '=': - case '!=': - case '<': - case '<=': - case '>': - case '>=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'SameDay': - return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)"; - case 'SameMonth': - return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; - case 'SameYear': - return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; - case 'Today': - return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; - case '>|': - return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - case '<|': - return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - case '=|': - return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - default: - return $this->GetSQLExpr()." = $sQValue"; + case '=': + case '!=': + case '<': + case '<=': + case '>': + case '>=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'SameDay': + return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)"; + case 'SameMonth': + return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; + case 'SameYear': + return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; + case 'Today': + return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; + case '>|': + return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '<|': + return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '=|': + return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + default: + return $this->GetSQLExpr()." = $sQValue"; } } - + public function MakeRealValue($proposedValue, $oHostObj) { if (is_null($proposedValue)) @@ -4716,8 +5396,7 @@ class AttributeDateTime extends AttributeDBField { $oFormat = new DateTimeFormat($this->GetInternalFormat()); $oFormat->Parse($proposedValue); - } - catch (Exception $e) + } catch (Exception $e) { throw new Exception('Wrong format for date attribute '.$this->GetCode().', expecting "'.$this->GetInternalFormat().'" and got "'.$proposedValue.'"'); } @@ -4731,9 +5410,10 @@ class AttributeDateTime extends AttributeDBField public function ScalarToSQL($value) { if (empty($value)) - { + { return null; } + return $value; } @@ -4747,24 +5427,30 @@ class AttributeDateTime extends AttributeDBField return Str::pure2xml($value); } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00')) { return ''; } - else if ((string)static::GetFormat() !== static::GetInternalFormat()) + else { - // Format conversion - $oDate = new DateTime($sValue); - if ($oDate !== false) + if ((string)static::GetFormat() !== static::GetInternalFormat()) { - $sValue = static::GetFormat()->format($oDate); + // Format conversion + $oDate = new DateTime($sValue); + if ($oDate !== false) + { + $sValue = static::GetFormat()->format($oDate); + } } } $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; } @@ -4781,17 +5467,18 @@ class AttributeDateTime extends AttributeDBField * @return Expression The search condition to be added (AND) to the current search * @throws \CoreException */ - public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false) - { + public function GetSmartConditionExpression( + $sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false + ) { // Possible smart patterns $aPatterns = array( 'between' => array('pattern' => '/^\[(.*),(.*)\]$/', 'operator' => 'n/a'), 'greater than or equal' => array('pattern' => '/^>=(.*)$/', 'operator' => '>='), 'greater than' => array('pattern' => '/^>(.*)$/', 'operator' => '>'), 'less than or equal' => array('pattern' => '/^<=(.*)$/', 'operator' => '<='), - 'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'), + 'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'), ); - + $sPatternFound = ''; $aMatches = array(); foreach($aPatterns as $sPatName => $sPattern) @@ -4800,61 +5487,61 @@ class AttributeDateTime extends AttributeDBField { $sPatternFound = $sPatName; break; - } + } } - - switch($sPatternFound) + + switch ($sPatternFound) { case 'between': - - $sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1'; - $oRightExpr = new VariableExpression($sParamName1); - if ($bParseSearchString) - { - $aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]); - } - else - { - $aParams[$sParamName1] = $aMatches[1]; - } - $oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr); - $sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2'; - $oRightExpr = new VariableExpression($sParamName2); - if ($bParseSearchString) - { - $aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]); - } - else - { - $aParams[$sParamName2] = $aMatches[2]; - } - $oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr); - - $oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2); - break; - + $sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1'; + $oRightExpr = new VariableExpression($sParamName1); + if ($bParseSearchString) + { + $aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]); + } + else + { + $aParams[$sParamName1] = $aMatches[1]; + } + $oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr); + + $sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2'; + $oRightExpr = new VariableExpression($sParamName2); + if ($bParseSearchString) + { + $aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]); + } + else + { + $aParams[$sParamName2] = $aMatches[2]; + } + $oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr); + + $oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2); + break; + case 'greater than': case 'greater than or equal': case 'less than': case 'less than or equal': - $sSQLOperator = $aPatterns[$sPatternFound]['operator']; - $sParamName = $oField->GetParent().'_'.$oField->GetName(); - $oRightExpr = new VariableExpression($sParamName); - if ($bParseSearchString) - { - $aParams[$sParamName] = $this->ParseSearchString($aMatches[1]); - } - else - { - $aParams[$sParamName] = $aMatches[1]; - } - $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); - - break; - + $sSQLOperator = $aPatterns[$sPatternFound]['operator']; + $sParamName = $oField->GetParent().'_'.$oField->GetName(); + $oRightExpr = new VariableExpression($sParamName); + if ($bParseSearchString) + { + $aParams[$sParamName] = $this->ParseSearchString($aMatches[1]); + } + else + { + $aParams[$sParamName] = $aMatches[1]; + } + $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); + + break; + default: - $oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams); + $oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams); } @@ -4865,30 +5552,50 @@ class AttributeDateTime extends AttributeDBField public function GetHelpOnSmartSearch() { $sDict = parent::GetHelpOnSmartSearch(); - + $oFormat = static::GetFormat(); $sExample = $oFormat->Format(new DateTime('2015-07-19 18:40:00')); + return vsprintf($sDict, array($oFormat->ToPlaceholder(), $sExample)); - } + } } /** - * Store a duration as a number of seconds + * Store a duration as a number of seconds * - * @package iTopORM + * @package iTopORM */ class AttributeDuration extends AttributeInteger { - public function GetEditClass() {return "Duration";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11) UNSIGNED";} + public function GetEditClass() + { + return "Duration"; + } - public function GetNullValue() {return '0';} + protected function GetSQLCol($bFullSpec = false) + { + return "INT(11) UNSIGNED"; + } + + public function GetNullValue() + { + return '0'; + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return null; - if (!is_numeric($proposedValue)) return null; - if ( ((int)$proposedValue) < 0) return null; + if (is_null($proposedValue)) + { + return null; + } + if (!is_numeric($proposedValue)) + { + return null; + } + if (((int)$proposedValue) < 0) + { + return null; + } return (int)$proposedValue; } @@ -4896,9 +5603,10 @@ class AttributeDuration extends AttributeInteger public function ScalarToSQL($value) { if (is_null($value)) - { + { return null; } + return $value; } @@ -4914,34 +5622,44 @@ class AttributeDuration extends AttributeInteger if ($duration < 60) { // Less than 1 min - $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']); - } - else if ($duration < 3600) - { - // less than 1 hour, display it in minutes/seconds - $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']); - } - else if ($duration < 86400) - { - // Less than 1 day, display it in hours/minutes/seconds - $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); + $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']); } else { - // more than 1 day, display it in days/hours/minutes/seconds - $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); + if ($duration < 3600) + { + // less than 1 hour, display it in minutes/seconds + $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']); + } + else + { + if ($duration < 86400) + { + // Less than 1 day, display it in hours/minutes/seconds + $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], + $aDuration['minutes'], $aDuration['seconds']); + } + else + { + // more than 1 day, display it in days/hours/minutes/seconds + $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], + $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); + } + } } + return $sResult; } - + static function SplitDuration($duration) { - $duration = (int) $duration; + $duration = (int)$duration; $days = floor($duration / 86400); - $hours = floor(($duration - (86400*$days)) / 3600); - $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); + $hours = floor(($duration - (86400 * $days)) / 3600); + $minutes = floor(($duration - (86400 * $days + 3600 * $hours)) / 60); $seconds = ($duration % 60); // modulo - return array( 'days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds ); + + return array('days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds); } static public function GetFormFieldClass() @@ -4967,23 +5685,25 @@ class AttributeDuration extends AttributeInteger } } + /** - * Map a date+time column to an attribute + * Map a date+time column to an attribute * - * @package iTopORM + * @package iTopORM */ class AttributeDate extends AttributeDateTime { const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE; static $oDateFormat = null; - + static public function GetFormat() { if (self::$oDateFormat == null) - { - AttributeDateTime::LoadFormatFromConfig(); + { + AttributeDateTime::LoadFormatFromConfig(); } + return self::$oDateFormat; } @@ -4994,6 +5714,7 @@ class AttributeDate extends AttributeDateTime /** * Returns the format string used for the date & time stored in memory + * * @return string */ static public function GetInternalFormat() @@ -5003,26 +5724,36 @@ class AttributeDate extends AttributeDateTime /** * Returns the format string used for the date & time written to MySQL + * * @return string */ static public function GetSQLFormat() { return 'Y-m-d'; } - + static public function ListExpectedParams() { return parent::ListExpectedParams(); //return array_merge(parent::ListExpectedParams(), array()); } - public function GetEditClass() {return "Date";} - protected function GetSQLCol($bFullSpec = false) {return "DATE";} + public function GetEditClass() + { + return "Date"; + } + + protected function GetSQLCol($bFullSpec = false) + { + return "DATE"; + } + public function GetImportColumns() { // Allow an empty string to be a valid value (synonym for "reset") $aColumns = array(); $aColumns[$this->GetCode()] = 'VARCHAR(10)'; + return $aColumns; } @@ -5030,16 +5761,17 @@ class AttributeDate extends AttributeDateTime /** * Override to specify Field class * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the + * $oFormField is passed, MakeFormField behave more like a Prepare. */ public function MakeFormField(DBObject $oObject, $oFormField = null) { $oFormField = parent::MakeFormField($oObject, $oFormField); $oFormField->SetDateOnly(true); - + return $oFormField; } - + } /** @@ -5052,6 +5784,7 @@ class AttributeDeadline extends AttributeDateTime public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { $sResult = self::FormatDeadline($value); + return $sResult; } @@ -5063,7 +5796,7 @@ class AttributeDeadline extends AttributeDateTime $iValue = AttributeDateTime::GetAsUnixSeconds($value); $sDate = AttributeDateTime::GetFormat()->Format($value); $difference = $iValue - time(); - + if ($difference >= 0) { $sDifference = self::FormatDuration($difference); @@ -5082,40 +5815,47 @@ class AttributeDeadline extends AttributeDateTime static function FormatDuration($duration) { $days = floor($duration / 86400); - $hours = floor(($duration - (86400*$days)) / 3600); - $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); + $hours = floor(($duration - (86400 * $days)) / 3600); + $minutes = floor(($duration - (86400 * $days + 3600 * $hours)) / 60); if ($duration < 60) { // Less than 1 min - $sResult =Dict::S('UI:Deadline_LessThan1Min'); - } - else if ($duration < 3600) - { - // less than 1 hour, display it in minutes - $sResult =Dict::Format('UI:Deadline_Minutes', $minutes); - } - else if ($duration < 86400) - { - // Less that 1 day, display it in hours/minutes - $sResult =Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes); + $sResult = Dict::S('UI:Deadline_LessThan1Min'); } else { - // Less that 1 day, display it in hours/minutes - $sResult =Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes); + if ($duration < 3600) + { + // less than 1 hour, display it in minutes + $sResult = Dict::Format('UI:Deadline_Minutes', $minutes); + } + else + { + if ($duration < 86400) + { + // Less that 1 day, display it in hours/minutes + $sResult = Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes); + } + else + { + // Less that 1 day, display it in hours/minutes + $sResult = Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes); + } + } } + return $sResult; } } /** - * Map a foreign key to an attribute + * Map a foreign key to an attribute * AttributeExternalKey and AttributeExternalField may be an external key * the difference is that AttributeExternalKey corresponds to a column into the defined table * where an AttributeExternalField corresponds to a column into another table (class) * - * @package iTopORM + * @package iTopORM */ class AttributeExternalKey extends AttributeDBFieldVoid { @@ -5137,9 +5877,9 @@ class AttributeExternalKey extends AttributeDBFieldVoid { return self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY; } + return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; - } - catch (CoreException $e) + } catch (CoreException $e) { } @@ -5151,27 +5891,59 @@ class AttributeExternalKey extends AttributeDBFieldVoid return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete")); } - public function GetEditClass() {return "ExtKey";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} + public function GetEditClass() + { + return "ExtKey"; + } + + protected function GetSQLCol($bFullSpec = false) + { + return "INT(11)".($bFullSpec ? " DEFAULT 0" : ""); + } + public function RequiresIndex() { return true; } - public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} - public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} - public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} - public function GetKeyAttCode() {return $this->GetCode();} - public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); } - + public function IsExternalKey($iType = EXTKEY_RELATIVE) + { + return true; + } + + public function GetTargetClass($iType = EXTKEY_RELATIVE) + { + return $this->Get("targetclass"); + } + + public function GetKeyAttDef($iType = EXTKEY_RELATIVE) + { + return $this; + } + + public function GetKeyAttCode() + { + return $this->GetCode(); + } + + public function GetDisplayStyle() + { + return $this->GetOptional('display_style', 'select'); + } + + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return 0; + } - public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} public function IsNullAllowed() { if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys')) { return true; } + return $this->Get("is_null_allowed"); } @@ -5180,6 +5952,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid { return parent::GetBasicFilterOperators(); } + public function GetBasicFilterLooseOperator() { return parent::GetBasicFilterLooseOperator(); @@ -5188,7 +5961,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid public function GetBasicFilterSQLExpr($sOpCode, $value) { return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } + } // overloaded here so that an ext key always have the answer to // "what are your possible values?" @@ -5200,6 +5973,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid // Let's propose every existing value $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); } + return $oValSetDef; } @@ -5209,11 +5983,11 @@ class AttributeExternalKey extends AttributeDBFieldVoid try { return parent::GetAllowedValues($aArgs, $sContains); - } - catch (MissingQueryArgument $e) //FIXME never enters here... + } catch (Exception $e) { // Some required arguments could not be found, enlarge to any existing value $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); + return $oValSetDef->GetValues($aArgs, $sContains); } } @@ -5222,6 +5996,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid { $oValSetDef = $this->GetValuesDef(); $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); + return $oSet; } @@ -5238,31 +6013,41 @@ class AttributeExternalKey extends AttributeDBFieldVoid public function GetNullValue() { return 0; - } + } public function IsNull($proposedValue) { return ($proposedValue == 0); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return 0; - if ($proposedValue === '') return 0; - if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey(); + if (is_null($proposedValue)) + { + return 0; + } + if ($proposedValue === '') + { + return 0; + } + if (MetaModel::IsValidObject($proposedValue)) + { + return $proposedValue->GetKey(); + } + return (int)$proposedValue; } - + public function GetMaximumComboLength() { return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length')); } - + public function GetMinAutoCompleteChars() { return $this->GetOptional('min_autocomplete_chars', MetaModel::GetConfig()->Get('min_autocomplete_chars')); } - + public function AllowTargetCreation() { return $this->GetOptional('allow_target_creation', MetaModel::GetConfig()->Get('allow_target_creation')); @@ -5270,6 +6055,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid /** * Find the corresponding "link" attribute on the target class, if any + * * @return null | AttributeDefinition * @throws \CoreException */ @@ -5277,14 +6063,25 @@ class AttributeExternalKey extends AttributeDBFieldVoid { $oRet = null; $sRemoteClass = $this->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) + foreach(MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) { - if (!$oRemoteAttDef->IsLinkSet()) continue; - if (!is_subclass_of($this->GetHostClass(), $oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) continue; - if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) continue; + if (!$oRemoteAttDef->IsLinkSet()) + { + continue; + } + if (!is_subclass_of($this->GetHostClass(), + $oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) + { + continue; + } + if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) + { + continue; + } $oRet = $oRemoteAttDef; break; } + return $oRet; } @@ -5297,11 +6094,11 @@ class AttributeExternalKey extends AttributeDBFieldVoid { if ($oFormField === null) { - // TODO : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } - + // Setting params $oFormField->SetMaximumComboLength($this->GetMaximumComboLength()); $oFormField->SetMinAutoCompleteChars($this->GetMinAutoCompleteChars()); @@ -5312,11 +6109,10 @@ class AttributeExternalKey extends AttributeDBFieldVoid { $oTmpAttDef = $this; $oTmpField = $oFormField; - $oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject) - { - /** @var $oTmpField \Combodo\iTop\Form\Field\Field */ - /** @var $oTmpAttDef \AttributeDefinition */ - /** @var $oObject \DBObject */ + $oFormField->SetOnFinalizeCallback(function () use ($oTmpField, $oTmpAttDef, $oObject) { + /** @var $oTmpField \Combodo\iTop\Form\Field\Field */ + /** @var $oTmpAttDef \AttributeDefinition */ + /** @var $oObject \DBObject */ // We set search object only if it has not already been set (overrided) if ($oTmpField->GetSearch() === null) @@ -5363,10 +6159,15 @@ class AttributeHierarchicalKey extends AttributeExternalKey unset($aParams[$idx]); $idx = array_search('jointype', $aParams); unset($aParams[$idx]); - return $aParams; // TODO: mettre les bons parametres ici !! + + return $aParams; // Later: mettre les bons parametres ici !! + } + + public function GetEditClass() + { + return "ExtKey"; } - public function GetEditClass() {return "ExtKey";} public function RequiresIndex() { return true; @@ -5374,7 +6175,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey /* * The target class is the class for which the attribute has been defined first - */ + */ public function SetHostClass($sHostClass) { if (!isset($this->m_sTargetClass)) @@ -5384,15 +6185,31 @@ class AttributeHierarchicalKey extends AttributeExternalKey parent::SetHostClass($sHostClass); } - static public function IsHierarchicalKey() {return true;} - public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->m_sTargetClass;} - public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} - public function GetKeyAttCode() {return $this->GetCode();} + static public function IsHierarchicalKey() + { + return true; + } + + public function GetTargetClass($iType = EXTKEY_RELATIVE) + { + return $this->m_sTargetClass; + } + + public function GetKeyAttDef($iType = EXTKEY_RELATIVE) + { + return $this; + } + + public function GetKeyAttCode() + { + return $this->GetCode(); + } public function GetBasicFilterOperators() { return parent::GetBasicFilterOperators(); } + public function GetBasicFilterLooseOperator() { return parent::GetBasicFilterLooseOperator(); @@ -5404,12 +6221,15 @@ class AttributeHierarchicalKey extends AttributeExternalKey $aColumns[$this->GetCode()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); $aColumns[$this->GetSQLLeft()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); $aColumns[$this->GetSQLRight()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); + return $aColumns; } + public function GetSQLRight() { return $this->GetCode().'_right'; } + public function GetSQLLeft() { return $this->GetCode().'_left'; @@ -5428,6 +6248,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey $aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()]; $aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()]; } + return $aValues; } @@ -5438,6 +6259,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey { $oValSetDef = $this->GetValuesDef(); $oValSetDef->AddCondition($oFilter); + return $oValSetDef->GetValues($aArgs, $sContains); } else @@ -5455,6 +6277,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey $oValSetDef->AddCondition($oFilter); } $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); + return $oSet; } @@ -5465,6 +6288,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey { return $oFilter; } + return parent::GetAllowedValuesAsFilter($aArgs, $sContains, $iAdditionalValue); } @@ -5478,14 +6302,17 @@ class AttributeHierarchicalKey extends AttributeExternalKey if ($iRootId > 0) // ignore objects that do no exist in the database... { $sClass = $this->m_sTargetClass; + return DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId"); } } + return false; } /** * Find the corresponding "link" attribute on the target class, if any + * * @return null | AttributeDefinition */ public function GetMirrorLinkAttribute() @@ -5495,9 +6322,9 @@ class AttributeHierarchicalKey extends AttributeExternalKey } /** - * An attribute which corresponds to an external key (direct or indirect) + * An attribute which corresponds to an external key (direct or indirect) * - * @package iTopORM + * @package iTopORM */ class AttributeExternalField extends AttributeDefinition { @@ -5525,8 +6352,7 @@ class AttributeExternalField extends AttributeDefinition case ($oRemoteAtt instanceof AttributeExternalKey): return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; } - } - catch (CoreException $e) + } catch (CoreException $e) { } @@ -5539,7 +6365,10 @@ class AttributeExternalField extends AttributeDefinition return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode")); } - public function GetEditClass() {return "ExtField";} + public function GetEditClass() + { + return "ExtField"; + } /** * @return \AttributeDefinition @@ -5548,14 +6377,16 @@ class AttributeExternalField extends AttributeDefinition public function GetFinalAttDef() { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetFinalAttDef(); + + return $oExtAttDef->GetFinalAttDef(); } protected function GetSQLCol($bFullSpec = false) { // throw new CoreException("external attribute: does it make any sense to request its type ?"); $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetSQLCol($bFullSpec); + + return $oExtAttDef->GetSQLCol($bFullSpec); } public function GetSQLExpressions($sPrefix = '') @@ -5567,7 +6398,7 @@ class AttributeExternalField extends AttributeDefinition else { return $sPrefix; - } + } } public function GetLabel($sDefault = null) @@ -5587,6 +6418,7 @@ class AttributeExternalField extends AttributeDefinition $sLabel = $oRemoteAtt->GetLabel($this->m_sCode); } } + return $sLabel; } @@ -5614,8 +6446,10 @@ class AttributeExternalField extends AttributeDefinition $oRemoteAtt = $this->GetExtAttDef(); $sLabel = $oRemoteAtt->GetDescription(''); } + return $sLabel; - } + } + public function GetHelpOnEdition($sDefault = null) { $sLabel = parent::GetHelpOnEdition(''); @@ -5624,23 +6458,25 @@ class AttributeExternalField extends AttributeDefinition $oRemoteAtt = $this->GetExtAttDef(); $sLabel = $oRemoteAtt->GetHelpOnEdition(''); } + return $sLabel; - } + } public function IsExternalKey($iType = EXTKEY_RELATIVE) { - switch($iType) + switch ($iType) { - case EXTKEY_ABSOLUTE: - // see further - $oRemoteAtt = $this->GetExtAttDef(); - return $oRemoteAtt->IsExternalKey($iType); + case EXTKEY_ABSOLUTE: + // see further + $oRemoteAtt = $this->GetExtAttDef(); - case EXTKEY_RELATIVE: - return false; + return $oRemoteAtt->IsExternalKey($iType); - default: - throw new CoreException("Unexpected value for argument iType: '$iType'"); + case EXTKEY_RELATIVE: + return false; + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); } } @@ -5655,7 +6491,7 @@ class AttributeExternalField extends AttributeDefinition { $bRet = $oRemoteAtt->IsFriendlyName(); } - elseif ($oRemoteAtt instanceof AttributeFriendlyName) + elseif ($oRemoteAtt instanceof AttributeFriendlyName) { $bRet = true; } @@ -5663,6 +6499,7 @@ class AttributeExternalField extends AttributeDefinition { $bRet = false; } + return $bRet; } @@ -5671,7 +6508,10 @@ class AttributeExternalField extends AttributeDefinition return $this->GetKeyAttDef($iType)->GetTargetClass(); } - static public function IsExternalField() {return true;} + static public function IsExternalField() + { + return true; + } public function GetKeyAttCode() { @@ -5692,30 +6532,37 @@ class AttributeExternalField extends AttributeDefinition */ public function GetKeyAttDef($iType = EXTKEY_RELATIVE) { - switch($iType) + switch ($iType) { - case EXTKEY_ABSOLUTE: - // see further - /** @var \AttributeExternalKey $oRemoteAtt */ - $oRemoteAtt = $this->GetExtAttDef(); - if ($oRemoteAtt->IsExternalField()) - { - return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE); - } - else if ($oRemoteAtt->IsExternalKey()) - { - return $oRemoteAtt; - } - return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! + case EXTKEY_ABSOLUTE: + // see further + /** @var \AttributeExternalKey $oRemoteAtt */ + $oRemoteAtt = $this->GetExtAttDef(); + if ($oRemoteAtt->IsExternalField()) + { + return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE); + } + else + { + if ($oRemoteAtt->IsExternalKey()) + { + return $oRemoteAtt; + } + } - case EXTKEY_RELATIVE: - return MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! - default: - throw new CoreException("Unexpected value for argument iType: '$iType'"); + case EXTKEY_RELATIVE: + /** @var \AttributeExternalKey $oAttDef */ + $oAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + + return $oAttDef; + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); } } - + public function GetPrerequisiteAttributes($sClass = null) { return array($this->Get("extkey_attcode")); @@ -5732,7 +6579,11 @@ class AttributeExternalField extends AttributeDefinition $oKeyAttDef = $this->GetKeyAttDef(); /** @var \AttributeExternalField $oExtAttDef */ $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode")); - if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode")); + if (!is_object($oExtAttDef)) + { + throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode")); + } + return $oExtAttDef; } @@ -5743,18 +6594,22 @@ class AttributeExternalField extends AttributeDefinition public function GetSQLExpr() { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetSQLExpr(); - } + + return $oExtAttDef->GetSQLExpr(); + } public function GetDefaultValue(DBObject $oHostObject = null) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetDefaultValue(); } + public function IsNullAllowed() { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->IsNullAllowed(); + + return $oExtAttDef->IsNullAllowed(); } static public function IsScalar() @@ -5770,35 +6625,42 @@ class AttributeExternalField extends AttributeDefinition public function GetBasicFilterOperators() { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterOperators(); + + return $oExtAttDef->GetBasicFilterOperators(); } + public function GetBasicFilterLooseOperator() { $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterLooseOperator(); + + return $oExtAttDef->GetBasicFilterLooseOperator(); } public function GetBasicFilterSQLExpr($sOpCode, $value) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); - } + } public function GetNullValue() { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetNullValue(); - } + } public function IsNull($proposedValue) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->IsNull($proposedValue); - } + } public function MakeRealValue($proposedValue, $oHostObj) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj); } @@ -5806,6 +6668,7 @@ class AttributeExternalField extends AttributeDefinition { // This one could be used in case of filtering only $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->ScalarToSQL($value); } @@ -5817,22 +6680,30 @@ class AttributeExternalField extends AttributeDefinition public function FromSQLToValue($aCols, $sPrefix = '') { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->FromSQLToValue($aCols, $sPrefix); } public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsHTML($value, null, $bLocalize); } + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsXML($value, null, $bLocalize); } - public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + + public function GetAsCSV( + $value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText); } @@ -5850,30 +6721,30 @@ class AttributeExternalField extends AttributeDefinition */ public function MakeFormField(DBObject $oObject, $oFormField = null) { - // Retrieving AttDef from the remote attribute - $oRemoteAttDef = $this->GetExtAttDef(); + // Retrieving AttDef from the remote attribute + $oRemoteAttDef = $this->GetExtAttDef(); - if ($oFormField === null) + if ($oFormField === null) { - // ExternalField's FormField are actually based on the FormField from the target attribute. - // Except for the AttributeExternalKey because we have no OQL and stuff - if($oRemoteAttDef instanceof AttributeExternalKey) - { - $sFormFieldClass = static::GetFormFieldClass(); - } - else - { - $sFormFieldClass = $oRemoteAttDef::GetFormFieldClass(); - } + // ExternalField's FormField are actually based on the FormField from the target attribute. + // Except for the AttributeExternalKey because we have no OQL and stuff + if ($oRemoteAttDef instanceof AttributeExternalKey) + { + $sFormFieldClass = static::GetFormFieldClass(); + } + else + { + $sFormFieldClass = $oRemoteAttDef::GetFormFieldClass(); + } $oFormField = new $sFormFieldClass($this->GetCode()); } parent::MakeFormField($oObject, $oFormField); - // Manually setting for remote ExternalKey, otherwise, the id would be displayed. - if($oRemoteAttDef instanceof AttributeExternalKey) - { - $oFormField->SetCurrentValue($oObject->Get($this->GetCode().'_friendlyname')); - } + // Manually setting for remote ExternalKey, otherwise, the id would be displayed. + if ($oRemoteAttDef instanceof AttributeExternalKey) + { + $oFormField->SetCurrentValue($oObject->Get($this->GetCode().'_friendlyname')); + } // Readonly field because we can't update external fields $oFormField->SetReadOnly(true); @@ -5885,41 +6756,13 @@ class AttributeExternalField extends AttributeDefinition { return false; } - } /** - * Multi value list of tags + * Map a varchar column to an URL (formats the ouput in HMTL) * - * @see TagSetFieldData - * @since 2.6 N°931 tag fields - */ -class AttributeTagSet extends AttributeString -{ - //TODO SQL type length (nb of tags per record, max tag length) - //TODO implement ?? - //TODO specific filters - public function RequiresIndex() - { - return true; - } - - public function RequiresFullTextIndex() - { - return true; - } - - public function IsNullAllowed() - { - return true; - } -} - -/** - * Map a varchar column to an URL (formats the ouput in HMTL) - * - * @package iTopORM + * @package iTopORM */ class AttributeURL extends AttributeString { @@ -5940,19 +6783,26 @@ class AttributeURL extends AttributeString { return 2048; } - - public function GetEditClass() {return "String";} + + public function GetEditClass() + { + return "String"; + } public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) { $sTarget = $this->Get("target"); - if (empty($sTarget)) $sTarget = "_blank"; + if (empty($sTarget)) + { + $sTarget = "_blank"; + } $sLabel = Str::pure2html($sValue); if (strlen($sLabel) > 128) { // Truncate the length to 128 characters, by removing the middle $sLabel = substr($sLabel, 0, 100).'.....'.substr($sLabel, -20); } + return "$sLabel"; } @@ -5961,10 +6811,10 @@ class AttributeURL extends AttributeString return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('url_validation_pattern').'$'); } - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\UrlField'; - } + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\UrlField'; + } /** * @param \DBObject $oObject @@ -5973,25 +6823,25 @@ class AttributeURL extends AttributeString * @return null * @throws \CoreException */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); - $oFormField->SetTarget($this->Get('target')); + $oFormField->SetTarget($this->Get('target')); - return $oFormField; - } + return $oFormField; + } } /** - * A blob is an ormDocument, it is stored as several columns in the database + * A blob is an ormDocument, it is stored as several columns in the database * - * @package iTopORM + * @package iTopORM */ class AttributeBlob extends AttributeDefinition { @@ -6002,29 +6852,55 @@ class AttributeBlob extends AttributeDefinition return array_merge(parent::ListExpectedParams(), array("depends_on")); } - public function GetEditClass() {return "Document";} + public function GetEditClass() + { + return "Document"; + } - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return "";} - public function IsNullAllowed(DBObject $oHostObject = null) {return $this->GetOptional("is_null_allowed", false);} + static public function IsBasedOnDBColumns() + { + return true; + } + + static public function IsScalar() + { + return true; + } + + public function IsWritable() + { + return true; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return ""; + } + + public function IsNullAllowed(DBObject $oHostObject = null) + { + return $this->GetOptional("is_null_allowed", false); + } public function GetEditValue($sValue, $oHostObj = null) { return ''; } - + /** * Users can provide the document from an URL (including an URL on iTop itself) * for CSV import. Administrators can even provide the path to a local file * {@inheritDoc} + * * @see AttributeDefinition::MakeRealValue() */ public function MakeRealValue($proposedValue, $oHostObj) { - if ($proposedValue === null) return null; - + if ($proposedValue === null) + { + return null; + } + if (is_object($proposedValue)) { $proposedValue = clone $proposedValue; @@ -6035,14 +6911,14 @@ class AttributeBlob extends AttributeDefinition { // Read the file from iTop, an URL (or the local file system - for admins only) $proposedValue = Utils::FileGetContentsAndMIMEType($proposedValue); - } - catch(Exception $e) + } catch (Exception $e) { IssueLog::Warning(get_class($this)."::MakeRealValue - ".$e->getMessage()); // Not a real document !! store is as text !!! (This was the default behavior before) $proposedValue = new ormDocument($e->getMessage()." \n".$proposedValue, 'text/plain'); - } + } } + return $proposedValue; } @@ -6057,6 +6933,7 @@ class AttributeBlob extends AttributeDefinition $aColumns[''] = $sPrefix.'_mimetype'; $aColumns['_data'] = $sPrefix.'_data'; $aColumns['_filename'] = $sPrefix.'_filename'; + return $aColumns; } @@ -6066,24 +6943,25 @@ class AttributeBlob extends AttributeDefinition { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } + } $sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; - if (!array_key_exists($sPrefix.'_data', $aCols)) + if (!array_key_exists($sPrefix.'_data', $aCols)) { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}"); - } + } $data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null; - if (!array_key_exists($sPrefix.'_filename', $aCols)) + if (!array_key_exists($sPrefix.'_filename', $aCols)) { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}"); - } - $sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : ''; + } + $sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : ''; $value = new ormDocument($data, $sMimeType, $sFileName); + return $value; } @@ -6109,6 +6987,7 @@ class AttributeBlob extends AttributeDefinition $aValues[$this->GetCode().'_mimetype'] = ''; $aValues[$this->GetCode().'_filename'] = ''; } + return $aValues; } @@ -6118,6 +6997,7 @@ class AttributeBlob extends AttributeDefinition $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb) $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); + return $aColumns; } @@ -6130,6 +7010,7 @@ class AttributeBlob extends AttributeDefinition { return array(); } + public function GetBasicFilterLooseOperator() { return '='; @@ -6138,7 +7019,7 @@ class AttributeBlob extends AttributeDefinition public function GetBasicFilterSQLExpr($sOpCode, $value) { return 'true'; - } + } public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { @@ -6146,6 +7027,7 @@ class AttributeBlob extends AttributeDefinition { return $value->GetAsHTML(); } + return ''; } @@ -6159,13 +7041,16 @@ class AttributeBlob extends AttributeDefinition * * @return string */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $sAttCode = $this->GetCode(); if ($sValue instanceof ormDocument && !$sValue->IsEmpty()) { return $sValue->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $sAttCode); } + return ''; // Not exportable in CSV ! } @@ -6188,13 +7073,14 @@ class AttributeBlob extends AttributeDefinition $sRet .= ''.base64_encode($value->GetData()).''; } } + return $sRet; } /** * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ + * The operation is the opposite to FromJSONToValue + */ public function GetForJSON($value) { if ($value instanceOf ormDocument) @@ -6208,13 +7094,14 @@ class AttributeBlob extends AttributeDefinition { $aValues = null; } + return $aValues; } /** * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - */ + * The operation is the opposite to GetForJSON + */ public function FromJSONToValue($json) { if (isset($json->data)) @@ -6226,9 +7113,10 @@ class AttributeBlob extends AttributeDefinition { $value = null; } + return $value; } - + public function Fingerprint($value) { $sFingerprint = ''; @@ -6236,7 +7124,8 @@ class AttributeBlob extends AttributeDefinition { $sFingerprint = md5($value->GetData()); } - return $sFingerprint; + + return $sFingerprint; } static public function GetFormFieldClass() @@ -6270,11 +7159,14 @@ class AttributeBlob extends AttributeDefinition /** * An image is a specific type of document, it is stored as several columns in the database * - * @package iTopORM + * @package iTopORM */ class AttributeImage extends AttributeBlob { - public function GetEditClass() {return "Image";} + public function GetEditClass() + { + return "Image"; + } /** * {@inheritDoc} @@ -6283,13 +7175,15 @@ class AttributeImage extends AttributeBlob public function MakeRealValue($proposedValue, $oHostObj) { $oDoc = parent::MakeRealValue($proposedValue, $oHostObj); + // The validation of the MIME Type is done by CheckFormat below return $oDoc; } - + /** * Check that the supplied ormDocument actually contains an image * {@inheritDoc} + * * @see AttributeDefinition::CheckFormat() */ public function CheckFormat($value) @@ -6298,9 +7192,10 @@ class AttributeImage extends AttributeBlob { return ($value->GetMainMimeType() == 'image'); } + return true; } - + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { $iMaxWidthPx = $this->Get('display_max_width').'px'; @@ -6309,7 +7204,8 @@ class AttributeImage extends AttributeBlob $sRet = ($sUrl !== null) ? '' : ''; if (is_object($value) && !$value->IsEmpty()) { - if ($oHostObject->IsNew() || ($oHostObject->IsModified() && (array_key_exists($this->GetCode(), $oHostObject->ListChanges())))) + if ($oHostObject->IsNew() || ($oHostObject->IsModified() && (array_key_exists($this->GetCode(), + $oHostObject->ListChanges())))) { // If the object is modified (or not yet stored in the database) we must serve the content of the image directly inline // otherwise (if we just give an URL) the browser will be given the wrong content... and may cache it @@ -6321,44 +7217,48 @@ class AttributeImage extends AttributeBlob } $sRet = ''; } + return '
    '.$sRet.'
    '; } - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\ImageField'; - } + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\ImageField'; + } - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } - parent::MakeFormField($oObject, $oFormField); + parent::MakeFormField($oObject, $oFormField); - // Generating urls - $value = $oObject->Get($this->GetCode()); - if (is_object($value) && !$value->IsEmpty()) - { - $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - } - else - { - $oFormField->SetDownloadUrl($this->Get('default_image')); - $oFormField->SetDisplayUrl($this->Get('default_image')); - } + // Generating urls + $value = $oObject->Get($this->GetCode()); + if (is_object($value) && !$value->IsEmpty()) + { + $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), + $this->GetCode())); + $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), + $this->GetCode())); + } + else + { + $oFormField->SetDownloadUrl($this->Get('default_image')); + $oFormField->SetDisplayUrl($this->Get('default_image')); + } - return $oFormField; - } + return $oFormField; + } } + /** - * A stop watch is an ormStopWatch object, it is stored as several columns in the database + * A stop watch is an ormStopWatch object, it is stored as several columns in the database * - * @package iTopORM + * @package iTopORM */ class AttributeStopWatch extends AttributeDefinition { @@ -6367,15 +7267,34 @@ class AttributeStopWatch extends AttributeDefinition static public function ListExpectedParams() { // The list of thresholds must be an array of iPercent => array of 'option' => value - return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds")); + return array_merge(parent::ListExpectedParams(), + array("states", "goal_computing", "working_time_computing", "thresholds")); } - public function GetEditClass() {return "StopWatch";} + public function GetEditClass() + { + return "StopWatch"; + } - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->NewStopWatch();} + static public function IsBasedOnDBColumns() + { + return true; + } + + static public function IsScalar() + { + return true; + } + + public function IsWritable() + { + return true; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->NewStopWatch(); + } /** * @param \ormStopWatch $value @@ -6401,14 +7320,15 @@ class AttributeStopWatch extends AttributeDefinition /** * Construct a brand new (but configured) stop watch - */ + */ public function NewStopWatch() { $oSW = new ormStopWatch(); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $oSW->DefineThreshold($iThreshold); } + return $oSW; } @@ -6419,6 +7339,7 @@ class AttributeStopWatch extends AttributeDefinition { return $this->NewStopWatch(); } + return $proposedValue; } @@ -6434,7 +7355,7 @@ class AttributeStopWatch extends AttributeDefinition $aColumns['_started'] = $sPrefix.'_started'; $aColumns['_laststart'] = $sPrefix.'_laststart'; $aColumns['_stopped'] = $sPrefix.'_stopped'; - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = '_'.$iThreshold; $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline'; @@ -6442,6 +7363,7 @@ class AttributeStopWatch extends AttributeDefinition $aColumns[$sThPrefix.'_triggered'] = $sPrefix.$sThPrefix.'_triggered'; $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun'; } + return $aColumns; } @@ -6453,6 +7375,7 @@ class AttributeStopWatch extends AttributeDefinition } $oDateTime = new DateTime($sDate); $iSeconds = $oDateTime->format('U'); + return $iSeconds; } @@ -6462,13 +7385,14 @@ class AttributeStopWatch extends AttributeDefinition { return null; } + return date("Y-m-d H:i:s", $iSeconds); } public function FromSQLToValue($aCols, $sPrefix = '') { $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped'); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = '_'.$iThreshold; $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline'; @@ -6476,13 +7400,13 @@ class AttributeStopWatch extends AttributeDefinition $aExpectedCols[] = $sPrefix.$sThPrefix.'_triggered'; $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun'; } - foreach ($aExpectedCols as $sExpectedCol) + foreach($aExpectedCols as $sExpectedCol) { if (!array_key_exists($sExpectedCol, $aCols)) { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}"); - } + } } $value = new ormStopWatch( @@ -6492,7 +7416,7 @@ class AttributeStopWatch extends AttributeDefinition self::DateToSeconds($aCols[$sPrefix.'_stopped']) ); - foreach ($this->ListThresholds() as $iThreshold => $aDefinition) + foreach($this->ListThresholds() as $iThreshold => $aDefinition) { $sThPrefix = '_'.$iThreshold; $value->DefineThreshold( @@ -6518,7 +7442,7 @@ class AttributeStopWatch extends AttributeDefinition $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate()); $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate()); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold)); @@ -6535,6 +7459,7 @@ class AttributeStopWatch extends AttributeDefinition $aValues[$this->GetCode().'_laststart'] = ''; $aValues[$this->GetCode().'_stopped'] = ''; } + return $aValues; } @@ -6545,7 +7470,7 @@ class AttributeStopWatch extends AttributeDefinition $aColumns[$this->GetCode().'_started'] = 'DATETIME'; $aColumns[$this->GetCode().'_laststart'] = 'DATETIME'; $aColumns[$this->GetCode().'_stopped'] = 'DATETIME'; - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; $aColumns[$sPrefix.'_deadline'] = 'DATETIME'; @@ -6553,6 +7478,7 @@ class AttributeStopWatch extends AttributeDefinition $aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)'; $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED'; } + return $aColumns; } @@ -6564,7 +7490,7 @@ class AttributeStopWatch extends AttributeDefinition $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'), $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped') ); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sPrefix = $this->GetCode().'_'.$iThreshold; $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline'); @@ -6572,6 +7498,7 @@ class AttributeStopWatch extends AttributeDefinition $aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered'); $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun'); } + return $aRes; } @@ -6579,6 +7506,7 @@ class AttributeStopWatch extends AttributeDefinition { return array(); } + public function GetBasicFilterLooseOperator() { return '='; @@ -6602,6 +7530,7 @@ class AttributeStopWatch extends AttributeDefinition { return $value->GetAsHTML($this, $oHostObject); } + return ''; } @@ -6615,8 +7544,10 @@ class AttributeStopWatch extends AttributeDefinition * * @return string */ - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { return $value->GetTimeSpent(); } @@ -6636,7 +7567,7 @@ class AttributeStopWatch extends AttributeDefinition { return $this->Get('thresholds'); } - + public function Fingerprint($value) { $sFingerprint = ''; @@ -6644,6 +7575,7 @@ class AttributeStopWatch extends AttributeDefinition { $sFingerprint = $value->GetAsHTML($this); } + return $sFingerprint; } @@ -6659,35 +7591,35 @@ class AttributeStopWatch extends AttributeDefinition public function GetSubItemSQLExpression($sItemCode) { $sPrefix = $this->GetCode(); - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - return array('' => $sPrefix.'_timespent'); - case 'started': - return array('' => $sPrefix.'_started'); - case 'laststart': - return array('' => $sPrefix.'_laststart'); - case 'stopped': - return array('' => $sPrefix.'_stopped'); + case 'timespent': + return array('' => $sPrefix.'_timespent'); + case 'started': + return array('' => $sPrefix.'_started'); + case 'laststart': + return array('' => $sPrefix.'_laststart'); + case 'stopped': + return array('' => $sPrefix.'_stopped'); } - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + switch ($sThresholdCode) { - case 'deadline': - return array('' => $sPrefix.'_'.$iThreshold.'_deadline'); - case 'passed': - return array('' => $sPrefix.'_'.$iThreshold.'_passed'); - case 'triggered': - return array('' => $sPrefix.'_'.$iThreshold.'_triggered'); - case 'overrun': - return array('' => $sPrefix.'_'.$iThreshold.'_overrun'); + case 'deadline': + return array('' => $sPrefix.'_'.$iThreshold.'_deadline'); + case 'passed': + return array('' => $sPrefix.'_'.$iThreshold.'_passed'); + case 'triggered': + return array('' => $sPrefix.'_'.$iThreshold.'_triggered'); + case 'overrun': + return array('' => $sPrefix.'_'.$iThreshold.'_overrun'); } } } @@ -6705,35 +7637,35 @@ class AttributeStopWatch extends AttributeDefinition public function GetSubItemValue($sItemCode, $value, $oHostObject = null) { $oStopWatch = $value; - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - return $oStopWatch->GetTimeSpent(); - case 'started': - return $oStopWatch->GetStartDate(); - case 'laststart': - return $oStopWatch->GetLastStartDate(); - case 'stopped': - return $oStopWatch->GetStopDate(); + case 'timespent': + return $oStopWatch->GetTimeSpent(); + case 'started': + return $oStopWatch->GetStartDate(); + case 'laststart': + return $oStopWatch->GetLastStartDate(); + case 'stopped': + return $oStopWatch->GetStopDate(); } - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + switch ($sThresholdCode) { - case 'deadline': - return $oStopWatch->GetThresholdDate($iThreshold); - case 'passed': - return $oStopWatch->IsThresholdPassed($iThreshold); - case 'triggered': - return $oStopWatch->IsThresholdTriggered($iThreshold); - case 'overrun': - return $oStopWatch->GetOverrun($iThreshold); + case 'deadline': + return $oStopWatch->GetThresholdDate($iThreshold); + case 'passed': + return $oStopWatch->IsThresholdPassed($iThreshold); + case 'triggered': + return $oStopWatch->IsThresholdTriggered($iThreshold); + case 'overrun': + return $oStopWatch->GetOverrun($iThreshold); } } } @@ -6744,48 +7676,51 @@ class AttributeStopWatch extends AttributeDefinition protected function GetBooleanLabel($bValue) { $sDictKey = $bValue ? 'yes' : 'no'; + return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey); } public function GetSubItemAsHTMLForHistory($sItemCode, $sValue) { $sHtml = null; - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null; - break; - case 'started': - case 'laststart': - case 'stopped': - $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; - break; + case 'timespent': + $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null; + break; + case 'started': + case 'laststart': + case 'stopped': + $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; + break; - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + default: + foreach($this->ListThresholds() as $iThreshold => $aFoo) { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { - case 'deadline': - $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; - break; - case 'passed': - $sHtml = $this->GetBooleanLabel((int)$sValue); - break; - case 'triggered': - $sHtml = $this->GetBooleanLabel((int)$sValue); - break; - case 'overrun': - $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : ''; + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) + { + case 'deadline': + $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), + (int)$sValue) : null; + break; + case 'passed': + $sHtml = $this->GetBooleanLabel((int)$sValue); + break; + case 'triggered': + $sHtml = $this->GetBooleanLabel((int)$sValue); + break; + case 'overrun': + $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : ''; + } } } - } } + return $sHtml; } @@ -6815,9 +7750,9 @@ class AttributeStopWatch extends AttributeDefinition break; default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { - $sThPrefix = $iThreshold . '_'; + $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned @@ -6846,6 +7781,7 @@ class AttributeStopWatch extends AttributeDefinition } } } + return $sRet; } @@ -6875,9 +7811,9 @@ class AttributeStopWatch extends AttributeDefinition break; default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) + foreach($this->ListThresholds() as $iThreshold => $aFoo) { - $sThPrefix = $iThreshold . '_'; + $sThPrefix = $iThreshold.'_'; if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { // The current threshold is concerned @@ -6906,65 +7842,68 @@ class AttributeStopWatch extends AttributeDefinition } } } + return $sHtml; } - public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false) - { + public function GetSubItemAsCSV( + $sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false + ) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$value); $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; + case 'timespent': + $sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier; break; - case 'started': - case 'laststart': - case 'stopped': + case 'started': + case 'laststart': + case 'stopped': if ($value !== null) { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; + $sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier; } break; - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + default: + foreach($this->ListThresholds() as $iThreshold => $aFoo) { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { - case 'deadline': + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) + { + case 'deadline': if ($value != '') { $oDateTime = new DateTime(); $oDateTime->setTimestamp($value); $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; + $sRet = $sTextQualifier.$oDateTimeFormat->Format($oDateTime).$sTextQualifier; } break; case 'passed': case 'triggered': - $sRet = $sTextQualifier . $this->GetBooleanLabel($value) . $sTextQualifier; + $sRet = $sTextQualifier.$this->GetBooleanLabel($value).$sTextQualifier; break; case 'overrun': - $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; + $sRet = $sTextQualifier.AttributeDuration::FormatDuration($value).$sTextQualifier; break; } + } } - } } + return $sRet; } @@ -6972,38 +7911,39 @@ class AttributeStopWatch extends AttributeDefinition { $sRet = Str::pure2xml((string)$value); - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - case 'started': - case 'laststart': - case 'stopped': + case 'timespent': + case 'started': + case 'laststart': + case 'stopped': break; - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + default: + foreach($this->ListThresholds() as $iThreshold => $aFoo) { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { - case 'deadline': - break; + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) + { + case 'deadline': + break; - case 'passed': - case 'triggered': - $sRet = $this->GetBooleanLabel($value); - break; + case 'passed': + case 'triggered': + $sRet = $this->GetBooleanLabel($value); + break; - case 'overrun': - break; + case 'overrun': + break; + } } } - } } + return $sRet; } @@ -7019,54 +7959,55 @@ class AttributeStopWatch extends AttributeDefinition { $sRet = $value; - switch($sItemCode) + switch ($sItemCode) { - case 'timespent': - break; + case 'timespent': + break; - case 'started': - case 'laststart': - case 'stopped': - if (is_null($value)) - { - $sRet = ''; // Undefined - } - else - { - $sRet = date((string)AttributeDateTime::GetFormat(), $value); - } - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + case 'started': + case 'laststart': + case 'stopped': + if (is_null($value)) { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) + $sRet = ''; // Undefined + } + else + { + $sRet = date((string)AttributeDateTime::GetFormat(), $value); + } + break; + + default: + foreach($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) { - case 'deadline': - if ($value) + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) { - $sRet = date((string)AttributeDateTime::GetFormat(), $value); + case 'deadline': + if ($value) + { + $sRet = date((string)AttributeDateTime::GetFormat(), $value); + } + else + { + $sRet = ''; + } + break; + case 'passed': + case 'triggered': + $sRet = $this->GetBooleanLabel($value); + break; + case 'overrun': + break; } - else - { - $sRet = ''; - } - break; - case 'passed': - case 'triggered': - $sRet = $this->GetBooleanLabel($value); - break; - case 'overrun': - break; } } - } } + return $sRet; } } @@ -7076,7 +8017,7 @@ class AttributeStopWatch extends AttributeDefinition * If an attribute implements the verbs GetSubItem.... then it can expose * internal values, each of them being an attribute and therefore they * can be displayed at different times in the object lifecycle, and used for - * reporting (as a condition in OQL, or as an additional column in an export) + * reporting (as a condition in OQL, or as an additional column in an export) * Known usages: Stop Watches can expose threshold statuses */ class AttributeSubItem extends AttributeDefinition @@ -7088,29 +8029,58 @@ class AttributeSubItem extends AttributeDefinition return array_merge(parent::ListExpectedParams(), array('target_attcode', 'item_code')); } - public function GetParentAttCode() {return $this->Get("target_attcode");} + public function GetParentAttCode() + { + return $this->Get("target_attcode"); + } /** - * Helper : get the attribute definition to which the execution will be forwarded - */ + * Helper : get the attribute definition to which the execution will be forwarded + */ public function GetTargetAttDef() { $sClass = $this->GetHostClass(); $oParentAttDef = MetaModel::GetAttributeDef($sClass, $this->Get('target_attcode')); + return $oParentAttDef; } - public function GetEditClass() {return "";} - - public function GetValuesDef() {return null;} + public function GetEditClass() + { + return ""; + } + + public function GetValuesDef() + { + return null; + } + + static public function IsBasedOnDBColumns() + { + return true; + } + + static public function IsScalar() + { + return true; + } + + public function IsWritable() + { + return false; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return null; + } - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return false;} - public function GetDefaultValue(DBObject $oHostObject = null) {return null;} // public function IsNullAllowed() {return false;} - static public function LoadInObject() {return false;} // if this verb returns false, then GetValue must be implemented + static public function LoadInObject() + { + return false; + } // if this verb returns false, then GetValues must be implemented /** * Used by DBOBject::Get() @@ -7126,6 +8096,7 @@ class AttributeSubItem extends AttributeDefinition $oParent = $this->GetTargetAttDef(); $parentValue = $oHostObject->GetStrict($oParent->GetCode()); $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject); + return $res; } @@ -7150,6 +8121,7 @@ class AttributeSubItem extends AttributeDefinition { return array(); } + public function GetBasicFilterLooseOperator() { return "="; @@ -7160,19 +8132,20 @@ class AttributeSubItem extends AttributeDefinition $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '=': - default: - return $this->GetSQLExpr()." = $sQValue"; + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '=': + default: + return $this->GetSQLExpr()." = $sQValue"; } - } + } public function GetSQLExpressions($sPrefix = '') { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemSQLExpression($this->Get('item_code')); + return $res; } @@ -7180,6 +8153,7 @@ class AttributeSubItem extends AttributeDefinition { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemAsPlainText($this->Get('item_code'), $value); + return $res; } @@ -7187,6 +8161,7 @@ class AttributeSubItem extends AttributeDefinition { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemAsHTML($this->Get('item_code'), $value); + return $res; } @@ -7194,33 +8169,40 @@ class AttributeSubItem extends AttributeDefinition { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value); + return $res; } - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, $bConvertToPlainText); + $res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, + $bConvertToPlainText); + return $res; } - + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemAsXML($this->Get('item_code'), $value); + return $res; } /** * As of now, this function must be implemented to have the value in spreadsheet format - */ + */ public function GetEditValue($value, $oHostObj = null) { $oParent = $this->GetTargetAttDef(); $res = $oParent->GetSubItemAsEditValue($this->Get('item_code'), $value); + return $res; } - + public function IsPartOfFingerprint() { return false; @@ -7262,13 +8244,35 @@ class AttributeOneWayPassword extends AttributeDefinition return array_merge(parent::ListExpectedParams(), array("depends_on")); } - public function GetEditClass() {return "One Way Password";} + public function GetEditClass() + { + return "One Way Password"; + } - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return "";} - public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + static public function IsBasedOnDBColumns() + { + return true; + } + + static public function IsScalar() + { + return true; + } + + public function IsWritable() + { + return true; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return ""; + } + + public function IsNullAllowed() + { + return $this->GetOptional("is_null_allowed", false); + } // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted) public function MakeRealValue($proposedValue, $oHostObj) @@ -7283,6 +8287,7 @@ class AttributeOneWayPassword extends AttributeDefinition $oPassword = new ormPassword('', ''); $oPassword->SetPassword($proposedValue); } + return $oPassword; } @@ -7296,6 +8301,7 @@ class AttributeOneWayPassword extends AttributeDefinition // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix $aColumns[''] = $sPrefix.'_hash'; $aColumns['_salt'] = $sPrefix.'_salt'; + return $aColumns; } @@ -7305,17 +8311,18 @@ class AttributeOneWayPassword extends AttributeDefinition { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } + } $hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; - if (!array_key_exists($sPrefix.'_salt', $aCols)) + if (!array_key_exists($sPrefix.'_salt', $aCols)) { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}"); - } + } $sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : ''; $value = new ormPassword($hashed, $sSalt); + return $value; } @@ -7339,6 +8346,7 @@ class AttributeOneWayPassword extends AttributeDefinition $aValues[$this->GetCode().'_hash'] = ''; $aValues[$this->GetCode().'_salt'] = ''; } + return $aValues; } @@ -7347,6 +8355,7 @@ class AttributeOneWayPassword extends AttributeDefinition $aColumns = array(); $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB'; $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB'; + return $aColumns; } @@ -7354,6 +8363,7 @@ class AttributeOneWayPassword extends AttributeDefinition { $aColumns = array(); $aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::GetSqlStringColumnDefinition(); + return $aColumns; } @@ -7363,11 +8373,12 @@ class AttributeOneWayPassword extends AttributeDefinition { $sAvailable = implode(', ', array_keys($aCols)); throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } + } $sClearPwd = $aCols[$sPrefix]; $oPassword = new ormPassword('', ''); $oPassword->SetPassword($sClearPwd); + return $oPassword; } @@ -7381,6 +8392,7 @@ class AttributeOneWayPassword extends AttributeDefinition { return array(); } + public function GetBasicFilterLooseOperator() { return '='; @@ -7389,7 +8401,7 @@ class AttributeOneWayPassword extends AttributeDefinition public function GetBasicFilterSQLExpr($sOpCode, $value) { return 'true'; - } + } public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { @@ -7397,25 +8409,28 @@ class AttributeOneWayPassword extends AttributeDefinition { return $value->GetAsHTML(); } + return ''; } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { return ''; // Not exportable in CSV } - + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { return ''; // Not exportable in XML } - + public function GetValueLabel($sValue, $oHostObj = null) { // Don't display anything in "group by" reports return '*****'; } - + } // Indexed array having two dimensions @@ -7423,7 +8438,10 @@ class AttributeTable extends AttributeDBField { const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - public function GetEditClass() {return "Table";} + public function GetEditClass() + { + return "Table"; + } protected function GetSQLCol($bFullSpec = false) { @@ -7438,12 +8456,12 @@ class AttributeTable extends AttributeDBField public function GetNullValue() { return array(); - } + } public function IsNull($proposedValue) { return (count($proposedValue) == 0); - } + } public function GetEditValue($sValue, $oHostObj = null) { @@ -7457,10 +8475,14 @@ class AttributeTable extends AttributeDBField { return array(); } - else if (!is_array($proposedValue)) + else { - return array(0 => array(0 => $proposedValue)); + if (!is_array($proposedValue)) + { + return array(0 => array(0 => $proposedValue)); + } } + return $proposedValue; } @@ -7473,8 +8495,7 @@ class AttributeTable extends AttributeDBField { $value = $this->MakeRealValue($aCols[$sPrefix.''], null); } - } - catch(Exception $e) + } catch (Exception $e) { $value = $this->MakeRealValue($aCols[$sPrefix.''], null); } @@ -7486,6 +8507,7 @@ class AttributeTable extends AttributeDBField { $aValues = array(); $aValues[$this->Get("sql")] = serialize($value); + return $aValues; } @@ -7505,7 +8527,7 @@ class AttributeTable extends AttributeDBField foreach($value as $iRow => $aRawData) { $sRes .= ""; - foreach ($aRawData as $iCol => $cell) + foreach($aRawData as $iCol => $cell) { // Note: avoid the warning in case the cell is made of an array $sCell = @Str::pure2html((string)$cell); @@ -7516,18 +8538,21 @@ class AttributeTable extends AttributeDBField } $sRes .= ""; $sRes .= ""; + return $sRes; } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { // Not implemented return ''; } public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - if (count($value) == 0) + if (!is_array($value) || count($value) == 0) { return ""; } @@ -7536,13 +8561,14 @@ class AttributeTable extends AttributeDBField foreach($value as $iRow => $aRawData) { $sRes .= ""; - foreach ($aRawData as $iCol => $cell) + foreach($aRawData as $iCol => $cell) { $sCell = Str::pure2xml((string)$cell); $sRes .= "$sCell"; } $sRes .= ""; } + return $sRes; } } @@ -7550,7 +8576,10 @@ class AttributeTable extends AttributeDBField // The PHP value is a hash array, it is stored as a TEXT column class AttributePropertySet extends AttributeTable { - public function GetEditClass() {return "PropertySet";} + public function GetEditClass() + { + return "PropertySet"; + } // Facilitate things: allow the user to Set the value from a string public function MakeRealValue($proposedValue, $oHostObj) @@ -7559,6 +8588,7 @@ class AttributePropertySet extends AttributeTable { return array('?' => (string)$proposedValue); } + return $proposedValue; } @@ -7588,12 +8618,15 @@ class AttributePropertySet extends AttributeTable } $sRes .= ""; $sRes .= ""; + return $sRes; } - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (count($value) == 0) + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { + if (!is_array($value) || count($value) == 0) { return ""; } @@ -7614,12 +8647,13 @@ class AttributePropertySet extends AttributeTable $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, $sRaw); + return $sTextQualifier.$sEscaped.$sTextQualifier; } public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - if (count($value) == 0) + if (!is_array($value) || count($value) == 0) { return ""; } @@ -7635,20 +8669,117 @@ class AttributePropertySet extends AttributeTable $sRes .= Str::pure2xml((string)$sValue); $sRes .= ""; } + return $sRes; } } /** * An unordered multi values attribute + * Allowed values are mandatory for this attribute to be modified * * Class AttributeSet */ -class AttributeSet extends AttributeDBFieldVoid +abstract class AttributeSet extends AttributeDBFieldVoid { + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + public function __construct($sCode, array $aParams) + { + parent::__construct($sCode, $aParams); + $this->aCSSClasses[] = 'attribute-set'; + } + static public function ListExpectedParams() { - return array_merge(parent::ListExpectedParams(), array('is_null_allowed')); + return array_merge(parent::ListExpectedParams(), array('is_null_allowed', 'max_items')); + } + + /** + * Allowed values are mandatory for this attribute to be modified + * + * @param array $aArgs + * @param string $sContains + * + * @return array|null + * @throws \CoreException + * @throws \OQLException + */ + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + return parent::GetAllowedValues($aArgs, $sContains); + } + + /** + * @param \ormSet $oValue + * + * @param $aArgs + * + * @return string JSON to be used in the itop.set_widget JQuery widget + * @throws \CoreException + * @throws \OQLException + */ + public function GetJsonForWidget($oValue, $aArgs = array()) + { + $aJson = array(); + + // possible_values + $aAllowedValues = $this->GetAllowedValues($aArgs); + $aSetKeyValData = array(); + foreach($aAllowedValues as $sCode => $sLabel) + { + $aSetKeyValData[] = [ + 'code' => $sCode, + 'label' => $sLabel, + ]; + } + $aJson['possible_values'] = $aSetKeyValData; + $aRemoved = array(); + if (is_null($oValue)) + { + $aJson['partial_values'] = array(); + $aJson['orig_value'] = array(); + } + else + { + $aPartialValues = $oValue->GetModified(); + foreach ($aPartialValues as $key => $value) + { + if (!isset($aAllowedValues[$value])) + { + unset($aPartialValues[$key]); + } + } + $aJson['partial_values'] = array_values($aPartialValues); + $aOrigValues = array_merge($oValue->GetValues(), $oValue->GetModified()); + foreach ($aOrigValues as $key => $value) + { + if (!isset($aAllowedValues[$value])) + { + // Remove unwanted values + $aRemoved[] = $value; + unset($aOrigValues[$key]); + } + } + $aJson['orig_value'] = array_values($aOrigValues); + } + $aJson['added'] = array(); + $aJson['removed'] = $aRemoved; + + $iMaxTags = $this->GetMaxItems(); + $aJson['max_items_allowed'] = $iMaxTags; + + return json_encode($aJson); + } + + public function RequiresIndex() + { + return true; + } + + public function RequiresFullTextIndex() + { + return true; } public function GetDefaultValue(DBObject $oHostObject = null) @@ -7663,7 +8794,7 @@ class AttributeSet extends AttributeDBFieldVoid public function GetEditClass() { - return "List"; + return "Set"; } public function GetEditValue($value, $oHostObj = null) @@ -7672,6 +8803,10 @@ class AttributeSet extends AttributeDBFieldVoid { return $value; } + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); @@ -7692,19 +8827,32 @@ class AttributeSet extends AttributeDBFieldVoid return 255; } + public function FromStringToArray($proposedValue) + { + $aValues = array(); + if (!empty($proposedValue)) + { + foreach(explode(',', $proposedValue) as $sCode) + { + $sValue = trim($sCode); + $aValues[] = $sValue; + } + } + return $aValues; + } + /** * @param array $aCols * @param string $sPrefix * * @return mixed - * @throws \CoreException * @throws \Exception */ public function FromSQLToValue($aCols, $sPrefix = '') { $sValue = $aCols["$sPrefix"]; - return $this->MakeRealValue($sValue, null); + return $this->MakeRealValue($sValue, null, true); } /** @@ -7727,35 +8875,27 @@ class AttributeSet extends AttributeDBFieldVoid * @param $proposedValue * @param \DBObject $oHostObj * + * @param bool $bIgnoreErrors + * * @return mixed - * @throws \Exception + * @throws \CoreException + * @throws \CoreUnexpectedValue */ - public function MakeRealValue($proposedValue, $oHostObj) + public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false) { - if (empty($proposedValue)) - { - return array(); - } - if (is_string($proposedValue)) + $oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + if (is_string($proposedValue) && !empty($proposedValue)) { $proposedValue = trim("$proposedValue"); - $proposedValue = explode(',', $proposedValue); - $aValues = array(); - foreach($proposedValue as $sValue) - { - $sValue = trim($sValue); - $aValues[$sValue] = $sValue; - } - return $aValues; + $aValues = $this->FromStringToArray($proposedValue); + $oSet->SetValues($aValues); } - - if (is_array($proposedValue)) + elseif ($proposedValue instanceof ormSet) { - return $proposedValue; + $oSet = $proposedValue; } - throw new CoreUnexpectedValue("Wrong format"); - + return $oSet; } /** @@ -7776,14 +8916,25 @@ class AttributeSet extends AttributeDBFieldVoid return $this->MakeRealValue($sProposedValue, null); } + /** + * @return null|\ormSet + * @throws \CoreException + * @throws \Exception + */ public function GetNullValue() { - return null; + return new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); } public function IsNull($proposedValue) { - return empty($proposedValue); + if (empty($proposedValue)) + { + return true; + } + + /** @var \ormSet $proposedValue */ + return $proposedValue->Count() == 0; } /** @@ -7792,11 +8943,14 @@ class AttributeSet extends AttributeDBFieldVoid * @param $sValue * * @return string label corresponding to the given value (in plain text) - * @throws \CoreWarning * @throws \Exception */ public function GetValueLabel($sValue) { + if ($sValue instanceof ormSet) + { + $sValue = $sValue->GetValues(); + } if (is_array($sValue)) { return implode(', ', $sValue); @@ -7809,7 +8963,7 @@ class AttributeSet extends AttributeDBFieldVoid * @param null $oHostObj * * @return string - * @throws \CoreWarning + * @throws \Exception */ public function GetAsPlainText($sValue, $oHostObj = null) { @@ -7820,7 +8974,6 @@ class AttributeSet extends AttributeDBFieldVoid * @param $value * * @return string - * @throws \CoreWarning */ public function ScalarToSQL($value) { @@ -7828,6 +8981,10 @@ class AttributeSet extends AttributeDBFieldVoid { return ''; } + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); @@ -7842,34 +8999,96 @@ class AttributeSet extends AttributeDBFieldVoid * * @return string|null * - * @throws \CoreException * @throws \Exception */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } if (is_array($value)) { return implode(', ', $value); } return $value; } -} -class AttributeObjectAttCodeSet extends AttributeSet -{ - static public function ListExpectedParams() + public function GetMaxItems() { - return array_merge(parent::ListExpectedParams(), array('class')); + return $this->Get('max_items'); } - public function GetEditClass() + static public function GetFormFieldClass() { - return "ObjectAttcodeSet"; + return '\\Combodo\\iTop\\Form\\Field\\SetField'; + } +} + +class AttributeClassAttCodeSet extends AttributeSet +{ + public function __construct($sCode, array $aParams) + { + parent::__construct($sCode, $aParams); + $this->aCSSClasses[] = 'attribute-class-attcode-set'; + } + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array('class_field', 'attribute_definition_list')); } public function GetMaxSize() { - return 255; + return max(255, 15 * $this->GetMaxItems()); + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + if (isset($aArgs['this'])) + { + $oHostObj = $aArgs['this']; + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObj->Get($sTargetClass); + + $aAllowedAttributes = array(); + if (empty($sClass)) + { + $aAllAttributes = array(); + } + else + { + $aAllAttributes = MetaModel::GetAttributesList($sClass); + } + $sAttDefList = $this->Get('attribute_definition_list'); + if (!empty($sAttDefList)) + { + $aAllowedDefs = array(); + foreach(explode(',', $sAttDefList) as $sAttDefName) + { + $sAttDefName = trim($sAttDefName); + $aAllowedDefs[$sAttDefName] = $sAttDefName; + } + foreach($aAllAttributes as $sAttCode) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (isset($aAllowedDefs[get_class($oAttDef)])) + { + $aAllowedAttributes[$sAttCode] = MetaModel::GetLabel($sClass, $sAttCode); + } + } + } + else + { + foreach($aAllAttributes as $sAttCode) + { + $aAllowedAttributes[$sAttCode] = MetaModel::GetLabel($sClass, $sAttCode); + } + } + return $aAllowedAttributes; + } + + return null; } /** @@ -7878,54 +9097,997 @@ class AttributeObjectAttCodeSet extends AttributeSet * @param $proposedValue * @param \DBObject $oHostObj * + * @param bool $bIgnoreErrors + * * @return mixed + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \OQLException * @throws \Exception */ - public function MakeRealValue($proposedValue, $oHostObj) + public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false) { - $aAllowedAttributes = array(); - $sClass = ''; + $oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + $aArgs = array(); if (!empty($oHostObj)) { - $sTargetClass = $this->Get('class'); - $sClass = $oHostObj->Get($sTargetClass); - $aAllowedAttributes = MetaModel::GetAttributesList($sClass); + $aArgs['this'] = $oHostObj; } + $aAllowedAttributes = $this->GetAllowedValues($aArgs); + $aInvalidAttCodes = array(); if (is_string($proposedValue) && !empty($proposedValue)) { - $proposedValue = trim("$proposedValue"); - $proposedValue = explode(',', $proposedValue); + $aJsonFromWidget = json_decode($proposedValue, true); + if (is_null($aJsonFromWidget)) + { + $proposedValue = trim($proposedValue); + $aValues = array(); + foreach(explode(',', $proposedValue) as $sValue) + { + $sAttCode = trim($sValue); + if (empty($aAllowedAttributes) || isset($aAllowedAttributes[$sAttCode])) + { + $aValues[$sAttCode] = $sAttCode; + } + else + { + $aInvalidAttCodes[] = $sAttCode; + } + } + $oSet->SetValues($aValues); + } + } + elseif ($proposedValue instanceof ormSet) + { + $oSet = $proposedValue; + } + if (!empty($aInvalidAttCodes) && !$bIgnoreErrors) + { + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObj->Get($sTargetClass); + throw new CoreUnexpectedValue("The attribute(s) ".implode(', ', $aInvalidAttCodes)." are invalid for class {$sClass}"); + } + + return $oSet; + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \Exception + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } + if (is_array($value)) + { + if (!empty($oHostObject) && $bLocalize) + { + $sTargetClass = $this->Get('class_field'); + $sClass = $oHostObject->Get($sTargetClass); + + $aLocalizedValues = array(); + foreach($value as $sAttCode) + { + try + { + $aLocalizedValues[] = ''.MetaModel::GetLabel($sClass, $sAttCode).''; + } catch (Exception $e) + { + // Ignore bad values + } + } + $value = $aLocalizedValues; + } + $value = implode('', $value); + } + return ''.$value.''; + } +} + +class AttributeQueryAttCodeSet extends AttributeSet +{ + public function __construct($sCode, array $aParams) + { + parent::__construct($sCode, $aParams); + $this->aCSSClasses[] = 'attribute-query-attcode-set'; + } + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array('query_field')); + } + + protected function GetSQLCol($bFullSpec = false) + { + return "TEXT".CMDBSource::GetSqlStringColumnDefinition(); + } + + public function GetMaxSize() + { + return 65535; + } + + /** + * Get a class array indexed by alias + * @param $oHostObj + * + * @return array + */ + private function GetClassList($oHostObj) + { + try + { + $sQueryField = $this->Get('query_field'); + $sQuery = $oHostObj->Get($sQueryField); + if (empty($sQuery)) + { + return array(); + } + $oFilter = DBSearch::FromOQL($sQuery); + return $oFilter->GetSelectedClasses(); + + } catch (OQLException $e) + { + IssueLog::Warning($e->getMessage()); + } + return array(); + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + if (isset($aArgs['this'])) + { + $oHostObj = $aArgs['this']; + $aClasses = $this->GetClassList($oHostObj); + + $aAllowedAttributes = array(); + $aAllAttributes = array(); + if (is_array($aClasses)) + { + if (!empty($aClasses)) + { + ksort($aClasses); + foreach($aClasses as $sAlias => $sClass) + { + $aAttributes = MetaModel::GetAttributesList($sClass); + foreach($aAttributes as $sAttCode) + { + $aAllAttributes[] = array('alias' => $sAlias, 'class' => $sClass, 'att_code' => $sAttCode); + } + } + } + foreach($aAllAttributes as $aFullAttCode) + { + $sAttCode = $aFullAttCode['alias'].'.'.$aFullAttCode['att_code']; + $sClass = $aFullAttCode['class']; + $sLabel = $aFullAttCode['alias'].'.'.MetaModel::GetLabel($sClass, $aFullAttCode['att_code']); + $aAllowedAttributes[$sAttCode] = $sLabel; + } + } + else + { + $sClass = "$aClasses"; + $aAttributes = MetaModel::GetAttributesList($sClass); + foreach($aAttributes as $sAttCode) + { + $aAllowedAttributes[$sAttCode] = MetaModel::GetLabel($sClass, $sAttCode); + } + } + return $aAllowedAttributes; + } + + return null; + } + + /** + * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! + * + * @param $proposedValue + * @param \DBObject $oHostObj + * + * @param bool $bIgnoreErrors + * + * @return mixed + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \OQLException + * @throws \Exception + */ + public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false) + { + $oSet = new ormSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + $aArgs = array(); + if (!empty($oHostObj)) + { + $aArgs['this'] = $oHostObj; + } + $aAllowedAttributes = $this->GetAllowedValues($aArgs); + $aInvalidAttCodes = array(); + if (is_string($proposedValue) && !empty($proposedValue)) + { + $proposedValue = trim($proposedValue); $aValues = array(); - foreach($proposedValue as $sValue) + foreach(explode(',', $proposedValue) as $sValue) { $sAttCode = trim($sValue); - if (empty($aAllowedAttributes) || in_array($sAttCode, $aAllowedAttributes)) + if (empty($aAllowedAttributes) || isset($aAllowedAttributes[$sAttCode])) { $aValues[$sAttCode] = $sAttCode; } else { - throw new CoreUnexpectedValue("The attribute {$sAttCode} does not exist in class {$sClass}"); + $aInvalidAttCodes[] = $sAttCode; } } - return $aValues; + $oSet->SetValues($aValues); + } + elseif ($proposedValue instanceof ormSet) + { + $oSet = $proposedValue; + } + if (!empty($aInvalidAttCodes) && !$bIgnoreErrors) + { + throw new CoreUnexpectedValue("The attribute(s) ".implode(', ', $aInvalidAttCodes)." are invalid"); } - return $proposedValue; + return $oSet; } + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \Exception + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + + if ($value instanceof ormSet) + { + $value = $value->GetValues(); + } + if (is_array($value)) + { + if (!empty($oHostObject) && $bLocalize) + { + $aArgs['this'] = $oHostObject; + $aAllowedAttributes = $this->GetAllowedValues($aArgs); + + $aLocalizedValues = array(); + foreach($value as $sAttCode) + { + if (isset($aAllowedAttributes[$sAttCode])) + { + $aLocalizedValues[] = ''.$aAllowedAttributes[$sAttCode].''; + } + } + $value = $aLocalizedValues; + } + $value = implode('', $value); + } + + return ''.$value.''; + } } /** - * The attribute dedicated to the friendly name automatic attribute (not written) + * Multi value list of tags * - * @package iTopORM + * @see TagSetFieldData + * @since 2.6 N°931 tag fields */ +class AttributeTagSet extends AttributeSet +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_TAG_SET; + + public function __construct($sCode, array $aParams) + { + parent::__construct($sCode, $aParams); + $this->aCSSClasses[] = 'attribute-tag-set'; + } + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array('tag_code_max_len')); + } + + /** + * @param \ormTagSet $oValue + * + * @param $aArgs + * + * @return string JSON to be used in the itop.tagset_widget JQuery widget + * @throws \CoreException + * @throws \OQLException + */ + public function GetJsonForWidget($oValue, $aArgs = array()) + { + $aJson = array(); + + // possible_values + $aTagSetObjectData = $this->GetAllowedValues($aArgs); + $aTagSetKeyValData = array(); + foreach($aTagSetObjectData as $sTagCode => $sTagLabel) + { + $aTagSetKeyValData[] = [ + 'code' => $sTagCode, + 'label' => $sTagLabel, + ]; + } + $aJson['possible_values'] = $aTagSetKeyValData; + + if (is_null($oValue)) + { + $aJson['partial_values'] = array(); + $aJson['orig_value'] = array(); + } + else + { + $aJson['partial_values'] = $oValue->GetModified(); + $aJson['orig_value'] = array_merge($oValue->GetValues(), $oValue->GetModified()); + } + $aJson['added'] = array(); + $aJson['removed'] = array(); + + $iMaxTags = $this->GetMaxItems(); + $aJson['max_items_allowed'] = $iMaxTags; + + return json_encode($aJson); + } + + public function FromStringToArray($proposedValue) + { + $aValues = array(); + if (!empty($proposedValue)) + { + foreach(explode(' ', $proposedValue) as $sCode) + { + $sValue = trim($sCode); + $aValues[] = $sValue; + } + } + return $aValues; + } + + /** + * Extract all existing tags from a string and ignore bad tags + * + * @param $sValue + * @param bool $bNoLimit : don't apply the maximum tag limit + * + * @return \ormTagSet + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function GetExistingTagsFromString($sValue, $bNoLimit = false) + { + $aTagCodes = $this->FromStringToArray("$sValue"); + $sAttCode = $this->GetCode(); + $sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode); + if ($bNoLimit) + { + $oTagSet = new ormTagSet($sClass, $sAttCode, 0); + } + else + { + $oTagSet = new ormTagSet($sClass, $sAttCode, $this->GetMaxItems()); + } + $aGoodTags = array(); + foreach($aTagCodes as $sTagCode) + { + if ($sTagCode === '') + { + continue; + } + if ($oTagSet->IsValidTag($sTagCode)) + { + $aGoodTags[] = $sTagCode; + if (!$bNoLimit && (count($aGoodTags) === $this->GetMaxItems())) + { + // extra and bad tags are ignored + break; + } + } + } + $oTagSet->SetValues($aGoodTags); + + return $oTagSet; + } + + public function GetTagCodeMaxLength() + { + return $this->Get('tag_code_max_len'); + } + + public function GetEditValue($value, $oHostObj = null) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValues(); + + return implode(' ', $aValues); + } + + return ''; + } + + public function GetMaxSize() + { + return max(255, ($this->GetMaxItems() * $this->GetTagCodeMaxLength()) + 1); + } + + public function Equals($val1, $val2) + { + if (($val1 instanceof ormTagSet) && ($val2 instanceof ormTagSet)) + { + return $val1->Equals($val2); + } + + return ($val1 == $val2); + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $sAttCode = $this->GetCode(); + $sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $sAttCode); + $aAllowedTags = TagSetFieldData::GetAllowedValues($sClass, $sAttCode); + $aAllowedValues = array(); + foreach($aAllowedTags as $oAllowedTag) + { + $aAllowedValues[$oAllowedTag->Get('code')] = $oAllowedTag->Get('label'); + } + + return $aAllowedValues; + } + + /** + * @param array $aCols + * @param string $sPrefix + * + * @return mixed + * @throws \CoreException + * @throws \Exception + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + $sValue = $aCols["$sPrefix"]; + + return $this->GetExistingTagsFromString($sValue); + } + + /** + * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! + * + * @param $proposedValue + * @param $oHostObj + * + * @param bool $bIgnoreErrors + * + * @return mixed + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function MakeRealValue($proposedValue, $oHostObj, $bIgnoreErrors = false) + { + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + if (is_string($proposedValue) && !empty($proposedValue)) + { + $sJsonFromWidget = json_decode($proposedValue, true); + if (is_null($sJsonFromWidget)) + { + $proposedValue = trim("$proposedValue"); + $aTagCodes = $this->FromStringToArray($proposedValue); + $oTagSet->SetValues($aTagCodes); + } + } + elseif ($proposedValue instanceof ormTagSet) + { + $oTagSet = $proposedValue; + } + + return $oTagSet; + } + + /** + * Get the value from a given string (plain text, CSV import) + * + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return mixed null if no match could be found + * @throws \Exception + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + if (is_null($sSepItem) || empty($sSepItem)) + { + $sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator'); + } + if ($bLocalizedValue && !empty($sProposedValue)) + { + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), + $this->GetCode(), $this->GetMaxItems()); + $aLabels = explode($sSepItem, $sProposedValue); + $aCodes = array(); + foreach($aLabels as $sTagLabel) + { + if (!empty($sTagLabel)) + { + $aCodes[] = $oTagSet->GetTagFromLabel($sTagLabel); + } + } + $sProposedValue = implode(' ', $aCodes); + } + + return $this->MakeRealValue($sProposedValue, null); + } + + public function GetNullValue() + { + return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems()); + } + + public function IsNull($proposedValue) + { + if (is_null($proposedValue)) + { + return true; + } + + /** @var \ormTagSet $proposedValue */ + return count($proposedValue->GetValues()) == 0; + } + + /** + * To be overloaded for localized enums + * + * @param $sValue + * + * @return string label corresponding to the given value (in plain text) + * @throws \CoreWarning + * @throws \Exception + */ + public function GetValueLabel($sValue) + { + if (empty($sValue)) + { + return ''; + } + if (is_string($sValue)) + { + $sValue = $this->GetExistingTagsFromString($sValue); + } + if ($sValue instanceof ormTagSet) + { + $aValues = $sValue->GetLabels(); + + return implode(', ', $aValues); + } + throw new CoreWarning('Expected the attribute value to be a TagSet', array( + 'found_type' => gettype($sValue), + 'value' => $sValue, + 'class' => $this->GetHostClass(), + 'attribute' => $this->GetCode() + )); + } + + /** + * @param $value + * + * @return string + * @throws \CoreWarning + */ + public function ScalarToSQL($value) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValues(); + + return implode(' ', $aValues); + } + throw new CoreWarning('Expected the attribute value to be a TagSet', array( + 'found_type' => gettype($value), + 'value' => $value, + 'class' => $this->GetHostClass(), + 'attribute' => $this->GetCode() + )); + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + * @throws \Exception + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if ($value instanceof ormTagSet) + { + if ($bLocalize) + { + $aValues = $value->GetTags(); + } + else + { + $aValues = $value->GetValues(); + } + if (empty($aValues)) + { + return ''; + } + + return $this->GenerateViewHtmlForValues($aValues); + } + if (is_string($value)) + { + try + { + $oValue = $this->MakeRealValue($value, $oHostObject); + + return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); + } catch (Exception $e) + { + // unknown tags are present display the code instead + } + $aTagCodes = $this->FromStringToArray($value); + $aValues = array(); + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), + $this->GetCode(), $this->GetMaxItems()); + foreach($aTagCodes as $sTagCode) + { + try + { + $oTagSet->Add($sTagCode); + } catch (Exception $e) + { + $aValues[] = $sTagCode; + } + } + $sHTML = ''; + if (!empty($aValues)) + { + $sHTML .= $this->GenerateViewHtmlForValues($aValues, 'attribute-set-item-undefined'); + } + $aValues = $oTagSet->GetTags(); + if (!empty($aValues)) + { + $sHTML .= $this->GenerateViewHtmlForValues($aValues); + } + + return $sHTML; + } + + return parent::GetAsHTML($value, $oHostObject, $bLocalize); + } + + // Do not display friendly names in the history of change + public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null) + { + $sResult = Dict::Format('Change:AttName_Changed', $this->GetLabel()).", "; + + $aNewValues = $this->FromStringToArray($sNewValue); + $aOldValues = $this->FromStringToArray($sOldValue); + + $aDelta['removed'] = array_diff($aOldValues, $aNewValues); + $aDelta['added'] = array_diff($aNewValues, $aOldValues); + + $aAllowedTags = TagSetFieldData::GetAllowedValues(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + + if (!empty($aDelta['removed'])) + { + $aRemoved = array(); + foreach($aDelta['removed'] as $idx => $sTagCode) + { + if (empty($sTagCode)) {continue;} + $sTagLabel = $sTagCode; + foreach($aAllowedTags as $oTag) + { + if ($sTagCode === $oTag->Get('code')) + { + $sTagLabel = $oTag->Get('label'); + } + } + $aRemoved[] = $sTagLabel; + } + + $sRemoved = $this->GenerateViewHtmlForValues($aRemoved, 'history-removed'); + if (!empty($sRemoved)) + { + $sResult .= Dict::Format('Change:LinkSet:Removed', $sRemoved); + } + } + + if (!empty($aDelta['added'])) + { + if (!empty($sRemoved)) + { + $sResult .= ', '; + } + + $aAdded = array(); + foreach($aDelta['added'] as $idx => $sTagCode) + { + if (empty($sTagCode)) {continue;} + $sTagLabel = $sTagCode; + foreach($aAllowedTags as $oTag) + { + if ($sTagCode === $oTag->Get('code')) + { + $sTagLabel = $oTag->Get('label'); + } + } + $aAdded[] = $sTagLabel; + } + + $sAdded = $this->GenerateViewHtmlForValues($aAdded, 'history-added'); + if (!empty($sAdded)) + { + $sResult .= Dict::Format('Change:LinkSet:Added', $sAdded); + } + } + + return $sResult; + } + + /** + * HTML representation of a list of tags (read-only) + * accept a list of strings or a list of TagSetFieldData + * + * @param array $aValues + * @param string $sCssClass + * + * @return string + * @throws \CoreException + */ + private function GenerateViewHtmlForValues($aValues, $sCssClass = '') + { + if (empty($aValues)) {return '';} + $sHtml = ''; + foreach($aValues as $oTag) + { + if ($oTag instanceof TagSetFieldData) + { + $sClass = MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()); + $sAttCode = $this->GetCode(); + $sTagCode = $oTag->Get('code'); + $sTagLabel = $oTag->Get('label'); + $sTagDescription = $oTag->Get('description'); + $oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'"); + $oAppContext = new ApplicationContext(); + $sContext = $oAppContext->GetForLink(); + $sUIPage = cmdbAbstractObject::ComputeStandardUIPage($oFilter->GetClass()); + $sFilter = urlencode($oFilter->serialize()); + $sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}"; + + $sHtml .= ''.$sTagLabel.''; + } + else + { + $sHtml .= ''.$oTag.''; + } + } + $sHtml .= ''; + + return $sHtml; + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * + */ + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + $sRes = "\n"; + if ($bLocalize) + { + $aValues = $value->GetLabels(); + } + else + { + $aValues = $value->GetValues(); + } + if (!empty($aValuess)) + { + $sRes .= ''.implode('', $aValues).''; + } + $sRes .= "\n"; + } + else + { + $sRes = ''; + } + + return $sRes; + } + + /** + * @param $value + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return mixed|string + * @throws \CoreException + */ + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { + $sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator'); + if (is_object($value) && ($value instanceof ormTagSet)) + { + if ($bLocalize) + { + $aValues = $value->GetLabels(); + } + else + { + $aValues = $value->GetValues(); + } + $sRes = implode($sSepItem, $aValues); + } + else + { + $sRes = ''; + } + + return "{$sTextQualifier}{$sRes}{$sTextQualifier}"; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text representation', + 'html' => 'HTML representation (unordered list)', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param mixed $value The current value of the field + * @param string $sVerb The verb specifying the representation of the value + * @param DBObject $oHostObject The object + * @param bool $bLocalize Whether or not to localize the value + * + * @return string + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + if ($bLocalize) + { + $aValues = $value->GetLabels(); + $sSep = ', '; + } + else + { + $aValues = $value->GetValues(); + $sSep = ' '; + } + + switch ($sVerb) + { + case '': + return implode($sSep, $aValues); + + case 'html': + return '
    • '.implode("
    • ", $aValues).'
    '; + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + throw new CoreUnexpectedValue("Bad value '$value' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param \ormTagSet $value + * + * @return array + */ + public function GetForJSON($value) + { + $aRet = array(); + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aRet = $value->GetValues(); + } + + return $aRet; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param $json + * + * @return \ormTagSet + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function FromJSONToValue($json) + { + $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode(), $this->GetMaxItems()); + $oSet->SetValues($json); + + return $oSet; + } + + /** + * The part of the current attribute in the object's signature, for the supplied value + * + * @param mixed $value The value of this attribute for the object + * + * @return string The "signature" for this field/attribute + */ + public function Fingerprint($value) + { + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValues(); + + return implode(' ', $aValues); + } + + return parent::Fingerprint($value); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\TagSetField'; + } +} /** - * The attribute dedicated to the friendly name automatic attribute (not written) + * The attribute dedicated to the friendly name automatic attribute (not written) * - * @package iTopORM + * @package iTopORM */ class AttributeFriendlyName extends AttributeDefinition { @@ -7943,13 +10105,30 @@ class AttributeFriendlyName extends AttributeDefinition } - public function GetEditClass() {return "";} + public function GetEditClass() + { + return ""; + } - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} + public function GetValuesDef() + { + return null; + } - static public function IsScalar() {return true;} - public function IsNullAllowed() {return false;} + public function GetPrerequisiteAttributes($sClass = null) + { + return $this->GetOptional("depends_on", array()); + } + + static public function IsScalar() + { + return true; + } + + public function IsNullAllowed() + { + return false; + } public function GetSQLExpressions($sPrefix = '') { @@ -7957,10 +10136,15 @@ class AttributeFriendlyName extends AttributeDefinition { $sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property } + return array('' => $sPrefix); } - static public function IsBasedOnOQLExpression() {return true;} + static public function IsBasedOnOQLExpression() + { + return true; + } + public function GetOQLExpression() { return MetaModel::GetNameExpression($this->GetHostClass()); @@ -7973,8 +10157,10 @@ class AttributeFriendlyName extends AttributeDefinition { $sLabel = Dict::S('Core:FriendlyName-Label'); } + return $sLabel; } + public function GetDescription($sDefault = null) { $sLabel = parent::GetDescription(''); @@ -7982,12 +10168,14 @@ class AttributeFriendlyName extends AttributeDefinition { $sLabel = Dict::S('Core:FriendlyName-Description'); } + return $sLabel; - } + } public function FromSQLToValue($aCols, $sPrefix = '') { - $sValue = $aCols[$sPrefix]; + $sValue = $aCols[$sPrefix]; + return $sValue; } @@ -7995,6 +10183,7 @@ class AttributeFriendlyName extends AttributeDefinition { return false; } + public function IsMagic() { return true; @@ -8009,6 +10198,7 @@ class AttributeFriendlyName extends AttributeDefinition { $this->m_sValue = $sValue; } + public function GetDefaultValue(DBObject $oHostObject = null) { return $this->m_sValue; @@ -8019,11 +10209,14 @@ class AttributeFriendlyName extends AttributeDefinition return Str::pure2html((string)$sValue); } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; } @@ -8058,7 +10251,7 @@ class AttributeFriendlyName extends AttributeDefinition public function GetBasicFilterOperators() { - return array("="=>"equals", "!="=>"differs from"); + return array("=" => "equals", "!=" => "differs from"); } public function GetBasicFilterLooseOperator() @@ -8071,20 +10264,23 @@ class AttributeFriendlyName extends AttributeDefinition $sQValue = CMDBSource::Quote($value); switch ($sOpCode) { - case '=': - case '!=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'Contains': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); - case 'NotLike': - return $this->GetSQLExpr()." NOT LIKE $sQValue"; - case 'Like': - default: - return $this->GetSQLExpr()." LIKE $sQValue"; + case '=': + case '!=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'Contains': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); + case 'NotLike': + return $this->GetSQLExpr()." NOT LIKE $sQValue"; + case 'Like': + default: + return $this->GetSQLExpr()." LIKE $sQValue"; } } - - public function IsPartOfFingerprint() { return false; } + + public function IsPartOfFingerprint() + { + return false; + } } /** @@ -8094,7 +10290,7 @@ class AttributeFriendlyName extends AttributeDefinition * - 'n', where n is a positive integer value giving the minimum count of items upstream * - 'n%', where n is a positive integer value, giving the minimum as a percentage of the total count of items upstream * - * @package iTopORM + * @package iTopORM */ class AttributeRedundancySettings extends AttributeDBField { @@ -8102,13 +10298,34 @@ class AttributeRedundancySettings extends AttributeDBField static public function ListExpectedParams() { - return array('sql', 'relation_code', 'from_class', 'neighbour_id', 'enabled', 'enabled_mode', 'min_up', 'min_up_type', 'min_up_mode'); + return array( + 'sql', + 'relation_code', + 'from_class', + 'neighbour_id', + 'enabled', + 'enabled_mode', + 'min_up', + 'min_up_type', + 'min_up_mode' + ); } - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return array();} + public function GetValuesDef() + { + return null; + } + + public function GetPrerequisiteAttributes($sClass = null) + { + return array(); + } + + public function GetEditClass() + { + return "RedundancySetting"; + } - public function GetEditClass() {return "RedundancySetting";} protected function GetSQLCol($bFullSpec = false) { return "VARCHAR(20)" @@ -8134,34 +10351,39 @@ class AttributeRedundancySettings extends AttributeDBField { if ($this->Get('min_up_type') == 'count') { - $sRet = (string) $this->Get('min_up'); + $sRet = (string)$this->Get('min_up'); } else // percent { $sRet = $this->Get('min_up').'%'; } } + return $sRet; } public function IsNullAllowed() { return false; - } + } public function GetNullValue() { return ''; - } + } public function IsNull($proposedValue) { return ($proposedValue == ''); - } + } public function MakeRealValue($proposedValue, $oHostObj) { - if (is_null($proposedValue)) return ''; + if (is_null($proposedValue)) + { + return ''; + } + return (string)$proposedValue; } @@ -8169,14 +10391,21 @@ class AttributeRedundancySettings extends AttributeDBField { if (!is_string($value)) { - throw new CoreException('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + throw new CoreException('Expected the attribute value to be a string', array( + 'found_type' => gettype($value), + 'value' => $value, + 'class' => $this->GetHostClass(), + 'attribute' => $this->GetCode() + )); } + return $value; } public function GetRelationQueryData() { - foreach (MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'), false) as $sDummy => $aQueryInfo) + foreach(MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'), + false) as $sDummy => $aQueryInfo) { if ($aQueryInfo['sFromClass'] == $this->Get('from_class')) { @@ -8186,6 +10415,7 @@ class AttributeRedundancySettings extends AttributeDBField } } } + return array(); } @@ -8211,6 +10441,7 @@ class AttributeRedundancySettings extends AttributeDBField // Browse the hierarchy again, accepting default (english) translations $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, $sDefault, false); } + return $sLabel; } @@ -8229,20 +10460,25 @@ class AttributeRedundancySettings extends AttributeDBField { $sCurrentOption = $this->GetCurrentOption($sValue); $sClass = $oHostObject ? get_class($oHostObject) : $this->m_sHostClass; - return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), MetaModel::GetName($sClass)); + + return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), + MetaModel::GetName($sClass)); } - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; } /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ + * Helper to interpret the value, given the current settings and string representation of the attribute + */ public function IsEnabled($sValue) { if ($this->get('enabled_mode') == 'fixed') @@ -8253,12 +10489,13 @@ class AttributeRedundancySettings extends AttributeDBField { $bRet = ($sValue != 'disabled'); } + return $bRet; } /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ + * Helper to interpret the value, given the current settings and string representation of the attribute + */ public function GetMinUpType($sValue) { if ($this->get('min_up_mode') == 'fixed') @@ -8273,17 +10510,18 @@ class AttributeRedundancySettings extends AttributeDBField $sRet = 'percent'; } } + return $sRet; } /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ + * Helper to interpret the value, given the current settings and string representation of the attribute + */ public function GetMinUpValue($sValue) { if ($this->get('min_up_mode') == 'fixed') { - $iRet = (int) $this->Get('min_up'); + $iRet = (int)$this->Get('min_up'); } else { @@ -8292,14 +10530,15 @@ class AttributeRedundancySettings extends AttributeDBField { $sRefValue = substr(trim($sValue), 0, -1); } - $iRet = (int) trim($sRefValue); + $iRet = (int)trim($sRefValue); } + return $iRet; } /** * Helper to determine if the redundancy can be viewed/edited by the end-user - */ + */ public function IsVisible() { $bRet = false; @@ -8311,6 +10550,7 @@ class AttributeRedundancySettings extends AttributeDBField { $bRet = true; } + return $bRet; } @@ -8320,12 +10560,13 @@ class AttributeRedundancySettings extends AttributeDBField { return false; } + return true; } /** * Returns an HTML form that can be read by ReadValueFromPostedForm - */ + */ public function GetDisplayForm($sCurrentValue, $oPage, $bEditMode = false, $sFormPrefix = '') { $sRet = ''; @@ -8343,9 +10584,11 @@ class AttributeRedundancySettings extends AttributeDBField { $bSelected = ($sUserOption == $sCurrentOption); $sRet .= '
    '; - $sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption, $bSelected); + $sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption, + $bSelected); $sRet .= '
    '; } + return $sRet; } @@ -8355,7 +10598,7 @@ class AttributeRedundancySettings extends AttributeDBField /** * Depending on the xxx_mode parameters, build the list of options that are allowed to the end-user - */ + */ protected function GetUserOptions($sValue) { $aRet = array(); @@ -8363,7 +10606,7 @@ class AttributeRedundancySettings extends AttributeDBField { $aRet[] = self::USER_OPTION_DISABLED; } - + if ($this->Get('min_up_mode') == 'user') { $aRet[] = self::USER_OPTION_ENABLED_COUNT; @@ -8380,12 +10623,13 @@ class AttributeRedundancySettings extends AttributeDBField $aRet[] = self::USER_OPTION_ENABLED_PERCENT; } } + return $aRet; } /** - * Convert the string representation into one of the existing options - */ + * Convert the string representation into one of the existing options + */ protected function GetCurrentOption($sValue) { $sRet = self::USER_OPTION_DISABLED; @@ -8400,6 +10644,7 @@ class AttributeRedundancySettings extends AttributeDBField $sRet = self::USER_OPTION_ENABLED_PERCENT; } } + return $sRet; } @@ -8418,8 +10663,9 @@ class AttributeRedundancySettings extends AttributeDBField * @throws \DictExceptionMissingString * @throws \Exception */ - protected function GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true) - { + protected function GetDisplayOption( + $sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true + ) { $sRet = ''; $iCurrentValue = $this->GetMinUpValue($sCurrentValue); @@ -8429,41 +10675,42 @@ class AttributeRedundancySettings extends AttributeDBField $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); switch ($sUserOption) { - case self::USER_OPTION_DISABLED: - $sValue = ''; // Empty placeholder - break; - - case self::USER_OPTION_ENABLED_COUNT: - if ($bEditMode) - { - $sName = $sHtmlNamesPrefix.'_min_up_count'; - $sEditValue = $bSelected ? $iCurrentValue : ''; - $sValue = ''; - // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) - $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); - } - else - { - $sValue = $iCurrentValue; - } - break; - - case self::USER_OPTION_ENABLED_PERCENT: - if ($bEditMode) - { - $sName = $sHtmlNamesPrefix.'_min_up_percent'; - $sEditValue = $bSelected ? $iCurrentValue : ''; - $sValue = ''; - // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) - $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); - } - else - { - $sValue = $iCurrentValue; - } - break; + case self::USER_OPTION_DISABLED: + $sValue = ''; // Empty placeholder + break; + + case self::USER_OPTION_ENABLED_COUNT: + if ($bEditMode) + { + $sName = $sHtmlNamesPrefix.'_min_up_count'; + $sEditValue = $bSelected ? $iCurrentValue : ''; + $sValue = ''; + // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) + $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); + } + else + { + $sValue = $iCurrentValue; + } + break; + + case self::USER_OPTION_ENABLED_PERCENT: + if ($bEditMode) + { + $sName = $sHtmlNamesPrefix.'_min_up_percent'; + $sEditValue = $bSelected ? $iCurrentValue : ''; + $sValue = ''; + // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) + $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); + } + else + { + $sValue = $iCurrentValue; + } + break; } - $sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue, MetaModel::GetName($this->GetHostClass())); + $sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue, + MetaModel::GetName($this->GetHostClass())); $sOptionName = $sHtmlNamesPrefix.'_user_option'; $sOptionId = $sOptionName.'_'.$sUserOption; @@ -8475,37 +10722,40 @@ class AttributeRedundancySettings extends AttributeDBField // Read-only: display only the currently selected option if ($bSelected) { - $sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue, MetaModel::GetName($this->GetHostClass())); + $sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue, + MetaModel::GetName($this->GetHostClass())); } } + return $sRet; } /** - * Makes the string representation out of the values given by the form defined in GetDisplayForm - */ + * Makes the string representation out of the values given by the form defined in GetDisplayForm + */ public function ReadValueFromPostedForm($sFormPrefix) { $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); - $iMinUpCount = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data'); - $iMinUpPercent = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data'); + $iMinUpCount = (int)utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data'); + $iMinUpPercent = (int)utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data'); $sSelectedOption = utils::ReadPostedParam($sHtmlNamesPrefix.'_user_option', null, 'raw_data'); switch ($sSelectedOption) { - case self::USER_OPTION_ENABLED_COUNT: - $sRet = $iMinUpCount; - break; + case self::USER_OPTION_ENABLED_COUNT: + $sRet = $iMinUpCount; + break; - case self::USER_OPTION_ENABLED_PERCENT: - $sRet = $iMinUpPercent.'%'; - break; + case self::USER_OPTION_ENABLED_PERCENT: + $sRet = $iMinUpPercent.'%'; + break; - case self::USER_OPTION_DISABLED: - default: - $sRet = 'disabled'; - break; + case self::USER_OPTION_DISABLED: + default: + $sRet = 'disabled'; + break; } + return $sRet; } } @@ -8524,22 +10774,45 @@ class AttributeCustomFields extends AttributeDefinition return array_merge(parent::ListExpectedParams(), array("handler_class")); } - public function GetEditClass() {return "CustomFields";} - public function IsWritable() {return true;} - static public function LoadFromDB() {return false;} // See ReadValue... + public function GetEditClass() + { + return "CustomFields"; + } + + public function IsWritable() + { + return true; + } + + static public function LoadFromDB() + { + return false; + } // See ReadValue... public function GetDefaultValue(DBObject $oHostObject = null) { return new ormCustomFieldsValue($oHostObject, $this->GetCode()); } - public function GetBasicFilterOperators() {return array();} - public function GetBasicFilterLooseOperator() {return '';} - public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} + public function GetBasicFilterOperators() + { + return array(); + } + + public function GetBasicFilterLooseOperator() + { + return ''; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return ''; + } /** * @param DBObject $oHostObject * @param array|null $aValues + * * @return CustomFieldsHandler */ public function GetHandler($aValues = null) @@ -8550,12 +10823,14 @@ class AttributeCustomFields extends AttributeDefinition { $oHandler->SetCurrentValues($aValues); } + return $oHandler; } public function GetPrerequisiteAttributes($sClass = null) { $sHandlerClass = $this->Get('handler_class'); + return $sHandlerClass::GetPrerequisiteAttributes($sClass); } @@ -8569,7 +10844,9 @@ class AttributeCustomFields extends AttributeDefinition */ public function ReadValueFromPostedForm($oHostObject, $sFormPrefix) { - $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true); + $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), + true); + return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData); } @@ -8582,6 +10859,7 @@ class AttributeCustomFields extends AttributeDefinition elseif (is_string($proposedValue)) { $aValues = json_decode($proposedValue, true); + return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); } elseif (is_array($proposedValue)) @@ -8603,7 +10881,8 @@ class AttributeCustomFields extends AttributeDefinition /** * Override to build the relevant form field * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behaves more like a Prepare. + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the + * $oFormField is passed, MakeFormField behaves more like a Prepare. */ public function MakeFormField(DBObject $oObject, $oFormField = null) { @@ -8614,13 +10893,14 @@ class AttributeCustomFields extends AttributeDefinition $oFormField->SetForm($this->GetForm($oObject)); } parent::MakeFormField($oObject, $oFormField); - + return $oFormField; } /** * @param DBObject $oHostObject * @param null $sFormPrefix + * * @return Combodo\iTop\Form\Form * @throws \Exception */ @@ -8633,8 +10913,7 @@ class AttributeCustomFields extends AttributeDefinition $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode(); $oHandler->BuildForm($oHostObject, $sFormId); $oForm = $oHandler->GetForm(); - } - catch (Exception $e) + } catch (Exception $e) { $oForm = new \Combodo\iTop\Form\Form(''); $oField = new \Combodo\iTop\Form\Field\LabelField(''); @@ -8642,12 +10921,16 @@ class AttributeCustomFields extends AttributeDefinition $oForm->AddField($oField); $oForm->Finalize(); } + return $oForm; } /** - * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false and LoadInObject returns true + * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false + * and LoadInObject returns true + * * @param $oHostObject + * * @return ormCustomFieldsValue */ public function ReadValue($oHostObject) @@ -8657,17 +10940,18 @@ class AttributeCustomFields extends AttributeDefinition $oHandler = $this->GetHandler(); $aValues = $oHandler->ReadValues($oHostObject); $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); - } - catch (Exception $e) + } catch (Exception $e) { $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode()); } + return $oRet; } /** * Record the data (currently in the processing of recording the host object) * It is assumed that the data has been checked prior to calling Write() + * * @param DBObject $oHostObject * @param ormCustomFieldsValue|null $oValue (null is the default value) */ @@ -8686,24 +10970,30 @@ class AttributeCustomFields extends AttributeDefinition $oForm = $oHandler->GetForm(); $aValues = $oForm->GetCurrentValues(); } + return $oHandler->WriteValues($oHostObject, $aValues); } /** * The part of the current attribute in the object's signature, for the supplied value + * * @param ormCustomFieldsValue $value The value of this attribute for the object + * * @return string The "signature" for this field/attribute */ public function Fingerprint($value) { $oHandler = $this->GetHandler($value->GetValues()); + return $oHandler->GetValueFingerprint(); } /** * Check the validity of the data + * * @param DBObject $oHostObject * @param $value + * * @return bool|string true or error message */ public function CheckValue(DBObject $oHostObject, $value) @@ -8721,23 +11011,25 @@ class AttributeCustomFields extends AttributeDefinition else { $aMessages = array(); - foreach ($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages) + foreach($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages) { $aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages); } $ret = 'Invalid value: '.implode(', ', $aMessages); } - } - catch (Exception $e) + } catch (Exception $e) { $ret = $e->getMessage(); } + return $ret; } /** * Cleanup data upon object deletion (object id still available here) + * * @param DBObject $oHostObject + * * @return * @throws \CoreException */ @@ -8745,6 +11037,7 @@ class AttributeCustomFields extends AttributeDefinition { $oValue = $oHostObject->Get($this->GetCode()); $oHandler = $this->GetHandler($oValue->GetValues()); + return $oHandler->DeleteValues($oHostObject); } @@ -8753,11 +11046,11 @@ class AttributeCustomFields extends AttributeDefinition try { $sRet = $value->GetAsHTML($bLocalize); - } - catch (Exception $e) + } catch (Exception $e) { $sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); } + return $sRet; } @@ -8766,27 +11059,29 @@ class AttributeCustomFields extends AttributeDefinition try { $sRet = $value->GetAsXML($bLocalize); - } - catch (Exception $e) + } catch (Exception $e) { $sRet = Str::pure2xml('Custom field error: '.$e->getMessage()); } + return $sRet; } - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, + $bConvertToPlainText = false + ) { try { $sRet = $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText); - } - catch (Exception $e) + } catch (Exception $e) { $sFrom = array("\r\n", $sTextQualifier); $sTo = array("\n", $sTextQualifier.$sTextQualifier); $sEscaped = str_replace($sFrom, $sTo, 'Custom field error: '.$e->getMessage()); $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; } + return $sRet; } @@ -8796,6 +11091,7 @@ class AttributeCustomFields extends AttributeDefinition public function EnumTemplateVerbs() { $sHandlerClass = $this->Get('handler_class'); + return $sHandlerClass::EnumTemplateVerbs(); } @@ -8814,16 +11110,18 @@ class AttributeCustomFields extends AttributeDefinition try { $sRet = $value->GetForTemplate($sVerb, $bLocalize); - } - catch (Exception $e) + } catch (Exception $e) { $sRet = 'Custom field error: '.$e->getMessage(); } + return $sRet; } - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { + public function MakeValueFromString( + $sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, + $sAttributeQualifier = null + ) { return null; } @@ -8858,11 +11156,11 @@ class AttributeCustomFields extends AttributeDefinition try { $bEquals = $val1->Equals($val2); - } - catch (Exception $e) + } catch (Exception $e) { $bEquals = false; } + return $bEquals; } } @@ -8871,45 +11169,63 @@ class AttributeArchiveFlag extends AttributeBoolean { public function __construct($sCode) { - parent::__construct($sCode, array("allowed_values" => null, "sql" => $sCode, "default_value" => false, "is_null_allowed" => false, "depends_on" => array())); + parent::__construct($sCode, array( + "allowed_values" => null, + "sql" => $sCode, + "default_value" => false, + "is_null_allowed" => false, + "depends_on" => array() + )); } + public function RequiresIndex() { return true; } + public function CopyOnAllTables() { return true; } + public function IsWritable() { return false; } + public function IsMagic() { return true; } + public function GetLabel($sDefault = null) { $sDefault = Dict::S('Core:AttributeArchiveFlag/Label', $sDefault); + return parent::GetLabel($sDefault); } + public function GetDescription($sDefault = null) { $sDefault = Dict::S('Core:AttributeArchiveFlag/Label+', $sDefault); + return parent::GetDescription($sDefault); } } + class AttributeArchiveDate extends AttributeDate { public function GetLabel($sDefault = null) { $sDefault = Dict::S('Core:AttributeArchiveDate/Label', $sDefault); + return parent::GetLabel($sDefault); } + public function GetDescription($sDefault = null) { $sDefault = Dict::S('Core:AttributeArchiveDate/Label+', $sDefault); + return parent::GetDescription($sDefault); } } @@ -8918,23 +11234,41 @@ class AttributeObsolescenceFlag extends AttributeBoolean { public function __construct($sCode) { - parent::__construct($sCode, array("allowed_values"=>null, "sql"=>$sCode, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())); + parent::__construct($sCode, array( + "allowed_values" => null, + "sql" => $sCode, + "default_value" => "", + "is_null_allowed" => false, + "depends_on" => array() + )); } + public function IsWritable() { return false; } + public function IsMagic() { return true; } - static public function IsBasedOnDBColumns() {return false;} + static public function IsBasedOnDBColumns() + { + return false; + } + /** - * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) + * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via + * GetOQLExpression) + * * @return bool */ - static public function IsBasedOnOQLExpression() {return true;} + static public function IsBasedOnOQLExpression() + { + return true; + } + public function GetOQLExpression() { return MetaModel::GetObsolescenceExpression($this->GetHostClass()); @@ -8944,32 +11278,68 @@ class AttributeObsolescenceFlag extends AttributeBoolean { return array(); } - public function GetSQLColumns($bFullSpec = false) {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) - public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) - public function GetEditClass() {return "";} + public function GetSQLColumns($bFullSpec = false) + { + return array(); + } // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} + public function GetSQLValues($value) + { + return array(); + } // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) + + public function GetEditClass() + { + return ""; + } + + public function GetValuesDef() + { + return null; + } + + public function GetPrerequisiteAttributes($sClass = null) + { + return $this->GetOptional("depends_on", array()); + } + + public function IsDirectField() + { + return true; + } + + static public function IsScalar() + { + return true; + } - public function IsDirectField() {return true;} - static public function IsScalar() {return true;} public function GetSQLExpr() { return null; } - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} - public function IsNullAllowed() {return false;} + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->MakeRealValue("", $oHostObject); + } + + public function IsNullAllowed() + { + return false; + } public function GetLabel($sDefault = null) { $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label', $sDefault); + return parent::GetLabel($sDefault); } + public function GetDescription($sDefault = null) { $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label+', $sDefault); + return parent::GetDescription($sDefault); } } @@ -8979,11 +11349,14 @@ class AttributeObsolescenceDate extends AttributeDate public function GetLabel($sDefault = null) { $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label', $sDefault); + return parent::GetLabel($sDefault); } + public function GetDescription($sDefault = null) { $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label+', $sDefault); + return parent::GetDescription($sDefault); } } diff --git a/core/autoload.php b/core/autoload.php index c1d674e59d..15eb719a15 100644 --- a/core/autoload.php +++ b/core/autoload.php @@ -29,7 +29,7 @@ MetaModel::IncludeModule('core/action.class.inc.php'); MetaModel::IncludeModule('core/trigger.class.inc.php'); MetaModel::IncludeModule('core/bulkexport.class.inc.php'); MetaModel::IncludeModule('core/ownershiplock.class.inc.php'); -MetaModel::IncludeModule('core/tagfield.class.inc.php'); +MetaModel::IncludeModule('core/tagsetfield.class.inc.php'); MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php'); MetaModel::IncludeModule('core/backgroundtask.class.inc.php'); MetaModel::IncludeModule('core/inlineimage.class.inc.php'); diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index 3588dd931d..c4622b1364 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -242,6 +242,64 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute return $sResult; } } + +/** + * Record the modification of a tag set attribute + * + * @package iTopORM + */ +class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_tagset", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + $sResult = ''; + $sTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $sAttCode = $this->Get('attcode'); + $oTargetSearch = new DBObjectSearch($sTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($sTargetObjectClass, $sAttCode, UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes... + + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sNewValue = $this->Get('newvalue'); + $sOldValue = $this->Get('oldvalue'); + $sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue); + } + return $sResult; + } +} + /** * Record the modification of an URL * diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 5e6144163e..e92aa45f61 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -409,7 +409,19 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("newvalue", $value); $iId = $oMyChangeOp->DBInsertNoReload(); } - else + elseif ($oAttDef instanceOf AttributeTagSet) + { + // Tag Set + // + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeTagSet"); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + $oMyChangeOp->Set("oldvalue", implode(' ', $original->GetValues())); + $oMyChangeOp->Set("newvalue", implode(' ', $value->GetValues())); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + else { // Scalars // @@ -551,6 +563,12 @@ abstract class CMDBObject extends DBObject $this->DBUpdate(); } + /** + * @param null $oDeletionPlan + * + * @return \DeletionPlan|null + * @throws \DeleteException + */ public function DBDelete(&$oDeletionPlan = null) { return $this->DBDeleteTracked_Internal($oDeletionPlan); @@ -563,6 +581,12 @@ abstract class CMDBObject extends DBObject $this->DBDeleteTracked_Internal($oDeletionPlan); } + /** + * @param null $oDeletionPlan + * + * @return \DeletionPlan|null + * @throws \DeleteException + */ protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) { $prevkey = $this->GetKey(); diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 4cdb286678..154a674466 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -408,6 +408,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => true, ), + 'tag_set_item_separator' => array( + 'type' => 'string', + 'description' => 'Tag set from string: tag label separator', + 'default' => '|', + 'value' => '|', + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), 'cron_max_execution_time' => array( 'type' => 'integer', 'description' => 'Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout', diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml index 8045a5c40f..624854e32f 100644 --- a/core/datamodel.core.xml +++ b/core/datamodel.core.xml @@ -1,5 +1,5 @@ - + diff --git a/core/dbobject.class.php b/core/dbobject.class.php index f5d98b5537..0b3761b103 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -978,6 +978,28 @@ abstract class DBObject implements iDisplay return MetaModel::GetClassIcon(get_class($this), $bImgTag); } + /** + * Get the name as defined in the dictionary + * @return string (empty for default name scheme) + */ + public static function GetClassName($sClass) + { + $sStringCode = 'Class:'.$sClass; + return Dict::S($sStringCode, str_replace('_', ' ', $sClass)); + } + + /** + * Get the description as defined in the dictionary + * @param string $sClass + * + * @return string + */ + final static public function GetClassDescription($sClass) + { + $sStringCode = 'Class:'.$sClass.'+'; + return Dict::S($sStringCode, ''); + } + /** * Gets the name of an object in a safe manner for displaying inside a web page * @return string @@ -1198,6 +1220,15 @@ abstract class DBObject implements iDisplay // check if the given (or current) value is suitable for the attribute // return true if successfull // return the error desciption otherwise + /** + * @param $sAttCode + * @param null $value + * + * @return bool|string + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \OQLException + */ public function CheckValue($sAttCode, $value = null) { if (!is_null($value)) @@ -1247,6 +1278,57 @@ abstract class DBObject implements iDisplay } } } + elseif ($oAtt instanceof AttributeTagSet) + { + if (is_string($toCheck)) + { + $oTag = new ormTagSet(get_class($this), $sAttCode); + try + { + $oTag->SetValues(explode(' ', $toCheck)); + } catch (Exception $e) + { + return "Tag value '$toCheck' is not a valid tag list"; + } + + return true; + } + + if ($toCheck instanceof ormTagSet) + { + return true; + } + + return "Bad type"; + } + elseif ($oAtt instanceof AttributeClassAttCodeSet) + { + if (is_string($toCheck)) + { + $oTag = new ormSet(get_class($this), $sAttCode); + try + { + $aValues = array(); + foreach(explode(',', $toCheck) as $sValue) + { + $aValues[] = trim($sValue); + } + $oTag->SetValues($aValues); + } catch (Exception $e) + { + return "Set value '$toCheck' is not a valid set"; + } + + return true; + } + + if ($toCheck instanceof ormSet) + { + return true; + } + + return "Bad type"; + } elseif ($oAtt->IsScalar()) { $aValues = $oAtt->GetAllowedValues($this->ToArgsForQuery()); @@ -2159,6 +2241,17 @@ abstract class DBObject implements iDisplay if (!MetaModel::DBIsReadOnly()) { $this->OnDelete(); + + // Activate any existing trigger + $sClass = get_class($this); + $sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)); + $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN ('$sClassList')")); + while ($oTrigger = $oSet->Fetch()) + { + /** @var \Trigger $oTrigger */ + $oTrigger->DoActivate($this->ToArgs('this')); + } + $this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 39acb773c9..50f6747580 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -480,6 +480,11 @@ class DBObjectSearch extends DBSearch $oNewCondition = Expression::FromOQL($sOQLCondition); break; + case 'MATCHES': + $oRightExpr = new ScalarExpression($value); + $oNewCondition = new MatchExpression($oField, $oRightExpr); + break; + case 'Contains': case 'Begins with': case 'Finishes with': @@ -1314,6 +1319,13 @@ class DBObjectSearch extends DBSearch $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); return new BinaryExpression($oLeft, $sOperator, $oRight); } + elseif ($oExpression instanceof MatchOqlExpression) + { + $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases); + $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); + + return new MatchExpression($oLeft, $oRight); + } elseif ($oExpression instanceof FieldOqlExpression) { $sClassAlias = $oExpression->GetParent(); diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index 79718081a2..026789f571 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -815,8 +815,8 @@ class DBObjectSet implements iDBObjectSetIterator if ($resQuery) { $aRow = CMDBSource::FetchArray($resQuery); - CMDBSource::FreeResult($resQuery); $iCount = intval($aRow['COUNT']); + CMDBSource::FreeResult($resQuery); } else { diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php index 92e937210a..6c3569ef9d 100644 --- a/core/dict.class.inc.php +++ b/core/dict.class.inc.php @@ -116,6 +116,22 @@ class Dict self::$m_iErrorMode = $iErrorMode; } + /** + * Check if a dictionary entry exists or not + * @param $sStringCode + * + * @return bool + */ + public static function Exists($sStringCode) + { + $sImpossibleString = 'aVlHYKEI3TZuDV5o0pghv7fvhYNYuzYkTk7WL0Zoqw8rggE7aq'; + if (static::S($sStringCode, $sImpossibleString) === $sImpossibleString) + { + return false; + } + return true; + } + /** * Returns a localised string from the dictonary * diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index ed15c94658..0a93193b44 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -188,11 +188,16 @@ EOF $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); } - else if ($value instanceOf ormDocument) - { - $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); - $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); - } + else if ($value instanceOf ormDocument) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); + } + else if ($value instanceOf ormTagSet) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); + } else { $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 06cd005eba..c1ba6c9642 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -320,8 +320,7 @@ abstract class MetaModel final static public function GetName($sClass) { self::_check_subclass($sClass); - $sStringCode = 'Class:'.$sClass; - return Dict::S($sStringCode, str_replace('_', ' ', $sClass)); + return $sClass::GetClassName($sClass); } /** @@ -411,8 +410,7 @@ abstract class MetaModel final static public function GetClassDescription($sClass) { self::_check_subclass($sClass); - $sStringCode = 'Class:'.$sClass.'+'; - return Dict::S($sStringCode, ''); + return $sClass::GetClassDescription($sClass); } /** @@ -1024,7 +1022,7 @@ abstract class MetaModel /** * array of ("classname" => array of attributes) * - * @var array + * @var \AttributeDefinition[] */ private static $m_aAttribDefs = array(); /** diff --git a/core/oql/build/PHP/LexerGenerator/Exception.php b/core/oql/build/PHP/LexerGenerator/Exception.php index 69b3066d25..c5cb2fa552 100644 --- a/core/oql/build/PHP/LexerGenerator/Exception.php +++ b/core/oql/build/PHP/LexerGenerator/Exception.php @@ -42,7 +42,7 @@ * @copyright 2006 Gregory Beaver * @license http://www.opensource.org/licenses/bsd-license.php New BSD License */ -require_once 'PEAR/Exception.php'; +//require_once 'PEAR/Exception.php'; /** * @package PHP_LexerGenerator * @author Gregory Beaver @@ -51,5 +51,5 @@ require_once 'PEAR/Exception.php'; * @version @package_version@ * @since File available since Release 0.1.0 */ -class PHP_LexerGenerator_Exception extends PEAR_Exception {} +class PHP_LexerGenerator_Exception extends Exception {} ?> \ No newline at end of file diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 371c750867..5c8d343350 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -22,6 +22,9 @@ class MissingQueryArgument extends CoreException } +/** + * @method Check($oModelReflection, array $aAliases, $sSourceQuery) + */ abstract class Expression { /** @@ -575,6 +578,7 @@ class BinaryExpression extends Expression * @param null $oAttDef * * @return array + * @throws \MissingQueryArgument */ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null) { @@ -673,6 +677,10 @@ class BinaryExpression extends Expression } } } + if (isset($aCriteriaLeft['widget']) && isset($aCriteriaRight['widget']) && ($aCriteriaLeft['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET) && ($aCriteriaRight['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET)) + { + $aCriteriaOverride['operator'] = 'MATCHES'; + } } return array_merge($aCriteriaLeft, $aCriteriaRight, $aCriteriaOverride); @@ -722,7 +730,9 @@ class MatchExpression extends BinaryExpression public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true) { + /** @var \FieldExpression $oLeft */ $oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); + /** @var \ScalarExpression $oRight */ $oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved); return new static($oLeft, $oRight); @@ -883,20 +893,20 @@ class ScalarExpression extends UnaryExpression public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null) { - $aCriteria = array(); + $aCriterion = array(); switch ((string)($this->m_value)) { case '%Y-%m-%d': - $aCriteria['unit'] = 'DAY'; + $aCriterion['unit'] = 'DAY'; break; case '%Y-%m': - $aCriteria['unit'] = 'MONTH'; + $aCriterion['unit'] = 'MONTH'; break; case '%w': - $aCriteria['unit'] = 'WEEKDAY'; + $aCriterion['unit'] = 'WEEKDAY'; break; case '%H': - $aCriteria['unit'] = 'HOUR'; + $aCriterion['unit'] = 'HOUR'; break; default: $aValue = array(); @@ -914,13 +924,47 @@ class ScalarExpression extends UnaryExpression $oObj = MetaModel::GetObject($sTarget, $this->GetValue()); $aValue['label'] = $oObj->Get("friendlyname"); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); + } else { $aValue['label'] = Dict::S('Enum:Undefined'); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); } + } catch (Exception $e) + { + IssueLog::Error($e->getMessage()); } - catch (Exception $e) + break; + case ($oAttDef instanceof AttributeTagSet): + try + { + if (!empty($this->GetValue())) + { + $aValues = array(); + $oValue = $this->GetValue(); + if (is_string($oValue)) + { + $oValue = $oAttDef->GetExistingTagsFromString($oValue, true); + } + /** @var \ormTagSet $oValue */ + $aTags = $oValue->GetTags(); + foreach($aTags as $oTag) + { + $aValue['label'] = $oTag->Get('label'); + $aValue['value'] = $oTag->Get('code'); + $aValues[] = $aValue; + } + $aCriterion['values'] = $aValues; + } + else + { + $aCriterion['has_undefined'] = true; + } + } catch (Exception $e) { IssueLog::Error($e->getMessage()); } @@ -934,13 +978,16 @@ class ScalarExpression extends UnaryExpression $sTarget = $oAttDef->GetTargetClass(); $oObj = MetaModel::GetObject($sTarget, $this->GetValue(), true, true); $aValue['label'] = $oObj->Get("friendlyname"); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); } else { $aValue['label'] = Dict::S('Enum:Undefined'); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); } - } - catch (Exception $e) + } catch (Exception $e) { // This object cannot be seen... ignore } @@ -949,23 +996,22 @@ class ScalarExpression extends UnaryExpression try { $aValue['label'] = $oAttDef->GetAsPlainText($this->GetValue()); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); } catch (Exception $e) { $aValue['label'] = $this->GetValue(); + $aValue['value'] = $this->GetValue(); + $aCriterion['values'] = array($aValue); } break; } } - if (!empty($aValue)) - { - // only if a label is found - $aValue['value'] = $this->GetValue(); - $aCriteria['values'] = array($aValue); - } break; } - $aCriteria['oql'] = $this->RenderExpression(false, $aArgs, $bRetrofitParams); - return $aCriteria; + $aCriterion['oql'] = $this->RenderExpression(false, $aArgs, $bRetrofitParams); + + return $aCriterion; } } @@ -1572,7 +1618,7 @@ class ListExpression extends Expression { if ($oExpr instanceof VariableExpression) { - $this->m_aExpressions[$idx] = $oExpr->GetAsScalar(); + $this->m_aExpressions[$idx] = $oExpr->GetAsScalar($aArgs); } else { @@ -2116,7 +2162,7 @@ class CharConcatExpression extends Expression { if ($oExpr instanceof VariableExpression) { - $this->m_aExpressions[$idx] = $oExpr->GetAsScalar(); + $this->m_aExpressions[$idx] = $oExpr->GetAsScalar($aArgs); } else { diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php index 23d9c7b6e6..36e5ce4ef1 100644 --- a/core/oql/oql-lexer.php +++ b/core/oql/oql-lexer.php @@ -140,6 +140,7 @@ class OQLLexerRaw '/\GNOT LIKE/ ', '/\GIN/ ', '/\GNOT IN/ ', + '/\GMATCHES/ ', '/\GINTERVAL/ ', '/\GIF/ ', '/\GELT/ ', @@ -441,204 +442,209 @@ class OQLLexerRaw function yy_r1_33($yy_subpatterns) { - $this->token = OQLParser::INTERVAL; + $this->token = OQLParser::MATCHES; } function yy_r1_34($yy_subpatterns) { - $this->token = OQLParser::F_IF; + $this->token = OQLParser::INTERVAL; } function yy_r1_35($yy_subpatterns) { - $this->token = OQLParser::F_ELT; + $this->token = OQLParser::F_IF; } function yy_r1_36($yy_subpatterns) { - $this->token = OQLParser::F_COALESCE; + $this->token = OQLParser::F_ELT; } function yy_r1_37($yy_subpatterns) { - $this->token = OQLParser::F_ISNULL; + $this->token = OQLParser::F_COALESCE; } function yy_r1_38($yy_subpatterns) { - $this->token = OQLParser::F_CONCAT; + $this->token = OQLParser::F_ISNULL; } function yy_r1_39($yy_subpatterns) { - $this->token = OQLParser::F_SUBSTR; + $this->token = OQLParser::F_CONCAT; } function yy_r1_40($yy_subpatterns) { - $this->token = OQLParser::F_TRIM; + $this->token = OQLParser::F_SUBSTR; } function yy_r1_41($yy_subpatterns) { - $this->token = OQLParser::F_DATE; + $this->token = OQLParser::F_TRIM; } function yy_r1_42($yy_subpatterns) { - $this->token = OQLParser::F_DATE_FORMAT; + $this->token = OQLParser::F_DATE; } function yy_r1_43($yy_subpatterns) { - $this->token = OQLParser::F_CURRENT_DATE; + $this->token = OQLParser::F_DATE_FORMAT; } function yy_r1_44($yy_subpatterns) { - $this->token = OQLParser::F_NOW; + $this->token = OQLParser::F_CURRENT_DATE; } function yy_r1_45($yy_subpatterns) { - $this->token = OQLParser::F_TIME; + $this->token = OQLParser::F_NOW; } function yy_r1_46($yy_subpatterns) { - $this->token = OQLParser::F_TO_DAYS; + $this->token = OQLParser::F_TIME; } function yy_r1_47($yy_subpatterns) { - $this->token = OQLParser::F_FROM_DAYS; + $this->token = OQLParser::F_TO_DAYS; } function yy_r1_48($yy_subpatterns) { - $this->token = OQLParser::F_YEAR; + $this->token = OQLParser::F_FROM_DAYS; } function yy_r1_49($yy_subpatterns) { - $this->token = OQLParser::F_MONTH; + $this->token = OQLParser::F_YEAR; } function yy_r1_50($yy_subpatterns) { - $this->token = OQLParser::F_DAY; + $this->token = OQLParser::F_MONTH; } function yy_r1_51($yy_subpatterns) { - $this->token = OQLParser::F_HOUR; + $this->token = OQLParser::F_DAY; } function yy_r1_52($yy_subpatterns) { - $this->token = OQLParser::F_MINUTE; + $this->token = OQLParser::F_HOUR; } function yy_r1_53($yy_subpatterns) { - $this->token = OQLParser::F_SECOND; + $this->token = OQLParser::F_MINUTE; } function yy_r1_54($yy_subpatterns) { - $this->token = OQLParser::F_DATE_ADD; + $this->token = OQLParser::F_SECOND; } function yy_r1_55($yy_subpatterns) { - $this->token = OQLParser::F_DATE_SUB; + $this->token = OQLParser::F_DATE_ADD; } function yy_r1_56($yy_subpatterns) { - $this->token = OQLParser::F_ROUND; + $this->token = OQLParser::F_DATE_SUB; } function yy_r1_57($yy_subpatterns) { - $this->token = OQLParser::F_FLOOR; + $this->token = OQLParser::F_ROUND; } function yy_r1_58($yy_subpatterns) { - $this->token = OQLParser::F_INET_ATON; + $this->token = OQLParser::F_FLOOR; } function yy_r1_59($yy_subpatterns) { - $this->token = OQLParser::F_INET_NTOA; + $this->token = OQLParser::F_INET_ATON; } function yy_r1_60($yy_subpatterns) { - $this->token = OQLParser::BELOW; + $this->token = OQLParser::F_INET_NTOA; } function yy_r1_61($yy_subpatterns) { - $this->token = OQLParser::BELOW_STRICT; + $this->token = OQLParser::BELOW; } function yy_r1_62($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW; + $this->token = OQLParser::BELOW_STRICT; } function yy_r1_63($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW_STRICT; + $this->token = OQLParser::NOT_BELOW; } function yy_r1_64($yy_subpatterns) { - $this->token = OQLParser::ABOVE; + $this->token = OQLParser::NOT_BELOW_STRICT; } function yy_r1_65($yy_subpatterns) { - $this->token = OQLParser::ABOVE_STRICT; + $this->token = OQLParser::ABOVE; } function yy_r1_66($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE; + $this->token = OQLParser::ABOVE_STRICT; } function yy_r1_67($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE_STRICT; + $this->token = OQLParser::NOT_ABOVE; } function yy_r1_68($yy_subpatterns) { - $this->token = OQLParser::HEXVAL; + $this->token = OQLParser::NOT_ABOVE_STRICT; } function yy_r1_69($yy_subpatterns) { - $this->token = OQLParser::NUMVAL; + $this->token = OQLParser::HEXVAL; } function yy_r1_70($yy_subpatterns) { - $this->token = OQLParser::STRVAL; + $this->token = OQLParser::NUMVAL; } function yy_r1_71($yy_subpatterns) { - $this->token = OQLParser::NAME; + $this->token = OQLParser::STRVAL; } function yy_r1_72($yy_subpatterns) { - $this->token = OQLParser::VARNAME; + $this->token = OQLParser::NAME; } function yy_r1_73($yy_subpatterns) + { + + $this->token = OQLParser::VARNAME; + } + function yy_r1_74($yy_subpatterns) { $this->token = OQLParser::DOT; @@ -666,25 +672,25 @@ class OQLLexer extends OQLLexerRaw function yylex() { - try - { - return parent::yylex(); - } - catch (Exception $e) - { - $sMessage = $e->getMessage(); - if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) - { - $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); - if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) - { - $iLine = $aMatches[1]; - $sUnexpected = $aMatches[2]; - throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); - } - } - // Default: forward the exception - throw $e; + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; } } } diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index ebd811a2f3..ba32ba2397 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -139,6 +139,7 @@ f_round = "ROUND" f_floor = "FLOOR" f_inet_aton = "INET_ATON" f_inet_ntoa = "INET_NTOA" +matches = "MATCHES" below = "BELOW" below_strict = "BELOW STRICT" not_below = "NOT BELOW" @@ -273,6 +274,9 @@ in { not_in { $this->token = OQLParser::NOT_IN; } +matches { + $this->token = OQLParser::MATCHES; +} interval { $this->token = OQLParser::INTERVAL; } @@ -419,25 +423,25 @@ class OQLLexer extends OQLLexerRaw function yylex() { - try - { - return parent::yylex(); - } - catch (Exception $e) - { - $sMessage = $e->getMessage(); - if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) - { - $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); - if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) - { - $iLine = $aMatches[1]; - $sUnexpected = $aMatches[2]; - throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); - } - } - // Default: forward the exception - throw $e; + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; } } } diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index e884c96ccb..ac3847796e 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -96,8 +96,8 @@ class OQLParser_yyStackEntry // code external to the class is included here // declare_class is output here -#line 24 "..\oql-parser.y" -class OQLParserRaw#line 102 "..\oql-parser.php" +#line 24 "../oql-parser.y" +class OQLParserRaw#line 102 "../oql-parser.php" { /* First off, code is included which follows the "include_class" declaration ** in the input file. */ @@ -157,36 +157,37 @@ class OQLParserRaw#line 102 "..\oql-parser.php" const LE = 44; const LIKE = 45; const NOT_LIKE = 46; - const BITWISE_LEFT_SHIFT = 47; - const BITWISE_RIGHT_SHIFT = 48; - const BITWISE_AND = 49; - const BITWISE_OR = 50; - const BITWISE_XOR = 51; - const IN = 52; - const NOT_IN = 53; - const F_IF = 54; - const F_ELT = 55; - const F_COALESCE = 56; - const F_ISNULL = 57; - const F_CONCAT = 58; - const F_SUBSTR = 59; - const F_TRIM = 60; - const F_DATE = 61; - const F_DATE_FORMAT = 62; - const F_CURRENT_DATE = 63; - const F_NOW = 64; - const F_TIME = 65; - const F_TO_DAYS = 66; - const F_FROM_DAYS = 67; - const F_DATE_ADD = 68; - const F_DATE_SUB = 69; - const F_ROUND = 70; - const F_FLOOR = 71; - const F_INET_ATON = 72; - const F_INET_NTOA = 73; - const YY_NO_ACTION = 290; - const YY_ACCEPT_ACTION = 289; - const YY_ERROR_ACTION = 288; + const MATCHES = 47; + const BITWISE_LEFT_SHIFT = 48; + const BITWISE_RIGHT_SHIFT = 49; + const BITWISE_AND = 50; + const BITWISE_OR = 51; + const BITWISE_XOR = 52; + const IN = 53; + const NOT_IN = 54; + const F_IF = 55; + const F_ELT = 56; + const F_COALESCE = 57; + const F_ISNULL = 58; + const F_CONCAT = 59; + const F_SUBSTR = 60; + const F_TRIM = 61; + const F_DATE = 62; + const F_DATE_FORMAT = 63; + const F_CURRENT_DATE = 64; + const F_NOW = 65; + const F_TIME = 66; + const F_TO_DAYS = 67; + const F_FROM_DAYS = 68; + const F_DATE_ADD = 69; + const F_DATE_SUB = 70; + const F_ROUND = 71; + const F_FLOOR = 72; + const F_INET_ATON = 73; + const F_INET_NTOA = 74; + const YY_NO_ACTION = 292; + const YY_ACCEPT_ACTION = 291; + const YY_ERROR_ACTION = 290; /* Next are that tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -238,162 +239,164 @@ class OQLParserRaw#line 102 "..\oql-parser.php" ** shifting non-terminals after a reduce. ** self::$yy_default Default action for each state. */ - const YY_SZ_ACTTAB = 549; + const YY_SZ_ACTTAB = 552; static public $yy_action = array( - /* 0 */ 7, 30, 6, 66, 60, 4, 153, 152, 155, 98, - /* 10 */ 107, 106, 105, 65, 101, 102, 25, 22, 23, 21, - /* 20 */ 19, 27, 26, 28, 24, 162, 151, 43, 177, 177, - /* 30 */ 80, 41, 62, 85, 150, 140, 103, 108, 109, 114, - /* 40 */ 115, 113, 112, 110, 111, 133, 134, 157, 158, 156, - /* 50 */ 154, 159, 160, 165, 166, 164, 20, 121, 125, 3, - /* 60 */ 68, 70, 69, 75, 99, 93, 142, 66, 76, 64, - /* 70 */ 119, 120, 7, 79, 50, 122, 121, 42, 153, 152, - /* 80 */ 155, 139, 107, 106, 105, 65, 101, 102, 149, 119, - /* 90 */ 120, 137, 143, 121, 132, 130, 62, 148, 147, 100, - /* 100 */ 146, 144, 145, 167, 61, 116, 119, 120, 103, 108, - /* 110 */ 109, 114, 115, 113, 112, 110, 111, 133, 134, 157, - /* 120 */ 158, 156, 154, 159, 160, 165, 166, 164, 7, 106, - /* 130 */ 11, 66, 9, 80, 153, 152, 155, 81, 107, 106, - /* 140 */ 105, 65, 101, 102, 163, 161, 72, 8, 10, 88, - /* 150 */ 78, 1, 46, 38, 44, 104, 54, 51, 41, 42, - /* 160 */ 62, 118, 135, 136, 103, 108, 109, 114, 115, 113, - /* 170 */ 112, 110, 111, 133, 134, 157, 158, 156, 154, 159, - /* 180 */ 160, 165, 166, 164, 289, 138, 67, 86, 66, 39, - /* 190 */ 87, 58, 37, 45, 126, 34, 48, 131, 124, 63, - /* 200 */ 13, 40, 66, 17, 230, 15, 12, 36, 97, 2, - /* 210 */ 47, 55, 41, 129, 127, 128, 117, 62, 66, 80, - /* 220 */ 80, 80, 80, 123, 126, 31, 48, 131, 124, 63, - /* 230 */ 59, 62, 237, 17, 91, 15, 20, 36, 73, 80, - /* 240 */ 237, 92, 237, 129, 127, 128, 117, 62, 66, 237, - /* 250 */ 94, 74, 237, 42, 126, 31, 48, 131, 124, 63, - /* 260 */ 237, 237, 66, 17, 66, 15, 66, 36, 83, 95, - /* 270 */ 56, 77, 82, 129, 127, 128, 117, 62, 141, 66, - /* 280 */ 237, 237, 237, 237, 237, 126, 34, 48, 131, 124, - /* 290 */ 63, 62, 237, 62, 17, 62, 15, 52, 36, 5, - /* 300 */ 42, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 310 */ 237, 8, 237, 71, 237, 126, 32, 48, 131, 124, - /* 320 */ 63, 237, 237, 66, 17, 118, 15, 53, 36, 89, - /* 330 */ 42, 57, 237, 237, 129, 127, 128, 117, 62, 66, - /* 340 */ 237, 237, 237, 237, 237, 126, 16, 48, 131, 124, - /* 350 */ 63, 237, 62, 66, 17, 237, 15, 237, 36, 90, - /* 360 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 370 */ 237, 237, 237, 237, 237, 126, 29, 48, 131, 124, - /* 380 */ 63, 237, 62, 237, 17, 237, 15, 237, 36, 237, - /* 390 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 400 */ 237, 237, 237, 237, 237, 126, 33, 48, 131, 124, - /* 410 */ 63, 237, 237, 237, 17, 237, 15, 237, 36, 237, - /* 420 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 430 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 440 */ 63, 237, 237, 237, 17, 237, 15, 237, 35, 237, - /* 450 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 460 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 470 */ 63, 237, 237, 237, 17, 237, 14, 66, 237, 237, - /* 480 */ 237, 237, 84, 56, 129, 127, 128, 117, 62, 66, - /* 490 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 500 */ 63, 237, 237, 237, 18, 66, 62, 237, 237, 237, - /* 510 */ 237, 96, 237, 237, 129, 127, 128, 117, 62, 66, - /* 520 */ 237, 237, 237, 237, 237, 126, 237, 49, 131, 124, - /* 530 */ 63, 237, 237, 237, 62, 237, 237, 237, 237, 237, - /* 540 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, + /* 0 */ 28, 25, 26, 23, 20, 27, 19, 21, 22, 24, + /* 10 */ 145, 42, 178, 178, 93, 40, 5, 44, 55, 125, + /* 20 */ 70, 44, 160, 153, 159, 127, 111, 112, 110, 67, + /* 30 */ 108, 107, 142, 39, 71, 146, 144, 127, 13, 129, + /* 40 */ 130, 143, 141, 140, 139, 138, 137, 135, 100, 132, + /* 50 */ 126, 129, 130, 106, 105, 104, 103, 102, 133, 117, + /* 60 */ 134, 152, 154, 155, 156, 157, 158, 162, 163, 164, + /* 70 */ 165, 166, 167, 5, 29, 7, 10, 59, 11, 160, + /* 80 */ 153, 159, 46, 111, 112, 110, 67, 108, 107, 131, + /* 90 */ 99, 147, 161, 151, 5, 53, 119, 66, 44, 120, + /* 100 */ 160, 153, 159, 80, 111, 112, 110, 67, 108, 107, + /* 110 */ 106, 105, 104, 103, 102, 133, 117, 134, 152, 154, + /* 120 */ 155, 156, 157, 158, 162, 163, 164, 165, 166, 167, + /* 130 */ 112, 106, 105, 104, 103, 102, 133, 117, 134, 152, + /* 140 */ 154, 155, 156, 157, 158, 162, 163, 164, 165, 166, + /* 150 */ 167, 136, 150, 36, 38, 62, 291, 98, 61, 168, + /* 160 */ 65, 149, 148, 4, 60, 41, 123, 32, 48, 122, + /* 170 */ 124, 63, 231, 54, 65, 17, 44, 14, 8, 37, + /* 180 */ 85, 75, 80, 80, 80, 116, 101, 115, 114, 64, + /* 190 */ 65, 113, 128, 80, 80, 68, 123, 33, 48, 122, + /* 200 */ 124, 63, 58, 64, 65, 17, 65, 14, 65, 37, + /* 210 */ 82, 77, 56, 95, 86, 116, 101, 115, 114, 64, + /* 220 */ 121, 65, 45, 52, 80, 9, 40, 123, 32, 48, + /* 230 */ 122, 124, 63, 64, 83, 64, 17, 64, 14, 69, + /* 240 */ 37, 8, 6, 47, 43, 94, 116, 101, 115, 114, + /* 250 */ 64, 65, 92, 109, 12, 128, 74, 123, 34, 48, + /* 260 */ 122, 124, 63, 1, 51, 40, 17, 44, 14, 3, + /* 270 */ 37, 50, 2, 57, 28, 239, 116, 101, 115, 114, + /* 280 */ 64, 65, 239, 239, 239, 239, 118, 123, 33, 48, + /* 290 */ 122, 124, 63, 239, 239, 65, 17, 239, 14, 239, + /* 300 */ 37, 88, 239, 239, 72, 239, 116, 101, 115, 114, + /* 310 */ 64, 65, 239, 239, 239, 239, 239, 123, 31, 48, + /* 320 */ 122, 124, 63, 239, 64, 239, 17, 239, 14, 239, + /* 330 */ 37, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 340 */ 64, 65, 239, 239, 239, 239, 239, 123, 16, 48, + /* 350 */ 122, 124, 63, 239, 239, 239, 17, 239, 14, 239, + /* 360 */ 37, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 370 */ 64, 65, 239, 239, 239, 239, 239, 123, 30, 48, + /* 380 */ 122, 124, 63, 239, 239, 65, 17, 239, 14, 239, + /* 390 */ 37, 90, 239, 239, 239, 239, 116, 101, 115, 114, + /* 400 */ 64, 65, 239, 239, 239, 239, 239, 123, 239, 48, + /* 410 */ 122, 124, 63, 239, 64, 239, 17, 239, 14, 239, + /* 420 */ 35, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 430 */ 64, 65, 239, 239, 239, 239, 239, 123, 239, 48, + /* 440 */ 122, 124, 63, 239, 239, 239, 17, 239, 15, 76, + /* 450 */ 73, 78, 89, 97, 96, 239, 116, 101, 115, 114, + /* 460 */ 64, 65, 239, 239, 239, 127, 239, 123, 239, 48, + /* 470 */ 122, 124, 63, 239, 239, 239, 18, 65, 65, 129, + /* 480 */ 130, 239, 91, 56, 79, 239, 116, 101, 115, 114, + /* 490 */ 64, 65, 65, 239, 239, 239, 239, 123, 81, 49, + /* 500 */ 122, 124, 63, 239, 239, 239, 64, 64, 239, 65, + /* 510 */ 239, 239, 239, 239, 239, 87, 116, 101, 115, 114, + /* 520 */ 64, 64, 65, 239, 239, 239, 239, 239, 84, 239, + /* 530 */ 239, 239, 239, 239, 239, 239, 239, 239, 64, 239, + /* 540 */ 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, + /* 550 */ 239, 64, ); static public $yy_lookahead = array( - /* 0 */ 18, 79, 20, 79, 82, 6, 24, 25, 26, 85, - /* 10 */ 28, 29, 30, 31, 32, 33, 9, 10, 11, 12, - /* 20 */ 13, 14, 15, 16, 17, 38, 39, 3, 4, 5, - /* 30 */ 108, 7, 108, 81, 47, 48, 54, 55, 56, 57, - /* 40 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, - /* 50 */ 68, 69, 70, 71, 72, 73, 2, 37, 93, 18, - /* 60 */ 21, 22, 23, 24, 25, 26, 9, 79, 76, 77, - /* 70 */ 50, 51, 18, 85, 80, 19, 37, 83, 24, 25, - /* 80 */ 26, 81, 28, 29, 30, 31, 32, 33, 31, 50, - /* 90 */ 51, 34, 35, 37, 52, 53, 108, 40, 41, 42, - /* 100 */ 43, 44, 45, 46, 79, 108, 50, 51, 54, 55, - /* 110 */ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - /* 120 */ 66, 67, 68, 69, 70, 71, 72, 73, 18, 29, - /* 130 */ 95, 79, 99, 108, 24, 25, 26, 85, 28, 29, - /* 140 */ 30, 31, 32, 33, 109, 110, 113, 100, 97, 81, - /* 150 */ 103, 18, 4, 5, 3, 30, 80, 27, 7, 83, - /* 160 */ 108, 114, 111, 112, 54, 55, 56, 57, 58, 59, - /* 170 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - /* 180 */ 70, 71, 72, 73, 75, 76, 77, 78, 79, 1, - /* 190 */ 79, 79, 79, 79, 85, 86, 87, 88, 89, 90, - /* 200 */ 8, 3, 79, 94, 27, 96, 8, 98, 85, 5, - /* 210 */ 79, 92, 7, 104, 105, 106, 107, 108, 79, 108, - /* 220 */ 108, 108, 108, 19, 85, 86, 87, 88, 89, 90, - /* 230 */ 91, 108, 115, 94, 81, 96, 2, 98, 36, 108, - /* 240 */ 115, 102, 115, 104, 105, 106, 107, 108, 79, 115, - /* 250 */ 80, 49, 115, 83, 85, 86, 87, 88, 89, 90, - /* 260 */ 115, 115, 79, 94, 79, 96, 79, 98, 85, 84, - /* 270 */ 85, 102, 85, 104, 105, 106, 107, 108, 78, 79, - /* 280 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 290 */ 90, 108, 115, 108, 94, 108, 96, 80, 98, 5, - /* 300 */ 83, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 310 */ 115, 100, 115, 19, 115, 85, 86, 87, 88, 89, - /* 320 */ 90, 115, 115, 79, 94, 114, 96, 80, 98, 85, - /* 330 */ 83, 101, 115, 115, 104, 105, 106, 107, 108, 79, - /* 340 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 350 */ 90, 115, 108, 79, 94, 115, 96, 115, 98, 85, - /* 360 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 370 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 380 */ 90, 115, 108, 115, 94, 115, 96, 115, 98, 115, - /* 390 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 400 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 410 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, - /* 420 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 430 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 440 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, - /* 450 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 460 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 470 */ 90, 115, 115, 115, 94, 115, 96, 79, 115, 115, - /* 480 */ 115, 115, 84, 85, 104, 105, 106, 107, 108, 79, - /* 490 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 500 */ 90, 115, 115, 115, 94, 79, 108, 115, 115, 115, - /* 510 */ 115, 85, 115, 115, 104, 105, 106, 107, 108, 79, - /* 520 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 530 */ 90, 115, 115, 115, 108, 115, 115, 115, 115, 115, - /* 540 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, + /* 0 */ 2, 9, 10, 11, 12, 13, 14, 15, 16, 17, + /* 10 */ 9, 3, 4, 5, 81, 7, 18, 84, 81, 19, + /* 20 */ 36, 84, 24, 25, 26, 37, 28, 29, 30, 31, + /* 30 */ 32, 33, 31, 3, 50, 34, 35, 37, 8, 51, + /* 40 */ 52, 40, 41, 42, 43, 44, 45, 46, 47, 53, + /* 50 */ 54, 51, 52, 55, 56, 57, 58, 59, 60, 61, + /* 60 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 70 */ 72, 73, 74, 18, 80, 20, 98, 83, 96, 24, + /* 80 */ 25, 26, 1, 28, 29, 30, 31, 32, 33, 94, + /* 90 */ 112, 113, 110, 111, 18, 81, 77, 78, 84, 82, + /* 100 */ 24, 25, 26, 109, 28, 29, 30, 31, 32, 33, + /* 110 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 120 */ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + /* 130 */ 29, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 140 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 150 */ 74, 38, 39, 80, 80, 80, 76, 77, 78, 79, + /* 160 */ 80, 48, 49, 6, 80, 80, 86, 87, 88, 89, + /* 170 */ 90, 91, 27, 81, 80, 95, 84, 97, 101, 99, + /* 180 */ 86, 104, 109, 109, 109, 105, 106, 107, 108, 109, + /* 190 */ 80, 109, 115, 109, 109, 80, 86, 87, 88, 89, + /* 200 */ 90, 91, 92, 109, 80, 95, 80, 97, 80, 99, + /* 210 */ 86, 85, 86, 103, 86, 105, 106, 107, 108, 109, + /* 220 */ 79, 80, 3, 27, 109, 100, 7, 86, 87, 88, + /* 230 */ 89, 90, 91, 109, 82, 109, 95, 109, 97, 114, + /* 240 */ 99, 101, 5, 4, 5, 82, 105, 106, 107, 108, + /* 250 */ 109, 80, 82, 30, 8, 115, 19, 86, 87, 88, + /* 260 */ 89, 90, 91, 18, 81, 7, 95, 84, 97, 18, + /* 270 */ 99, 93, 5, 102, 2, 116, 105, 106, 107, 108, + /* 280 */ 109, 80, 116, 116, 116, 116, 19, 86, 87, 88, + /* 290 */ 89, 90, 91, 116, 116, 80, 95, 116, 97, 116, + /* 300 */ 99, 86, 116, 116, 103, 116, 105, 106, 107, 108, + /* 310 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 320 */ 89, 90, 91, 116, 109, 116, 95, 116, 97, 116, + /* 330 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 340 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 350 */ 89, 90, 91, 116, 116, 116, 95, 116, 97, 116, + /* 360 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 370 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 380 */ 89, 90, 91, 116, 116, 80, 95, 116, 97, 116, + /* 390 */ 99, 86, 116, 116, 116, 116, 105, 106, 107, 108, + /* 400 */ 109, 80, 116, 116, 116, 116, 116, 86, 116, 88, + /* 410 */ 89, 90, 91, 116, 109, 116, 95, 116, 97, 116, + /* 420 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 430 */ 109, 80, 116, 116, 116, 116, 116, 86, 116, 88, + /* 440 */ 89, 90, 91, 116, 116, 116, 95, 116, 97, 21, + /* 450 */ 22, 23, 24, 25, 26, 116, 105, 106, 107, 108, + /* 460 */ 109, 80, 116, 116, 116, 37, 116, 86, 116, 88, + /* 470 */ 89, 90, 91, 116, 116, 116, 95, 80, 80, 51, + /* 480 */ 52, 116, 85, 86, 86, 116, 105, 106, 107, 108, + /* 490 */ 109, 80, 80, 116, 116, 116, 116, 86, 86, 88, + /* 500 */ 89, 90, 91, 116, 116, 116, 109, 109, 116, 80, + /* 510 */ 116, 116, 116, 116, 116, 86, 105, 106, 107, 108, + /* 520 */ 109, 109, 80, 116, 116, 116, 116, 116, 86, 116, + /* 530 */ 116, 116, 116, 116, 116, 116, 116, 116, 109, 116, + /* 540 */ 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, + /* 550 */ 116, 109, ); - const YY_SHIFT_USE_DFLT = -19; + const YY_SHIFT_USE_DFLT = -17; const YY_SHIFT_MAX = 67; static public $yy_shift_ofst = array( - /* 0 */ 54, -18, -18, 110, 110, 110, 110, 110, 110, 110, - /* 10 */ 110, 110, 100, 100, 57, 57, 39, -13, -13, 100, - /* 20 */ 100, 100, 100, 100, 100, 100, 100, 100, 100, 56, - /* 30 */ 24, 20, 20, 20, 20, 202, 202, 151, 100, 234, - /* 40 */ 100, 100, 205, 100, 100, 205, 100, 205, 42, 42, - /* 50 */ -1, 100, -1, -1, -1, 41, 7, 294, 198, 204, - /* 60 */ 148, 192, 177, 133, 188, 125, 130, 188, + /* 0 */ -2, 55, 55, 76, 76, 76, 76, 76, 76, 76, + /* 10 */ 76, 76, 101, 101, 1, 1, 428, 113, 113, 101, + /* 20 */ 101, 101, 101, 101, 101, 101, 101, 101, 101, 8, + /* 30 */ 0, -12, -12, -12, -12, -16, 219, -16, 258, 101, + /* 40 */ 101, 258, 101, 101, 258, 101, 272, 101, -4, -4, + /* 50 */ 251, 157, 101, 157, 157, 157, -8, 237, 267, 239, + /* 60 */ 30, 81, 246, 245, 145, 196, 81, 223, ); - const YY_REDUCE_USE_DFLT = -79; + const YY_REDUCE_USE_DFLT = -68; const YY_REDUCE_MAX = 55; static public $yy_reduce_ofst = array( - /* 0 */ 109, 139, 169, 230, 200, 320, 260, 290, 350, 380, - /* 10 */ 410, 440, 398, 185, 51, 51, 47, 35, 35, 274, - /* 20 */ -78, 426, -12, 123, 52, -76, 183, 244, 187, 211, - /* 30 */ 247, 211, 211, 211, 211, 33, 33, 76, 111, -8, - /* 40 */ 25, 112, 170, 131, 114, 217, 113, -6, 119, 119, - /* 50 */ 153, -3, 68, 0, -48, -35, + /* 0 */ 80, 110, 201, 171, 141, 291, 231, 261, 321, 351, + /* 10 */ 381, 411, 397, 126, -22, -22, 77, -18, -18, 94, + /* 20 */ 429, 442, 124, 398, 412, 305, 215, 128, -6, 183, + /* 30 */ 140, 140, 140, 140, 140, 125, 14, 125, -63, 75, + /* 40 */ 84, 92, 85, 115, -67, 74, 19, 73, 178, 178, + /* 50 */ -5, 17, 82, 152, 163, 170, ); static public $yyExpectedTokens = array( - /* 0 */ array(2, 18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 1 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 2 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 3 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 4 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 5 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 6 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 7 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 8 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 9 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 10 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 11 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 0 */ array(2, 18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 1 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 2 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 3 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 4 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 5 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 6 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 7 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 8 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 9 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 10 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 11 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), /* 12 */ array(29, ), /* 13 */ array(29, ), - /* 14 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), - /* 15 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), - /* 16 */ array(21, 22, 23, 24, 25, 26, 37, 50, 51, ), - /* 17 */ array(38, 39, 47, 48, ), - /* 18 */ array(38, 39, 47, 48, ), + /* 14 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, ), + /* 15 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, ), + /* 16 */ array(21, 22, 23, 24, 25, 26, 37, 51, 52, ), + /* 17 */ array(38, 39, 48, 49, ), + /* 18 */ array(38, 39, 48, 49, ), /* 19 */ array(29, ), /* 20 */ array(29, ), /* 21 */ array(29, ), @@ -404,45 +407,45 @@ static public $yy_action = array( /* 26 */ array(29, ), /* 27 */ array(29, ), /* 28 */ array(29, ), - /* 29 */ array(19, 37, 50, 51, ), - /* 30 */ array(3, 4, 5, 7, ), - /* 31 */ array(37, 50, 51, ), - /* 32 */ array(37, 50, 51, ), - /* 33 */ array(37, 50, 51, ), - /* 34 */ array(37, 50, 51, ), - /* 35 */ array(36, 49, ), - /* 36 */ array(36, 49, ), - /* 37 */ array(3, 7, ), - /* 38 */ array(29, ), - /* 39 */ array(2, ), + /* 29 */ array(3, 4, 5, 7, ), + /* 30 */ array(19, 37, 51, 52, ), + /* 31 */ array(37, 51, 52, ), + /* 32 */ array(37, 51, 52, ), + /* 33 */ array(37, 51, 52, ), + /* 34 */ array(37, 51, 52, ), + /* 35 */ array(36, 50, ), + /* 36 */ array(3, 7, ), + /* 37 */ array(36, 50, ), + /* 38 */ array(7, ), + /* 39 */ array(29, ), /* 40 */ array(29, ), - /* 41 */ array(29, ), - /* 42 */ array(7, ), + /* 41 */ array(7, ), + /* 42 */ array(29, ), /* 43 */ array(29, ), - /* 44 */ array(29, ), - /* 45 */ array(7, ), - /* 46 */ array(29, ), - /* 47 */ array(7, ), - /* 48 */ array(52, 53, ), - /* 49 */ array(52, 53, ), - /* 50 */ array(6, ), - /* 51 */ array(29, ), - /* 52 */ array(6, ), + /* 44 */ array(7, ), + /* 45 */ array(29, ), + /* 46 */ array(2, ), + /* 47 */ array(29, ), + /* 48 */ array(53, 54, ), + /* 49 */ array(53, 54, ), + /* 50 */ array(18, ), + /* 51 */ array(6, ), + /* 52 */ array(29, ), /* 53 */ array(6, ), /* 54 */ array(6, ), - /* 55 */ array(18, ), + /* 55 */ array(6, ), /* 56 */ array(9, 10, 11, 12, 13, 14, 15, 16, 17, ), /* 57 */ array(5, 19, ), - /* 58 */ array(3, 8, ), - /* 59 */ array(5, 19, ), - /* 60 */ array(4, 5, ), - /* 61 */ array(8, ), - /* 62 */ array(27, ), + /* 58 */ array(5, 19, ), + /* 59 */ array(4, 5, ), + /* 60 */ array(3, 8, ), + /* 61 */ array(1, ), + /* 62 */ array(8, ), /* 63 */ array(18, ), - /* 64 */ array(1, ), - /* 65 */ array(30, ), - /* 66 */ array(27, ), - /* 67 */ array(1, ), + /* 64 */ array(27, ), + /* 65 */ array(27, ), + /* 66 */ array(1, ), + /* 67 */ array(30, ), /* 68 */ array(), /* 69 */ array(), /* 70 */ array(), @@ -543,25 +546,26 @@ static public $yy_action = array( /* 165 */ array(), /* 166 */ array(), /* 167 */ array(), + /* 168 */ array(), ); static public $yy_default = array( - /* 0 */ 288, 213, 288, 288, 288, 288, 288, 288, 288, 288, - /* 10 */ 288, 288, 288, 288, 207, 206, 288, 204, 205, 288, - /* 20 */ 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, - /* 30 */ 183, 216, 211, 212, 195, 209, 208, 183, 288, 288, - /* 40 */ 288, 288, 182, 288, 288, 183, 288, 183, 202, 203, - /* 50 */ 180, 288, 180, 180, 180, 288, 288, 288, 288, 288, - /* 60 */ 288, 288, 228, 288, 171, 288, 288, 169, 218, 220, - /* 70 */ 219, 210, 245, 244, 260, 221, 172, 215, 217, 187, - /* 80 */ 230, 194, 193, 192, 185, 175, 170, 178, 176, 191, - /* 90 */ 190, 174, 214, 223, 181, 184, 189, 188, 186, 222, - /* 100 */ 253, 235, 236, 265, 234, 233, 232, 231, 266, 267, - /* 110 */ 272, 273, 271, 270, 268, 269, 229, 227, 247, 261, - /* 120 */ 262, 246, 200, 199, 198, 201, 197, 225, 226, 224, - /* 130 */ 264, 196, 263, 274, 275, 239, 240, 241, 168, 173, - /* 140 */ 259, 179, 242, 243, 255, 256, 254, 252, 250, 251, - /* 150 */ 258, 249, 280, 281, 282, 279, 278, 276, 277, 283, - /* 160 */ 284, 238, 248, 237, 287, 285, 286, 257, + /* 0 */ 290, 214, 290, 290, 290, 290, 290, 290, 290, 290, + /* 10 */ 290, 290, 290, 290, 207, 208, 290, 205, 206, 290, + /* 20 */ 290, 290, 290, 290, 290, 290, 290, 290, 290, 184, + /* 30 */ 290, 213, 196, 217, 212, 210, 184, 209, 184, 290, + /* 40 */ 290, 184, 290, 290, 183, 290, 290, 290, 203, 204, + /* 50 */ 290, 181, 290, 181, 181, 181, 290, 290, 290, 290, + /* 60 */ 290, 170, 290, 290, 229, 290, 172, 290, 179, 246, + /* 70 */ 245, 262, 216, 220, 211, 218, 219, 186, 221, 189, + /* 80 */ 231, 195, 194, 176, 193, 192, 191, 190, 188, 222, + /* 90 */ 187, 185, 177, 182, 175, 215, 224, 223, 169, 240, + /* 100 */ 259, 226, 271, 270, 269, 268, 267, 237, 236, 235, + /* 110 */ 234, 232, 233, 230, 228, 227, 225, 273, 200, 173, + /* 120 */ 174, 180, 197, 198, 199, 201, 266, 247, 248, 263, + /* 130 */ 264, 202, 265, 272, 274, 258, 249, 257, 256, 255, + /* 140 */ 254, 253, 252, 251, 244, 243, 242, 241, 261, 260, + /* 150 */ 250, 239, 275, 282, 276, 277, 278, 279, 280, 281, + /* 160 */ 283, 238, 284, 285, 286, 287, 288, 289, 171, ); /* The next thing included is series of defines which control ** various aspects of the generated parser. @@ -578,11 +582,11 @@ static public $yy_action = array( ** self::YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ - const YYNOCODE = 116; + const YYNOCODE = 117; const YYSTACKDEPTH = 100; - const YYNSTATE = 168; - const YYNRULE = 120; - const YYERRORSYMBOL = 74; + const YYNSTATE = 169; + const YYNRULE = 121; + const YYERRORSYMBOL = 75; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; /** The next table maps tokens into fallback tokens. If a construct @@ -675,24 +679,24 @@ static public $yy_action = array( 'HEXVAL', 'STRVAL', 'REGEXP', 'NOT_EQ', 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', 'GT', 'LT', 'GE', - 'LE', 'LIKE', 'NOT_LIKE', 'BITWISE_LEFT_SHIFT', - 'BITWISE_RIGHT_SHIFT', 'BITWISE_AND', 'BITWISE_OR', 'BITWISE_XOR', - 'IN', 'NOT_IN', 'F_IF', 'F_ELT', - 'F_COALESCE', 'F_ISNULL', 'F_CONCAT', 'F_SUBSTR', - 'F_TRIM', 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', - 'F_NOW', 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', - 'F_DATE_ADD', 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', - 'F_INET_ATON', 'F_INET_NTOA', 'error', 'result', - 'union', 'query', 'condition', 'class_name', - 'join_statement', 'where_statement', 'class_list', 'join_item', - 'join_condition', 'field_id', 'expression_prio4', 'expression_basic', - 'scalar', 'var_name', 'func_name', 'arg_list', - 'list_operator', 'list', 'expression_prio1', 'operator1', - 'expression_prio2', 'operator2', 'expression_prio3', 'operator3', - 'operator4', 'list_items', 'argument', 'interval_unit', - 'num_scalar', 'str_scalar', 'num_value', 'str_value', - 'name', 'num_operator1', 'bitwise_operator1', 'num_operator2', - 'str_operator', 'bitwise_operator3', 'bitwise_operator4', + 'LE', 'LIKE', 'NOT_LIKE', 'MATCHES', + 'BITWISE_LEFT_SHIFT', 'BITWISE_RIGHT_SHIFT', 'BITWISE_AND', 'BITWISE_OR', + 'BITWISE_XOR', 'IN', 'NOT_IN', 'F_IF', + 'F_ELT', 'F_COALESCE', 'F_ISNULL', 'F_CONCAT', + 'F_SUBSTR', 'F_TRIM', 'F_DATE', 'F_DATE_FORMAT', + 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', 'F_TO_DAYS', + 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', 'F_ROUND', + 'F_FLOOR', 'F_INET_ATON', 'F_INET_NTOA', 'error', + 'result', 'union', 'query', 'condition', + 'class_name', 'join_statement', 'where_statement', 'class_list', + 'join_item', 'join_condition', 'field_id', 'expression_prio4', + 'expression_basic', 'scalar', 'var_name', 'func_name', + 'arg_list', 'list_operator', 'list', 'expression_prio1', + 'operator1', 'expression_prio2', 'operator2', 'expression_prio3', + 'operator3', 'operator4', 'list_items', 'argument', + 'interval_unit', 'num_scalar', 'str_scalar', 'num_value', + 'str_value', 'name', 'num_operator1', 'bitwise_operator1', + 'num_operator2', 'str_operator', 'bitwise_operator3', 'bitwise_operator4', ); /** @@ -790,36 +794,37 @@ static public $yy_action = array( /* 87 */ "num_operator2 ::= LE", /* 88 */ "str_operator ::= LIKE", /* 89 */ "str_operator ::= NOT_LIKE", - /* 90 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", - /* 91 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", - /* 92 */ "bitwise_operator3 ::= BITWISE_AND", - /* 93 */ "bitwise_operator4 ::= BITWISE_OR", - /* 94 */ "bitwise_operator4 ::= BITWISE_XOR", - /* 95 */ "list_operator ::= IN", - /* 96 */ "list_operator ::= NOT_IN", - /* 97 */ "func_name ::= F_IF", - /* 98 */ "func_name ::= F_ELT", - /* 99 */ "func_name ::= F_COALESCE", - /* 100 */ "func_name ::= F_ISNULL", - /* 101 */ "func_name ::= F_CONCAT", - /* 102 */ "func_name ::= F_SUBSTR", - /* 103 */ "func_name ::= F_TRIM", - /* 104 */ "func_name ::= F_DATE", - /* 105 */ "func_name ::= F_DATE_FORMAT", - /* 106 */ "func_name ::= F_CURRENT_DATE", - /* 107 */ "func_name ::= F_NOW", - /* 108 */ "func_name ::= F_TIME", - /* 109 */ "func_name ::= F_TO_DAYS", - /* 110 */ "func_name ::= F_FROM_DAYS", - /* 111 */ "func_name ::= F_YEAR", - /* 112 */ "func_name ::= F_MONTH", - /* 113 */ "func_name ::= F_DAY", - /* 114 */ "func_name ::= F_DATE_ADD", - /* 115 */ "func_name ::= F_DATE_SUB", - /* 116 */ "func_name ::= F_ROUND", - /* 117 */ "func_name ::= F_FLOOR", - /* 118 */ "func_name ::= F_INET_ATON", - /* 119 */ "func_name ::= F_INET_NTOA", + /* 90 */ "str_operator ::= MATCHES", + /* 91 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", + /* 92 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", + /* 93 */ "bitwise_operator3 ::= BITWISE_AND", + /* 94 */ "bitwise_operator4 ::= BITWISE_OR", + /* 95 */ "bitwise_operator4 ::= BITWISE_XOR", + /* 96 */ "list_operator ::= IN", + /* 97 */ "list_operator ::= NOT_IN", + /* 98 */ "func_name ::= F_IF", + /* 99 */ "func_name ::= F_ELT", + /* 100 */ "func_name ::= F_COALESCE", + /* 101 */ "func_name ::= F_ISNULL", + /* 102 */ "func_name ::= F_CONCAT", + /* 103 */ "func_name ::= F_SUBSTR", + /* 104 */ "func_name ::= F_TRIM", + /* 105 */ "func_name ::= F_DATE", + /* 106 */ "func_name ::= F_DATE_FORMAT", + /* 107 */ "func_name ::= F_CURRENT_DATE", + /* 108 */ "func_name ::= F_NOW", + /* 109 */ "func_name ::= F_TIME", + /* 110 */ "func_name ::= F_TO_DAYS", + /* 111 */ "func_name ::= F_FROM_DAYS", + /* 112 */ "func_name ::= F_YEAR", + /* 113 */ "func_name ::= F_MONTH", + /* 114 */ "func_name ::= F_DAY", + /* 115 */ "func_name ::= F_DATE_ADD", + /* 116 */ "func_name ::= F_DATE_SUB", + /* 117 */ "func_name ::= F_ROUND", + /* 118 */ "func_name ::= F_FLOOR", + /* 119 */ "func_name ::= F_INET_ATON", + /* 120 */ "func_name ::= F_INET_NTOA", ); /** @@ -1184,126 +1189,127 @@ static public $yy_action = array( * */ static public $yyRuleInfo = array( - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 76, 'rhs' => 3 ), - array( 'lhs' => 76, 'rhs' => 3 ), - array( 'lhs' => 77, 'rhs' => 4 ), - array( 'lhs' => 77, 'rhs' => 6 ), - array( 'lhs' => 77, 'rhs' => 6 ), - array( 'lhs' => 77, 'rhs' => 8 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 3 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 77, 'rhs' => 3 ), + array( 'lhs' => 77, 'rhs' => 3 ), + array( 'lhs' => 78, 'rhs' => 4 ), + array( 'lhs' => 78, 'rhs' => 6 ), + array( 'lhs' => 78, 'rhs' => 6 ), + array( 'lhs' => 78, 'rhs' => 8 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 3 ), + array( 'lhs' => 82, 'rhs' => 2 ), + array( 'lhs' => 82, 'rhs' => 0 ), array( 'lhs' => 81, 'rhs' => 2 ), + array( 'lhs' => 81, 'rhs' => 1 ), array( 'lhs' => 81, 'rhs' => 0 ), - array( 'lhs' => 80, 'rhs' => 2 ), - array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 80, 'rhs' => 0 ), - array( 'lhs' => 83, 'rhs' => 6 ), - array( 'lhs' => 83, 'rhs' => 4 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 78, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 4 ), - array( 'lhs' => 87, 'rhs' => 3 ), - array( 'lhs' => 87, 'rhs' => 3 ), - array( 'lhs' => 94, 'rhs' => 1 ), - array( 'lhs' => 94, 'rhs' => 3 ), - array( 'lhs' => 96, 'rhs' => 1 ), - array( 'lhs' => 96, 'rhs' => 3 ), - array( 'lhs' => 98, 'rhs' => 1 ), - array( 'lhs' => 98, 'rhs' => 3 ), - array( 'lhs' => 86, 'rhs' => 1 ), - array( 'lhs' => 86, 'rhs' => 3 ), - array( 'lhs' => 93, 'rhs' => 3 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 3 ), - array( 'lhs' => 91, 'rhs' => 0 ), - array( 'lhs' => 91, 'rhs' => 1 ), - array( 'lhs' => 91, 'rhs' => 3 ), - array( 'lhs' => 102, 'rhs' => 1 ), - array( 'lhs' => 102, 'rhs' => 3 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 104, 'rhs' => 1 ), - array( 'lhs' => 105, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 6 ), + array( 'lhs' => 84, 'rhs' => 4 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), array( 'lhs' => 85, 'rhs' => 3 ), array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 4 ), + array( 'lhs' => 88, 'rhs' => 3 ), + array( 'lhs' => 88, 'rhs' => 3 ), + array( 'lhs' => 95, 'rhs' => 1 ), + array( 'lhs' => 95, 'rhs' => 3 ), + array( 'lhs' => 97, 'rhs' => 1 ), + array( 'lhs' => 97, 'rhs' => 3 ), + array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 99, 'rhs' => 3 ), + array( 'lhs' => 87, 'rhs' => 1 ), + array( 'lhs' => 87, 'rhs' => 3 ), + array( 'lhs' => 94, 'rhs' => 3 ), + array( 'lhs' => 102, 'rhs' => 1 ), + array( 'lhs' => 102, 'rhs' => 3 ), + array( 'lhs' => 92, 'rhs' => 0 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 3 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 3 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 108, 'rhs' => 1 ), - array( 'lhs' => 106, 'rhs' => 1 ), - array( 'lhs' => 106, 'rhs' => 2 ), + array( 'lhs' => 89, 'rhs' => 1 ), + array( 'lhs' => 105, 'rhs' => 1 ), array( 'lhs' => 106, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 3 ), + array( 'lhs' => 80, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 109, 'rhs' => 1 ), array( 'lhs' => 107, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 99, 'rhs' => 1 ), - array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 107, 'rhs' => 2 ), + array( 'lhs' => 107, 'rhs' => 1 ), + array( 'lhs' => 108, 'rhs' => 1 ), + array( 'lhs' => 96, 'rhs' => 1 ), + array( 'lhs' => 96, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), array( 'lhs' => 100, 'rhs' => 1 ), array( 'lhs' => 100, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 112, 'rhs' => 1 ), - array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 101, 'rhs' => 1 ), + array( 'lhs' => 101, 'rhs' => 1 ), array( 'lhs' => 110, 'rhs' => 1 ), array( 'lhs' => 110, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), array( 'lhs' => 114, 'rhs' => 1 ), - array( 'lhs' => 114, 'rhs' => 1 ), - array( 'lhs' => 92, 'rhs' => 1 ), - array( 'lhs' => 92, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 115, 'rhs' => 1 ), + array( 'lhs' => 115, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), ); /** @@ -1365,9 +1371,9 @@ static public $yy_action = array( 32 => 32, 33 => 33, 35 => 33, - 37 => 33, 39 => 33, 41 => 33, + 37 => 37, 42 => 42, 45 => 45, 49 => 49, @@ -1376,7 +1382,6 @@ static public $yy_action = array( 60 => 60, 61 => 61, 62 => 62, - 97 => 62, 98 => 62, 99 => 62, 100 => 62, @@ -1399,6 +1404,7 @@ static public $yy_action = array( 117 => 62, 118 => 62, 119 => 62, + 120 => 62, 63 => 63, 64 => 64, 65 => 65, @@ -1433,6 +1439,7 @@ static public $yy_action = array( 94 => 69, 95 => 69, 96 => 69, + 97 => 69, ); /* Beginning here are the reduction cases. A typical example ** follows: @@ -1440,171 +1447,183 @@ static public $yy_action = array( ** function yy_r0($yymsp){ ... } // User supplied code ** #line */ -#line 29 "..\oql-parser.y" +#line 29 "../oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1449 "..\oql-parser.php" -#line 33 "..\oql-parser.y" - function yy_r3(){ - $this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); +#line 1456 "../oql-parser.php" +#line 33 "../oql-parser.y" + function yy_r3(){ + $this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1454 "..\oql-parser.php" -#line 40 "..\oql-parser.y" - function yy_r5(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); +#line 1461 "../oql-parser.php" +#line 40 "../oql-parser.y" + function yy_r5(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1459 "..\oql-parser.php" -#line 43 "..\oql-parser.y" - function yy_r6(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); +#line 1466 "../oql-parser.php" +#line 43 "../oql-parser.y" + function yy_r6(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1464 "..\oql-parser.php" -#line 47 "..\oql-parser.y" - function yy_r7(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); +#line 1471 "../oql-parser.php" +#line 47 "../oql-parser.y" + function yy_r7(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); } -#line 1469 "..\oql-parser.php" -#line 50 "..\oql-parser.y" - function yy_r8(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); +#line 1476 "../oql-parser.php" +#line 50 "../oql-parser.y" + function yy_r8(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); } -#line 1474 "..\oql-parser.php" -#line 55 "..\oql-parser.y" - function yy_r9(){ - $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); +#line 1481 "../oql-parser.php" +#line 55 "../oql-parser.y" + function yy_r9(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); } -#line 1479 "..\oql-parser.php" -#line 58 "..\oql-parser.y" - function yy_r10(){ - array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); - $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; +#line 1486 "../oql-parser.php" +#line 58 "../oql-parser.y" + function yy_r10(){ + array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; } -#line 1485 "..\oql-parser.php" -#line 63 "..\oql-parser.y" +#line 1492 "../oql-parser.php" +#line 63 "../oql-parser.y" function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1488 "..\oql-parser.php" -#line 64 "..\oql-parser.y" +#line 1495 "../oql-parser.php" +#line 64 "../oql-parser.y" function yy_r12(){ $this->_retvalue = null; } -#line 1491 "..\oql-parser.php" -#line 66 "..\oql-parser.y" - function yy_r13(){ - // insert the join statement on top of the existing list - array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); - // and return the updated array - $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; +#line 1498 "../oql-parser.php" +#line 66 "../oql-parser.y" + function yy_r13(){ + // insert the join statement on top of the existing list + array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + // and return the updated array + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1499 "..\oql-parser.php" -#line 72 "..\oql-parser.y" - function yy_r14(){ - $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); +#line 1506 "../oql-parser.php" +#line 72 "../oql-parser.y" + function yy_r14(){ + $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1504 "..\oql-parser.php" -#line 78 "..\oql-parser.y" - function yy_r16(){ - // create an array with one single item - $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); +#line 1511 "../oql-parser.php" +#line 78 "../oql-parser.y" + function yy_r16(){ + // create an array with one single item + $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1510 "..\oql-parser.php" -#line 83 "..\oql-parser.y" - function yy_r17(){ - // create an array with one single item - $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); +#line 1517 "../oql-parser.php" +#line 83 "../oql-parser.y" + function yy_r17(){ + // create an array with one single item + $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1516 "..\oql-parser.php" -#line 88 "..\oql-parser.y" +#line 1523 "../oql-parser.php" +#line 88 "../oql-parser.y" function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1519 "..\oql-parser.php" -#line 89 "..\oql-parser.y" +#line 1526 "../oql-parser.php" +#line 89 "../oql-parser.y" function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1522 "..\oql-parser.php" -#line 90 "..\oql-parser.y" +#line 1529 "../oql-parser.php" +#line 90 "../oql-parser.y" function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1525 "..\oql-parser.php" -#line 91 "..\oql-parser.y" +#line 1532 "../oql-parser.php" +#line 91 "../oql-parser.y" function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1528 "..\oql-parser.php" -#line 92 "..\oql-parser.y" +#line 1535 "../oql-parser.php" +#line 92 "../oql-parser.y" function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1531 "..\oql-parser.php" -#line 93 "..\oql-parser.y" +#line 1538 "../oql-parser.php" +#line 93 "../oql-parser.y" function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1534 "..\oql-parser.php" -#line 94 "..\oql-parser.y" +#line 1541 "../oql-parser.php" +#line 94 "../oql-parser.y" function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1537 "..\oql-parser.php" -#line 95 "..\oql-parser.y" +#line 1544 "../oql-parser.php" +#line 95 "../oql-parser.y" function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1540 "..\oql-parser.php" -#line 96 "..\oql-parser.y" +#line 1547 "../oql-parser.php" +#line 96 "../oql-parser.y" function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1543 "..\oql-parser.php" -#line 98 "..\oql-parser.y" +#line 1550 "../oql-parser.php" +#line 98 "../oql-parser.y" function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1546 "..\oql-parser.php" -#line 103 "..\oql-parser.y" +#line 1553 "../oql-parser.php" +#line 103 "../oql-parser.y" function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1549 "..\oql-parser.php" -#line 104 "..\oql-parser.y" +#line 1556 "../oql-parser.php" +#line 104 "../oql-parser.y" function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1552 "..\oql-parser.php" -#line 105 "..\oql-parser.y" +#line 1559 "../oql-parser.php" +#line 105 "../oql-parser.y" function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1555 "..\oql-parser.php" -#line 120 "..\oql-parser.y" - function yy_r42(){ - $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); +#line 1562 "../oql-parser.php" +#line 111 "../oql-parser.y" + function yy_r37(){ + if ($this->yystack[$this->yyidx + -1]->minor == 'MATCHES') + { + $this->_retvalue = new MatchOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1560 "..\oql-parser.php" -#line 131 "..\oql-parser.y" - function yy_r45(){ - $this->_retvalue = array(); + else + { + $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1565 "..\oql-parser.php" -#line 142 "..\oql-parser.y" + } +#line 1574 "../oql-parser.php" +#line 129 "../oql-parser.y" + function yy_r42(){ + $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); + } +#line 1579 "../oql-parser.php" +#line 140 "../oql-parser.y" + function yy_r45(){ + $this->_retvalue = array(); + } +#line 1584 "../oql-parser.php" +#line 151 "../oql-parser.y" function yy_r49(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1568 "..\oql-parser.php" -#line 154 "..\oql-parser.y" +#line 1587 "../oql-parser.php" +#line 163 "../oql-parser.y" function yy_r58(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1571 "..\oql-parser.php" -#line 157 "..\oql-parser.y" +#line 1590 "../oql-parser.php" +#line 166 "../oql-parser.y" function yy_r60(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1574 "..\oql-parser.php" -#line 158 "..\oql-parser.y" +#line 1593 "../oql-parser.php" +#line 167 "../oql-parser.y" function yy_r61(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1577 "..\oql-parser.php" -#line 159 "..\oql-parser.y" +#line 1596 "../oql-parser.php" +#line 168 "../oql-parser.y" function yy_r62(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1580 "..\oql-parser.php" -#line 162 "..\oql-parser.y" +#line 1599 "../oql-parser.php" +#line 171 "../oql-parser.y" function yy_r63(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } -#line 1583 "..\oql-parser.php" -#line 164 "..\oql-parser.y" - function yy_r64(){ - if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') - { - $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); - } - else - { - $name = $this->yystack[$this->yyidx + 0]->minor; - } - $this->_retvalue = new OqlName($name, $this->m_iColPrev); +#line 1602 "../oql-parser.php" +#line 173 "../oql-parser.y" + function yy_r64(){ + if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') + { + $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); + } + else + { + $name = $this->yystack[$this->yyidx + 0]->minor; + } + $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1596 "..\oql-parser.php" -#line 175 "..\oql-parser.y" +#line 1615 "../oql-parser.php" +#line 184 "../oql-parser.y" function yy_r65(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; } -#line 1599 "..\oql-parser.php" -#line 176 "..\oql-parser.y" +#line 1618 "../oql-parser.php" +#line 185 "../oql-parser.y" function yy_r66(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; } -#line 1602 "..\oql-parser.php" -#line 177 "..\oql-parser.y" +#line 1621 "../oql-parser.php" +#line 186 "../oql-parser.y" function yy_r67(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); } -#line 1605 "..\oql-parser.php" -#line 178 "..\oql-parser.y" +#line 1624 "../oql-parser.php" +#line 187 "../oql-parser.y" function yy_r68(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1608 "..\oql-parser.php" -#line 181 "..\oql-parser.y" +#line 1627 "../oql-parser.php" +#line 190 "../oql-parser.y" function yy_r69(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1611 "..\oql-parser.php" +#line 1630 "../oql-parser.php" /** * placeholder for the left hand side in a reduce operation. @@ -1716,10 +1735,10 @@ static public $yy_action = array( */ function yy_syntax_error($yymajor, $TOKEN) { -#line 25 "..\oql-parser.y" - -throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1727 "..\oql-parser.php" +#line 25 "../oql-parser.y" + +throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); +#line 1746 "../oql-parser.php" } /** @@ -1886,69 +1905,69 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); } } -#line 239 "..\oql-parser.y" - - -class OQLParserException extends OQLException -{ - public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue) - { - $sIssue = "Unexpected token $sTokenName"; - - parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue); - } -} - -class OQLParser extends OQLParserRaw -{ - // dirty, but working for us (no other mean to get the final result :-( - protected $my_result; - - public function GetResult() - { - return $this->my_result; - } - - // More info on the source query and the current position while parsing it - // Data used when an exception is raised - protected $m_iLine; // still not used - protected $m_iCol; - protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token - protected $m_sSourceQuery; - - public function __construct($sQuery) - { - $this->m_iLine = 0; - $this->m_iCol = 0; - $this->m_iColPrev = 0; - $this->m_sSourceQuery = $sQuery; - // no constructor - parent::__construct(); - } - - public function doParse($token, $value, $iCurrPosition = 0) - { - $this->m_iColPrev = $this->m_iCol; - $this->m_iCol = $iCurrPosition; - - return parent::DoParse($token, $value); - } - - public function doFinish() - { - $this->doParse(0, 0); - return $this->my_result; - } - - public function __destruct() - { - // Bug in the original destructor, causing an infinite loop ! - // This is a real issue when a fatal error occurs on the first token (the error could not be seen) - if (is_null($this->yyidx)) - { - $this->yyidx = -1; - } - parent::__destruct(); - } -} - -#line 1960 "..\oql-parser.php" +#line 255 "../oql-parser.y" + + +class OQLParserException extends OQLException +{ + public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue) + { + $sIssue = "Unexpected token $sTokenName"; + + parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue); + } +} + +class OQLParser extends OQLParserRaw +{ + // dirty, but working for us (no other mean to get the final result :-( + protected $my_result; + + public function GetResult() + { + return $this->my_result; + } + + // More info on the source query and the current position while parsing it + // Data used when an exception is raised + protected $m_iLine; // still not used + protected $m_iCol; + protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token + protected $m_sSourceQuery; + + public function __construct($sQuery) + { + $this->m_iLine = 0; + $this->m_iCol = 0; + $this->m_iColPrev = 0; + $this->m_sSourceQuery = $sQuery; + // no constructor - parent::__construct(); + } + + public function doParse($token, $value, $iCurrPosition = 0) + { + $this->m_iColPrev = $this->m_iCol; + $this->m_iCol = $iCurrPosition; + + return parent::DoParse($token, $value); + } + + public function doFinish() + { + $this->doParse(0, 0); + return $this->my_result; + } + + public function __destruct() + { + // Bug in the original destructor, causing an infinite loop ! + // This is a real issue when a fatal error occurs on the first token (the error could not be seen) + if (is_null($this->yyidx)) + { + $this->yyidx = -1; + } + parent::__destruct(); + } +} + +#line 1979 "../oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index 79dfb31398..c2603402b0 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -16,7 +16,7 @@ Example: %left TIMES DIVIDE MOD. %right EXP NOT. -TODO : solve the 2 remaining shift-reduce conflicts (JOIN) +later : solve the 2 remaining shift-reduce conflicts (JOIN) */ @@ -108,7 +108,16 @@ expression_prio1(A) ::= expression_basic(X). { A = X; } expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio2(A) ::= expression_prio1(X). { A = X; } -expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); } +expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z).{ + if (Y == 'MATCHES') + { + A = new MatchOqlExpression(X, Z); + } + else + { + A = new BinaryOqlExpression(X, Y, Z); + } +} expression_prio3(A) ::= expression_prio2(X). { A = X; } expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); } @@ -180,18 +189,22 @@ str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));} operator1(A) ::= num_operator1(X). {A=X;} operator1(A) ::= bitwise_operator1(X). {A=X;} + operator2(A) ::= num_operator2(X). {A=X;} operator2(A) ::= str_operator(X). {A=X;} operator2(A) ::= REGEXP(X). {A=X;} operator2(A) ::= EQ(X). {A=X;} operator2(A) ::= NOT_EQ(X). {A=X;} + operator3(A) ::= LOG_AND(X). {A=X;} operator3(A) ::= bitwise_operator3(X). {A=X;} + operator4(A) ::= LOG_OR(X). {A=X;} operator4(A) ::= bitwise_operator4(X). {A=X;} num_operator1(A) ::= MATH_DIV(X). {A=X;} num_operator1(A) ::= MATH_MULT(X). {A=X;} + num_operator2(A) ::= MATH_PLUS(X). {A=X;} num_operator2(A) ::= MATH_MINUS(X). {A=X;} num_operator2(A) ::= GT(X). {A=X;} @@ -201,10 +214,13 @@ num_operator2(A) ::= LE(X). {A=X;} str_operator(A) ::= LIKE(X). {A=X;} str_operator(A) ::= NOT_LIKE(X). {A=X;} +str_operator(A) ::= MATCHES(X). {A=X;} bitwise_operator1(A) ::= BITWISE_LEFT_SHIFT(X). {A=X;} bitwise_operator1(A) ::= BITWISE_RIGHT_SHIFT(X). {A=X;} + bitwise_operator3(A) ::= BITWISE_AND(X). {A=X;} + bitwise_operator4(A) ::= BITWISE_OR(X). {A=X;} bitwise_operator4(A) ::= BITWISE_XOR(X). {A=X;} diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index 5fbcc8aeda..3747c01f49 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -160,6 +160,26 @@ class BinaryOqlExpression extends BinaryExpression implements CheckableExpressio } } +class MatchOqlExpression extends MatchExpression implements CheckableExpression +{ + public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) + { + $this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery); + $this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery); + + // Only field MATCHES scalar is allowed + if (!$this->m_oLeftExpr instanceof FieldExpression) + { + throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0)); + } + // Only field MATCHES scalar is allowed + if (!$this->m_oRightExpr instanceof ScalarExpression) + { + throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0)); + } + } +} + class ScalarOqlExpression extends ScalarExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) diff --git a/core/oql/version.txt b/core/oql/version.txt index 25ada144d7..052f0a1a18 100644 --- a/core/oql/version.txt +++ b/core/oql/version.txt @@ -1 +1 @@ -2015-08-31 +2018-08-31 \ No newline at end of file diff --git a/core/ormset.class.inc.php b/core/ormset.class.inc.php new file mode 100644 index 0000000000..4578bbdf0a --- /dev/null +++ b/core/ormset.class.inc.php @@ -0,0 +1,380 @@ + + * + */ + +/** + * Created by PhpStorm. + * Date: 24/08/2018 + * Time: 14:35 + */ +class ormSet +{ + protected $sClass; // class of the field + protected $sAttCode; // attcode of the field + protected $aOriginalObjects = null; + + /** + * Object from the original set, minus the removed objects + */ + protected $aPreserved = array(); + + /** + * New items + */ + protected $aAdded = array(); + + /** + * Removed items + */ + protected $aRemoved = array(); + + /** + * Modified items (mass edit) + */ + protected $aModified = array(); + + /** + * @var int Max number of tags in collection + */ + protected $iLimit; + + /** + * __toString magical function overload. + */ + public function __toString() + { + $aValue = $this->GetValues(); + if (!empty($aValue)) + { + return implode(', ', $aValue); + } + else + { + return ' '; + } + } + + /** + * ormSet constructor. + * + * @param string $sClass + * @param string $sAttCode + * @param int $iLimit + * + * @throws \Exception + */ + public function __construct($sClass, $sAttCode, $iLimit = 12) + { + $this->sAttCode = $sAttCode; + + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!$oAttDef instanceof AttributeSet) + { + throw new Exception("ormSet: field {$sClass}:{$sAttCode} is not a set"); + } + $this->sClass = $sClass; + $this->iLimit = $iLimit; + } + + /** + * @return string + */ + public function GetClass() + { + return $this->sClass; + } + + /** + * @return string + */ + public function GetAttCode() + { + return $this->sAttCode; + } + + /** + * + * @param array $aItems + * + * @throws \CoreException + * @throws \CoreUnexpectedValue when a code is invalid + */ + public function SetValues($aItems) + { + if (!is_array($aItems)) + { + throw new CoreUnexpectedValue("Wrong value {$aItems} for {$this->sClass}:{$this->sAttCode}"); + } + + $oValues = array(); + $iCount = 0; + $bError = false; + foreach($aItems as $oItem) + { + $iCount++; + if (($this->iLimit != 0) && ($iCount > $this->iLimit)) + { + $bError = true; + continue; + } + $oValues[] = $oItem; + } + + $this->aPreserved = &$oValues; + $this->aRemoved = array(); + $this->aAdded = array(); + $this->aModified = array(); + $this->aOriginalObjects = $oValues; + + if ($bError) + { + throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + } + + public function Count() + { + return count($this->aPreserved) + count($this->aAdded) - count($this->aRemoved); + } + + /** + * @return array of codes + */ + public function GetValues() + { + $aValues = array_merge($this->aPreserved, $this->aAdded); + + return $aValues; + } + + /** + * @return array of tag labels indexed by code for only the added tags + */ + private function GetAdded() + { + return $this->aAdded; + } + + /** + * @return array of tag labels indexed by code for only the removed tags + */ + private function GetRemoved() + { + return $this->aRemoved; + } + + /** Get the delta with another ItemSet + * + * $aDelta['added] = array of tag codes for only the added tags + * $aDelta['removed'] = array of tag codes for only the removed tags + * + * @param \ormSet $oOtherSet + * + * @return array + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function GetDelta(ormSet $oOtherSet) + { + $oSet = new ormSet($this->sClass, $this->sAttCode); + // Set the initial value + $aOrigItems = $this->GetValues(); + $oSet->SetValues($aOrigItems); + + // now remove everything + foreach($aOrigItems as $oItem) + { + $oSet->Remove($oItem); + } + + // now add the tags of the other ItemSet + foreach($oOtherSet->GetValues() as $oItem) + { + $oSet->Add($oItem); + } + + $aDelta = array(); + $aDelta['added'] = $oSet->GetAdded(); + $aDelta['removed'] = $oSet->GetRemoved(); + + return $aDelta; + } + + /** + * @return string[] list of codes for partial entries + */ + public function GetModified() + { + return $this->aModified; + } + + /** + * Apply a delta to the current ItemSet + * $aDelta['added] = array of added items + * $aDelta['removed'] = array of removed items + * + * @param $aDelta + * + * @throws \CoreException + */ + public function ApplyDelta($aDelta) + { + if (isset($aDelta['removed'])) + { + foreach($aDelta['removed'] as $oItem) + { + $this->Remove($oItem); + } + } + if (isset($aDelta['added'])) + { + foreach($aDelta['added'] as $oItem) + { + $this->Add($oItem); + } + } + + // Reset the object + $this->SetValues($this->GetValues()); + } + + /** + * @param string $oItem + * + * @throws \CoreException + */ + public function Add($oItem) + { + if ($this->Count() === $this->iLimit) + { + throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + if ($this->IsItemInList($this->aPreserved, $oItem) || $this->IsItemInList($this->aAdded, $oItem)) + { + // nothing to do, already existing tag + return; + } + // if removed and added again + if (($this->RemoveItemFromList($this->aRemoved, $oItem)) !== false) + { + // put it back into preserved + $this->aPreserved[] = $oItem; + // no need to add it to aModified : was already done when calling RemoveItem method + } + else + { + $this->aAdded[] = $oItem; + $this->aModified[] = $oItem; + } + } + + /** + * @param $oItem + */ + public function Remove($oItem) + { + if ($this->IsItemInList($this->aRemoved, $oItem)) + { + // nothing to do, already removed tag + return; + } + + if ($this->RemoveItemFromList($this->aAdded, $oItem) !== false) + { + $this->aModified[] = $oItem; + + return; // if present in added, can't be in preserved ! + } + + if ($this->RemoveItemFromList($this->aPreserved, $oItem) !== false) + { + $this->aModified[] = $oItem; + $this->aRemoved[] = $oItem; + } + } + + private function IsItemInList($aItemList, $oItem) + { + return in_array($oItem, $aItemList); + } + + /** + * @param \DBObject[] $aItemList + * @param $oItem + * + * @return bool|\DBObject false if not found, else the removed element + */ + private function RemoveItemFromList(&$aItemList, $oItem) + { + if (!($this->IsItemInList($aItemList, $oItem))) + { + return false; + } + foreach ($aItemList as $index => $value) + { + if ($value === $oItem) + { + unset($aItemList[$index]); + return $oItem; + } + } + + return false; + } + + /** + * Populates the added and removed arrays for bulk edit + * + * @param string[] $aItems + * + * @throws \CoreException + */ + public function GenerateDiffFromArray($aItems) + { + foreach($this->GetValues() as $oCurrentItem) + { + if (!in_array($oCurrentItem, $aItems)) + { + $this->Remove($oCurrentItem); + } + } + + foreach($aItems as $oNewItem) + { + $this->Add($oNewItem); + } + } + + /** + * Compare Item Set + * + * @param \ormSet $other + * + * @return bool true if same tag set + */ + public function Equals(ormSet $other) + { + return implode(', ', $this->GetValue()) === implode(', ', $other->GetValue()); + } + + +} \ No newline at end of file diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php new file mode 100644 index 0000000000..ab4cdfdf60 --- /dev/null +++ b/core/ormtagset.class.inc.php @@ -0,0 +1,478 @@ + + * + */ + +/** + * Created by PhpStorm. + * Date: 24/08/2018 + * Time: 14:35 + */ +final class ormTagSet extends ormSet +{ + /** + * ormTagSet constructor. + * + * @param string $sClass + * @param string $sAttCode + * @param int $iLimit + * + * @throws \Exception + */ + public function __construct($sClass, $sAttCode, $iLimit = 12) + { + parent::__construct($sClass, $sAttCode, $iLimit); + } + + /** + * + * @param array $aTagCodes + * + * @throws \CoreException + * @throws \CoreUnexpectedValue when a code is invalid + */ + public function SetValues($aTagCodes) + { + if (!is_array($aTagCodes)) + { + throw new CoreUnexpectedValue("Wrong value {$aTagCodes} for {$this->sClass}:{$this->sAttCode}"); + } + + $oTags = array(); + $iCount = 0; + $bError = false; + foreach($aTagCodes as $sTagCode) + { + $iCount++; + if (($this->iLimit != 0) && ($iCount > $this->iLimit)) + { + $bError = true; + continue; + } + $oTag = $this->GetTagFromCode($sTagCode); + $oTags[$sTagCode] = $oTag; + } + + $this->aPreserved = &$oTags; + $this->aRemoved = array(); + $this->aAdded = array(); + $this->aModified = array(); + $this->aOriginalObjects = $oTags; + + if ($bError) + { + throw new CoreException("Maximum number of tags ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + } + + /** + * @return array of tag codes + */ + public function GetValues() + { + $aValues = array(); + foreach($this->aPreserved as $sTagCode => $oTag) + { + $aValues[] = $sTagCode; + } + foreach($this->aAdded as $sTagCode => $oTag) + { + $aValues[] = $sTagCode; + } + + sort($aValues); + + return $aValues; + } + + /** + * @return array of tag labels indexed by code + */ + public function GetLabels() + { + $aTags = array(); + /** @var \TagSetFieldData $oTag */ + foreach($this->aPreserved as $sTagCode => $oTag) + { + try + { + $aTags[$sTagCode] = $oTag->Get('label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + foreach($this->aAdded as $sTagCode => $oTag) + { + try + { + $aTags[$sTagCode] = $oTag->Get('label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + ksort($aTags); + + return $aTags; + } + + /** + * @return array of tags indexed by code + */ + public function GetTags() + { + $aTags = array(); + foreach($this->aPreserved as $sTagCode => $oTag) + { + $aTags[$sTagCode] = $oTag; + } + foreach($this->aAdded as $sTagCode => $oTag) + { + $aTags[$sTagCode] = $oTag; + } + ksort($aTags); + + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the added tags + */ + private function GetAddedCodes() + { + $aTags = array(); + foreach($this->aAdded as $sTagCode => $oTag) + { + $aTags[] = $sTagCode; + } + ksort($aTags); + + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the removed tags + */ + private function GetRemovedCodes() + { + $aTags = array(); + foreach($this->aRemoved as $sTagCode => $oTag) + { + $aTags[] = $sTagCode; + } + ksort($aTags); + + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the added tags + */ + private function GetAddedTags() + { + $aTags = array(); + foreach($this->aAdded as $sTagCode => $oTag) + { + $aTags[$sTagCode] = $oTag; + } + ksort($aTags); + + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the removed tags + */ + private function GetRemovedTags() + { + $aTags = array(); + foreach($this->aRemoved as $sTagCode => $oTag) + { + $aTags[$sTagCode] = $oTag; + } + ksort($aTags); + + return $aTags; + } + + /** Get the delta with another TagSet + * + * $aDelta['added] = array of tag codes for only the added tags + * $aDelta['removed'] = array of tag codes for only the removed tags + * + * @param \ormTagSet $oOtherTagSet + * + * @return array + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function GetDelta(ormSet $oOtherTagSet) + { + $oTag = new ormTagSet($this->sClass, $this->sAttCode); + // Set the initial value + $aOrigTagCodes = $this->GetValues(); + $oTag->SetValues($aOrigTagCodes); + // now remove everything + foreach($aOrigTagCodes as $sTagCode) + { + $oTag->Remove($sTagCode); + } + // now add the tags of the other TagSet + foreach($oOtherTagSet->GetValues() as $sTagCode) + { + $oTag->Add($sTagCode); + } + $aDelta = array(); + $aDelta['added'] = $oTag->GetAddedCodes(); + $aDelta['removed'] = $oTag->GetRemovedCodes(); + + return $aDelta; + } + + /** Get the delta with another TagSet + * + * $aDelta['added] = array of tag labels indexed by code for only the added tags + * $aDelta['removed'] = array of tag labels indexed by code for only the removed tags + * + * @param \ormTagSet $oOtherTagSet + * + * @return array + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function GetDeltaTags(ormTagSet $oOtherTagSet) + { + $oTag = new ormTagSet($this->sClass, $this->sAttCode); + // Set the initial value + $aOrigTagCodes = $this->GetValues(); + $oTag->SetValues($aOrigTagCodes); + // now remove everything + foreach($aOrigTagCodes as $sTagCode) + { + $oTag->Remove($sTagCode); + } + // now add the tags of the other TagSet + foreach($oOtherTagSet->GetValues() as $sTagCode) + { + $oTag->Add($sTagCode); + } + $aDelta = array(); + $aDelta['added'] = $oTag->GetAddedTags(); + $aDelta['removed'] = $oTag->GetRemovedTags(); + + return $aDelta; + } + + /** + * @return string[] list of codes for partial entries + */ + public function GetModified() + { + $aModifiedTagCodes = array_keys($this->aModified); + sort($aModifiedTagCodes); + + return $aModifiedTagCodes; + } + + /** + * Check whether a tag code is valid or not for this TagSet + * + * @param string $sTagCode + * + * @return bool + */ + public function IsValidTag($sTagCode) + { + try + { + $this->GetTagFromCode($sTagCode); + + return true; + } catch (Exception $e) + { + return false; + } + } + + /** + * @param string $sTagCode + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function Add($sTagCode) + { + if ($this->Count() === $this->iLimit) + { + throw new CoreException("Maximum number of tags ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}"); + } + if ($this->IsTagInList($this->aPreserved, $sTagCode) || $this->IsTagInList($this->aAdded, $sTagCode)) + { + // nothing to do, already existing tag + return; + } + // if removed then added again + if (($oTag = $this->RemoveTagFromList($this->aRemoved, $sTagCode)) !== false) + { + // put it back into preserved + $this->aPreserved[$sTagCode] = $oTag; + // no need to add it to aModified : was already done when calling Remove method + } + else + { + $oTag = $this->GetTagFromCode($sTagCode); + $this->aAdded[$sTagCode] = $oTag; + $this->aModified[$sTagCode] = $oTag; + } + } + + /** + * @param $sTagCode + */ + public function Remove($sTagCode) + { + if ($this->IsTagInList($this->aRemoved, $sTagCode)) + { + // nothing to do, already removed tag + return; + } + + $oTag = $this->RemoveTagFromList($this->aAdded, $sTagCode); + if ($oTag !== false) + { + $this->aModified[$sTagCode] = $oTag; + + return; // if present in added, can't be in preserved ! + } + + $oTag = $this->RemoveTagFromList($this->aPreserved, $sTagCode); + if ($oTag !== false) + { + $this->aModified[$sTagCode] = $oTag; + $this->aRemoved[$sTagCode] = $oTag; + } + } + + private function IsTagInList($aTagList, $sTagCode) + { + return isset($aTagList[$sTagCode]); + } + + /** + * @param \DBObject[] $aTagList + * @param string $sTagCode + * + * @return bool|\DBObject false if not found, else the removed element + */ + private function RemoveTagFromList(&$aTagList, $sTagCode) + { + if (!($this->IsTagInList($aTagList, $sTagCode))) + { + return false; + } + + $oTag = $aTagList[$sTagCode]; + unset($aTagList[$sTagCode]); + + return $oTag; + } + + /** + * @param $sTagCode + * + * @return DBObject tag + * @throws \CoreUnexpectedValue + * @throws \CoreException + */ + private function GetTagFromCode($sTagCode) + { + $aAllowedTags = $this->GetAllowedTags(); + foreach($aAllowedTags as $oAllowedTag) + { + if ($oAllowedTag->Get('code') === $sTagCode) + { + return $oAllowedTag; + } + } + throw new CoreUnexpectedValue("{$sTagCode} is not defined as a valid tag for {$this->sClass}:{$this->sAttCode}"); + } + + /** + * @param $sTagCode + * + * @return DBObject tag + * @throws \CoreUnexpectedValue + * @throws \CoreException + */ + public function GetTagFromLabel($sTagLabel) + { + $aAllowedTags = $this->GetAllowedTags(); + foreach($aAllowedTags as $oAllowedTag) + { + if ($oAllowedTag->Get('label') === $sTagLabel) + { + return $oAllowedTag->Get('code'); + } + } + throw new CoreUnexpectedValue("{$sTagLabel} is not defined as a valid tag for {$this->sClass}:{$this->sAttCode}"); + } + + /** + * @return \TagSetFieldData[] + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException + */ + private function GetAllowedTags() + { + return TagSetFieldData::GetAllowedValues($this->sClass, $this->sAttCode); + } + + /** + * Compare Tag Set + * + * @param \ormTagSet $other + * + * @return bool true if same tag set + */ + public function Equals(ormSet $other) + { + if (!($other instanceof ormTagSet)) + { + return false; + } + if ($this->GetTagDataClass() !== $other->GetTagDataClass()) + { + return false; + } + + return implode(' ', $this->GetValues()) === implode(' ', $other->GetValues()); + } + + public function GetTagDataClass() + { + return TagSetFieldData::GetTagDataClassName($this->sClass, $this->sAttCode); + } + +} \ No newline at end of file diff --git a/core/tagfield.class.inc.php b/core/tagfield.class.inc.php deleted file mode 100644 index a8a785fb98..0000000000 --- a/core/tagfield.class.inc.php +++ /dev/null @@ -1,81 +0,0 @@ - - - -/** - *

    Stores data for {@link AttributeTagSet} fields - * - *

    We will have an implementation for each class/field to be able to customize rights (generated in \MFCompiler::CompileClass).
    - * Only this abstract class will exists in the DB : the implementations won't had any new field. - * - * @since 2.6 N°931 tag fields - */ -abstract class TagSetFieldData extends cmdbAbstractObject -{ - public static function Init() - { - $aParams = array - ( - 'category' => 'bizmodel', - 'key_type' => 'autoincrement', - 'name_attcode' => array('tag_label'), - 'state_attcode' => '', - 'reconc_keys' => array('tag_code'), - 'db_table' => 'priv_tagfielddata', - 'db_key_field' => 'id', - 'db_finalclass_field' => 'finalclass', - ); - - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); - - MetaModel::Init_AddAttribute(new AttributeString("tag_code", array( - "allowed_values" => null, - "sql" => 'tag_code', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeString("tag_label", array( - "allowed_values" => null, - "sql" => 'tag_label', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeString("tag_description", array( - "allowed_values" => null, - "sql" => 'tag_description', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeBoolean("is_default", array( - "allowed_values" => null, - "sql" => "is_default", - "default_value" => false, - "is_null_allowed" => false, - "depends_on" => array() - ))); - - - MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - } -} \ No newline at end of file diff --git a/core/tagsetfield.class.inc.php b/core/tagsetfield.class.inc.php new file mode 100644 index 0000000000..a82a6e3950 --- /dev/null +++ b/core/tagsetfield.class.inc.php @@ -0,0 +1,385 @@ + + + +/** + *

    Stores data for {@link AttributeTagSet} fields + * + *

    We will have an implementation for each class/field to be able to customize rights (generated in + * \MFCompiler::CompileClass).
    Only this abstract class will exists in the DB : the implementations won't had any + * new field. + * + * @since 2.6 N°931 tag fields + */ +abstract class TagSetFieldData extends cmdbAbstractObject +{ + private static $m_aAllowedValues = array(); + + /** + * @throws \CoreException + * @throws \Exception + */ + public static function Init() + { + $aParams = array + ( + 'category' => 'bizmodel', + 'key_type' => 'autoincrement', + 'name_attcode' => array('label'), + 'state_attcode' => '', + 'reconc_keys' => array('code'), + 'db_table' => 'priv_tagfielddata', + 'db_key_field' => 'id', + 'db_finalclass_field' => 'finalclass', + ); + + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + MetaModel::Init_AddAttribute(new AttributeString("code", array( + "allowed_values" => null, + "sql" => 'code', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array(), + "validation_pattern" => '^[a-zA-Z0-9]{3,}$', + ))); + MetaModel::Init_AddAttribute(new AttributeString("label", array( + "allowed_values" => null, + "sql" => 'label', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeHTML("description", array( + "allowed_values" => null, + "sql" => 'description', + "default_value" => '', + "is_null_allowed" => true, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("obj_class", array( + "allowed_values" => null, + "sql" => 'obj_class', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("obj_attcode", array( + "allowed_values" => null, + "sql" => 'obj_attcode', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + + + MetaModel::Init_SetZListItems('details', array('code', 'label', 'description')); + MetaModel::Init_SetZListItems('standard_search', array('code', 'label', 'description')); + MetaModel::Init_SetZListItems('list', array('code', 'label', 'description')); + } + + public function ComputeValues() + { + $sClassName = get_class($this); + $aRes = static::ExtractTagFieldName($sClassName); + $this->_Set('obj_class', $aRes['obj_class']); + $this->_Set('obj_attcode', $aRes['obj_attcode']); + } + + public static function GetTagDataClassName($sClass, $sAttCode) + { + $sTagSuffix = $sClass.'__'.$sAttCode; + + return 'TagSetFieldDataFor_'.$sTagSuffix; + } + + /** + * Extract Tag class and attcode from the TagFieldData class name + * + * @param $sClassName + * + * @return string[] + * @throws \CoreException + */ + public static function ExtractTagFieldName($sClassName) + { + $aRes = array(); + // Extract class and attcode from class name using pattern TagSetFieldDataFor__>; + if (preg_match('@^TagSetFieldDataFor_(?\w+)__(?\w+)$@', $sClassName, $aMatches)) + { + $aRes['obj_class'] = $aMatches['class']; + $aRes['obj_attcode'] = $aMatches['attcode']; + } + else + { + throw new CoreException("Bad Class name format: $sClassName"); + } + return $aRes; + } + + /** + * @param \DeletionPlan $oDeletionPlan + * + * @throws \CoreException + */ + public function DoCheckToDelete(&$oDeletionPlan) + { + parent::DoCheckToDelete($oDeletionPlan); + + $sTagCode = $this->Get('code'); + if ($this->IsCodeUsed($sTagCode)) + { + $this->m_aDeleteIssues[] = Dict::S('Core:TagSetFieldData:ErrorDeleteUsedTag'); + } + // Clear cache + $sClass = $this->Get('obj_class'); + $sAttCode = $this->Get('obj_attcode'); + $sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode); + unset(self::$m_aAllowedValues[$sTagDataClass]); + } + + /** + * @throws \CoreException + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + * @throws \Exception + */ + public function DoCheckToWrite() + { + $this->ComputeValues(); + $sClass = $this->Get('obj_class'); + $sAttCode = $this->Get('obj_attcode'); + $iMaxLen = 20; + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef instanceof AttributeTagSet) + { + $iMaxLen = $oAttDef->GetTagCodeMaxLength(); + } + + $sTagCode = $this->Get('code'); + // Check code syntax + if (!preg_match("@^[a-zA-Z0-9]{3,$iMaxLen}$@", $sTagCode)) + { + $this->m_aCheckIssues[] = Dict::Format('Core:TagSetFieldData:ErrorTagCodeSyntax', $iMaxLen); + } + + // Check that the code is not a MySQL stop word + $sSQL = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD"; + try + { + $aResults = CMDBSource::QueryToArray($sSQL); + } catch (MySQLException $e) + { + IssueLog::Warning($e->getMessage()); + $aResults = array(); + } + + foreach($aResults as $aResult) + { + if ($aResult['value'] == $sTagCode) + { + $this->m_aCheckIssues[] = Dict::S('Core:TagSetFieldData:ErrorTagCodeReservedWord'); + break; + } + } + + $sTagLabel = $this->Get('label'); + $sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator'); + if (empty($sTagLabel) || (strpos($sTagLabel, $sSepItem) !== false)) + { + // Label must not contain | character + $this->m_aCheckIssues[] = Dict::Format('Core:TagSetFieldData:ErrorTagLabelSyntax', $sSepItem); + } + + // Check that code and labels are uniques + $id = $this->GetKey(); + $sClassName = get_class($this); + if (empty($id)) + { + $oSearch = DBSearch::FromOQL("SELECT $sClassName WHERE (code = :tag_code OR label = :tag_label)"); + } + else + { + $oSearch = DBSearch::FromOQL("SELECT $sClassName WHERE id != :id AND (code = :tag_code OR label = :tag_label)"); + } + $aArgs = array('id' => $id, 'tag_code' => $sTagCode, 'tag_label' => $sTagLabel); + $oSet = new DBObjectSet($oSearch, array(), $aArgs); + if ($oSet->CountExceeds(0)) + { + $this->m_aCheckIssues[] = Dict::S('Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel'); + } + // Clear cache + $sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode); + unset(self::$m_aAllowedValues[$sTagDataClass]); + + parent::DoCheckToWrite(); + } + + /** + * @throws \CoreException + */ + public function OnUpdate() + { + parent::OnUpdate(); + $aChanges = $this->ListChanges(); + if (array_key_exists('code', $aChanges)) + { + $sTagCode = $this->GetOriginal('code'); + if ($this->IsCodeUsed($sTagCode)) + { + throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorCodeUpdateNotAllowed')); + } + } + if (array_key_exists('obj_class', $aChanges)) + { + throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorClassUpdateNotAllowed')); + } + if (array_key_exists('obj_attcode', $aChanges)) + { + throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed')); + } + } + + private function IsCodeUsed($sTagCode) + { + try + { + $sClass = $this->Get('obj_class'); + $sAttCode = $this->Get('obj_attcode'); + $oSearch = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'"); + $oSet = new DBObjectSet($oSearch); + if ($oSet->CountExceeds(0)) + { + return true; + } + } + catch (Exception $e) + { + IssueLog::Warning($e->getMessage()); + } + return false; + } + + /** + * Display Tag Usage + * + * @param \WebPage $oPage + * @param bool $bEditMode + * + * @throws \CoreException + * @throws \DictExceptionMissingString + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + function DisplayBareRelations(WebPage $oPage, $bEditMode = false) + { + parent::DisplayBareRelations($oPage, $bEditMode); + if (!$bEditMode) + { + $sClass = $this->Get('obj_class'); + $sAttCode = $this->Get('obj_attcode'); + $sTagCode = $this->Get('code'); + $oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'"); + $oSet = new DBObjectSet($oFilter); + $iCount = $oSet->Count(); + $oPage->SetCurrentTab(Dict::Format('Core:TagSetFieldData:WhereIsThisTagTab', $iCount)); + if ($iCount === 0) + { + $sNoEntries = Dict::S('Core:TagSetFieldData:NoEntryFound'); + $oPage->add("

    $sNoEntries

    "); + } + else + { + $aClassLabels = array(); + foreach(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sCurrentClass) + { + $aClassLabels[$sCurrentClass] = MetaModel::GetName($sCurrentClass); + } + + foreach($aClassLabels as $sClass => $sClassLabel) + { + $oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'"); + $oSet = new DBObjectSet($oFilter); + if ($oSet->CountExceeds(0)) + { + $oPage->add("

    $sClassLabel

    "); + $oResultBlock = new DisplayBlock($oFilter, 'list', false); + $oResultBlock->Display($oPage, 1); + } + } + } + } + } + + public static function GetClassName($sClass) + { + if ($sClass == 'TagSetFieldData') + { + $aWords = preg_split('/(?=[A-Z]+)/', $sClass); + return trim(implode(' ', $aWords)); + } + try + { + $aTagFieldInfo = self::ExtractTagFieldName($sClass); + } catch (CoreException $e) + { + return $sClass; + } + $sClassDesc = MetaModel::GetName($aTagFieldInfo['obj_class']); + $sAttDesc = MetaModel::GetAttributeDef($aTagFieldInfo['obj_class'], $aTagFieldInfo['obj_attcode'])->GetLabel(); + if (Dict::Exists("Class:$sClass")) + { + $sName = Dict::Format("Class:$sClass", $sClassDesc, $sAttDesc); + } + else + { + $sName = Dict::Format('Class:TagSetFieldData', $sClassDesc, $sAttDesc); + } + return $sName; + } + + /** + * @param $sClass + * @param $sAttCode + * + * @return \TagSetFieldData[] + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MySQLException + */ + public static function GetAllowedValues($sClass, $sAttCode) + { + $sClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode); + $sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode); + if (!isset(self::$m_aAllowedValues[$sTagDataClass])) + { + $oSearch = new DBObjectSearch($sTagDataClass); + $oSearch->AddCondition('obj_class', $sClass); + $oSearch->AddCondition('obj_attcode', $sAttCode); + $oSet = new DBObjectSet($oSearch); + self::$m_aAllowedValues[$sTagDataClass] = $oSet->ToArray(); + } + + return self::$m_aAllowedValues[$sTagDataClass]; + } +} \ No newline at end of file diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index 0f9da40c13..1853ac35dd 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -289,7 +289,7 @@ abstract class TriggerOnStateChange extends TriggerOnObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("state", array("allowed_values" => null, "sql" => "state", "default_value" => null, "is_null_allowed" => false, "depends_on" => array()))); + MetaModel::Init_AddAttribute(new AttributeClassState("state", array("class_field" => 'target_class', "allowed_values" => null, "sql" => "state", "default_value" => null, "is_null_allowed" => false, "depends_on" => array('target_class')))); // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'state', 'action_list')); // Attributes to be displayed for the complete details @@ -402,6 +402,40 @@ class TriggerOnObjectCreate extends TriggerOnObject } } +/** + * Class TriggerOnObjectCreate + */ +class TriggerOnObjectDelete extends TriggerOnObject +{ + /** + * @throws \CoreException + */ + public static function Init() + { + $aParams = array + ( + "category" => "grant_by_profile,core/cmdb,application", + "key_type" => "autoincrement", + "name_attcode" => "description", + "state_attcode" => "", + "reconc_keys" => array('description'), + "db_table" => "priv_trigger_onobjdelete", + "db_key_field" => "id", + "db_finalclass_field" => "", + "display_template" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + + // Display lists + MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list + // Search criteria + MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form + // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form + } +} + /** * Class TriggerOnObjectCreate */ @@ -427,7 +461,7 @@ class TriggerOnObjectUpdate extends TriggerOnObject ); MetaModel::Init_Params($aParams); MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeObjectAttCodeSet('target_attcodes', array("allowed_values" => null, "class" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "depends_on" => array('target_class')))); + MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('target_attcodes', array("allowed_values" => null, "class_field" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "max_items" => 20, "min_items" => 0, "attribute_definition_list" => null, "depends_on" => array('target_class')))); // Display lists MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'target_attcodes', 'action_list')); // Attributes to be displayed for the complete details @@ -444,11 +478,13 @@ class TriggerOnObjectUpdate extends TriggerOnObject } // Check the attribute - $aAttCodes = $this->Get('target_attcodes'); + $oAttCodeSet = $this->Get('target_attcodes'); + $aAttCodes = $oAttCodeSet->GetValues(); if (empty($aAttCodes)) { return true; } + foreach($aAttCodes as $sAttCode) { if (array_key_exists($sAttCode, $aChanges)) @@ -458,6 +494,39 @@ class TriggerOnObjectUpdate extends TriggerOnObject } return false; } + + public function ComputeValues() + { + parent::ComputeValues(); + + // Remove unwanted attribute codes + $aChanges = $this->ListChanges(); + if (isset($aChanges['target_attcodes'])) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($this), 'target_attcodes'); + $aArgs = array('this' => $this); + $aAllowedValues = $oAttDef->GetAllowedValues($aArgs); + + /** @var \ormSet $oValue */ + $oValue = $this->Get('target_attcodes'); + $aValues = $oValue->GetValues(); + $bChanged = false; + foreach($aValues as $key => $sValue) + { + if (!isset($aAllowedValues[$sValue])) + { + unset($aValues[$key]); + $bChanged = true; + } + } + if ($bChanged) + { + $oValue->SetValues($aValues); + $this->Set('target_attcodes', $oValue); + } + } + } + } /** diff --git a/css/css-variables.scss b/css/css-variables.scss index 2417798502..e980419bf8 100644 --- a/css/css-variables.scss +++ b/css/css-variables.scss @@ -26,13 +26,12 @@ $frame-background-color: $gray-extra-light; $text-color: #000; $box-radius: 0px; $box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15); -// - Boxes -//$search-criteria-box-color: #2D2D2D; -//$search-criteria-box-bg-color: #f0f3f5; -//$search-criteria-box-border-color: #3f7294; -//$search-criteria-box-border: 1px solid $search-criteria-box-border-color; -//$search-criteria-box-radius: 1px; -// + +$hyperlink-color: $complement-color; +$hyperlink-text-decoration: none; + +//////////// +// Search // $search-criteria-box-color: #2D2D2D; $search-criteria-box-picto-color: #E87C1E; $search-criteria-box-bg-color: #EEEEEE; @@ -50,4 +49,4 @@ $search-button-box-bg-color: $white; $search-button-box-bg-hover-color: $gray-extra-light; // Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0 -$version: "v2.5.0-beta"; +$version: "v2.6.0-dev"; diff --git a/css/light-grey.css b/css/light-grey.css index 935d1d058f..89647bcb59 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -92,21 +92,21 @@ table.listResults td .view-image img { margin-left: auto; margin-right: auto; } -table.listResults > tbody > tr.selected > * { - background-color: #f5c093; -} table.listResults > tbody > tr > * { - transition: background-color 200ms ease-in-out; + transition: background-color 400ms linear; } table.listResults > tbody > tr:hover > * { cursor: pointer; } table.listResults > tbody > tr.selected:hover > * { /* hover on lines is currently done toggling td.hover, and having a rule for links */ - background-color: #f1a564; + background-color: #f3b37b; + color: #000; } -table.listResults > tbody > tr.selected:hover a { - background-color: transparent; +table.listResults > tbody > tr:hover > * { + /* hover on lines is currently done toggling td.hover, and having a rule for links */ + background-color: #fdf5d0; + color: #000; } .edit-image .view-image { display: inline-block; @@ -195,7 +195,6 @@ tr.green td, .wizContainer tr.green td { background-color: #b3e5b4; } tr td.hover, tr.even td.hover, .hover a, .hover a:visited, .hover a:hover, .wizContainer tr.even td.hover, .wizContainer tr td.hover { - background-color: #fdf5d0; color: #000; } th { @@ -343,10 +342,10 @@ a.small_action { padding-left: 5px; padding-top: 2px; padding-bottom: 2px; - background: #ea7d1e url(../images/actions_left.png?v=v2.5.0-beta) no-repeat left; + background: #ea7d1e url(../images/actions_left.png?v=v2.6.0-dev) no-repeat left; } .actions_details span { - background: url(../images/actions_right.png?v=v2.5.0-beta) no-repeat right; + background: url(../images/actions_right.png?v=v2.6.0-dev) no-repeat right; color: #fff; font-weight: bold; padding-top: 2px; @@ -520,7 +519,7 @@ div.actions_menu > ul { nowidth: 70px; padding-left: 5px; /* Nasty work-around for IE... en attendant mieux */ - background: #ea7d1e url(../images/actions_left.png?v=v2.5.0-beta) no-repeat top left; + background: #ea7d1e url(../images/actions_left.png?v=v2.6.0-dev) no-repeat top left; cursor: pointer; margin: 0; } @@ -532,7 +531,7 @@ div.actions_menu > ul > li { height: 17px; padding-right: 16px; padding-left: 4px; - background: url(../images/actions_right.png?v=v2.5.0-beta) no-repeat top right transparent; + background: url(../images/actions_right.png?v=v2.6.0-dev) no-repeat top right transparent; font-weight: bold; color: #fff; vertical-align: middle; @@ -675,7 +674,7 @@ td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-da display: block; text-indent: -2000px; overflow: hidden; - background: url(../images/calendar.png?v=v2.5.0-beta) no-repeat; + background: url(../images/calendar.png?v=v2.6.0-dev) no-repeat; } td a.dp-choose-date.dp-disabled, a.dp-choose-date.dp-disabled { background-position: 0 -20px; @@ -887,6 +886,9 @@ input.dp-applied { left: 0px; margin-top: -1px; } +.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_more_criterion .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_button .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .search_form_criteria .sfm_content .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_button .sfm_content .sfc_fg_buttons { + white-space: nowrap; +} .search_form_handler .sf_criterion_area .search_form_criteria { /* Non editable criteria */ /* Draft criteria (modifications not applied) */ @@ -1130,6 +1132,15 @@ input.dp-applied { .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_enum .sfc_form_group .sfc_fg_operator_in > label .sfc_op_content { width: 100%; } +.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_tag_set .sfc_form_group .sfc_fg_operator_in > label { + display: inline-block; + width: 100%; + line-height: initial; + white-space: nowrap; +} +.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_tag_set .sfc_form_group .sfc_fg_operator_in > label .sfc_op_content { + width: 100%; +} .search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_numeric .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_between .sfc_op_content_from_outer { display: inline; } @@ -1316,19 +1327,19 @@ input.dp-applied { } /* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ table.listResults tr.odd td.truncated, table.listResults tr td.truncated, .wizContainer table.listResults tr.odd td.truncated, .wizContainer table.listResults tr td.truncated { - background: url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x; + background: url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x; } /* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ table.listResults tr.even td.truncated, .wizContainer table.listResults tr.even td.truncated { - background: #f9f9f1 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x; + background: #f9f9f1 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x; } /* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ table.listResults tr.even td.hover.truncated, .wizContainer table.listResults tr.even td.hover.truncated { - background: #fdf5d0 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x; + background: #fdf5d0 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x; } /* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */ table.listResults tr.odd td.hover.truncated, table.listResults tr td.hover.truncated, .wizContainer table.listResults tr.odd td.hover.truncated, .wizContainer table.listResults tr td.hover.truncated { - background: #fdf5d0 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x; + background: #fdf5d0 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x; } table.listResults.truncated { border-bottom: 0; @@ -1436,7 +1447,7 @@ div#logo { div#logo div { height: 88px; width: 244px; - background: url(../images/itop-logo-2.png?v=v2.5.0-beta) left no-repeat; + background: url(../images/itop-logo-2.png?v=v2.6.0-dev) left no-repeat; } #left-pane .ui-layout-north { overflow: hidden; @@ -1528,7 +1539,7 @@ div#logo div { } #global-search-image { vertical-align: middle; - background: url(../images/search.png?v=v2.5.0-beta) center center no-repeat; + background: url(../images/search.png?v=v2.6.0-dev) center center no-repeat; display: inline-block; width: 28px; height: 30px; @@ -1557,7 +1568,7 @@ span.ui-icon { margin: 0 2px; } .ui-layout-button-pin-down { - background: url(../images/splitter-bkg.png?v=v2.5.0-beta) transparent; + background: url(../images/splitter-bkg.png?v=v2.6.0-dev) transparent; width: 16px; background-position: -144px -144px; } @@ -1933,6 +1944,22 @@ fieldset .details > .field_container { .field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_extkey > .field_input_btn > img { max-width: 20px; } +.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control { + width: 100%; +} +.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-dropdown, .field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input, .field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input input { + font-size: 12px; +} +.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input { + padding: 2px 2px 0px 2px; + /* padding-bottom = padding-top - item margin-bottom */ + border: 1px solid #ababab; + border-radius: 0; +} +.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input .attribute-set-item.partial-code { + color: rgba(34, 34, 34, 0.6); + background-color: #eaeaea; +} .one-col-details .details .field_container.field_small { /* On a single column, field labels can take more width but they are limited so it doesn't feel weird when all labels are short */ } @@ -2057,7 +2084,7 @@ img.prev, img.first, img.next, img.last { } div.actions_button { float: right; - background: #ea7d1e url("../images/actions_left.png?v=v2.5.0-beta") no-repeat scroll left top; + background: #ea7d1e url("../images/actions_left.png?v=v2.6.0-dev") no-repeat scroll left top; padding-left: 5px; margin-top: 0; margin-right: 10px; @@ -2065,7 +2092,7 @@ div.actions_button { vertical-align: middle; } div.actions_button a, .actions_button a:hover, .actions_button a:visited { - background: #ea7d1e url(../images/actions_bkg.png?v=v2.5.0-beta) no-repeat scroll right top; + background: #ea7d1e url(../images/actions_bkg.png?v=v2.6.0-dev) no-repeat scroll right top; color: #fff; padding-right: 8px; cursor: pointer; @@ -2089,10 +2116,10 @@ select#org_id { cursor: not-allowed; } .dragHover { - background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.5.0-beta); + background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.6.0-dev); } .edit_mode .dashlet { - background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.5.0-beta); + background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.6.0-dev); padding: 5px; margin: 0; position: relative; @@ -2126,7 +2153,7 @@ table.prop_table { top: 0; right: 0; z-index: 10; - background: transparent url(../images/delete.png?v=v2.5.0-beta) no-repeat center; + background: transparent url(../images/delete.png?v=v2.6.0-dev) no-repeat center; } td.prop_value { text-align: left; @@ -2328,17 +2355,17 @@ a.summary, a.summary:hover { } .message_info { border: 1px solid #993; - background: url(../images/info-mini.png?v=v2.5.0-beta) 1em 1em no-repeat #ffc; + background: url(../images/info-mini.png?v=v2.6.0-dev) 1em 1em no-repeat #ffc; padding-left: 3em; } .message_ok { border: 1px solid #393; - background: url(../images/ok.png?v=v2.5.0-beta) 1em 1em no-repeat #cfc; + background: url(../images/ok.png?v=v2.6.0-dev) 1em 1em no-repeat #cfc; padding-left: 3em; } .message_error { border: 1px solid #933; - background: url(../images/error.png?v=v2.5.0-beta) 1em 1em no-repeat #fcc; + background: url(../images/error.png?v=v2.6.0-dev) 1em 1em no-repeat #fcc; padding-left: 3em; } .fg-menu a img { @@ -2469,18 +2496,18 @@ div.explain-printable { } #hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter span { padding-left: 20px; - background: url(../images/eye-open-555.png?v=v2.5.0-beta) 2px center no-repeat; + background: url(../images/eye-open-555.png?v=v2.6.0-dev) 2px center no-repeat; } #hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter.strikethrough span { text-decoration: line-through; - background: url(../images/eye-closed-555.png?v=v2.5.0-beta) 2px center no-repeat; + background: url(../images/eye-closed-555.png?v=v2.6.0-dev) 2px center no-repeat; } .printable-version legend { padding-left: 26px; - background: #1c94c4 url(../images/eye-open-fff.png?v=v2.5.0-beta) 8px center no-repeat; + background: #1c94c4 url(../images/eye-open-fff.png?v=v2.6.0-dev) 8px center no-repeat; } .printable-version .strikethrough legend { - background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.5.0-beta) 8px center no-repeat; + background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.6.0-dev) 8px center no-repeat; } .printable-version fieldset.strikethrough span { display: none; @@ -2631,7 +2658,7 @@ span.search-button, span.refresh-button { #itop-breadcrumb .breadcrumb-item a::after { content: ''; position: absolute; - background-image: url(../images/breadcrumb-separator.png?v=v2.5.0-beta); + background-image: url(../images/breadcrumb-separator.png?v=v2.6.0-dev); background-repeat: no-repeat; width: 8px; height: 16px; @@ -2865,3 +2892,46 @@ table.listResults .originColor { .menu-icon-select > .ui-menu-item { padding: 0.3em 3%; } +.attribute.attribute-set .attribute-set-item::after { + content: ","; + margin-right: 0.5em; +} +.attribute.attribute-set .attribute-set-item:last-of-type::after { + content: ""; + margin-right: 0; +} +.attribute-edit .attribute-set .attribute-set-item, .attribute-tag-set.attribute-set .attribute-set-item, .selectize-control > .selectize-input > .item[data-value], .selectize-control.multi > .selectize-input > .item[data-value], .selectize-control > .selectize-input > .item[data-value].active, .selectize-control.multi > .selectize-input > .item[data-value].active { + display: inline-block; + margin-right: 3px; + margin-bottom: 3px; + padding: 4px 6px; + max-width: 120px; + background: #fdfdfd none; + border-radius: 2px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px #f1f1f1; + color: #222; + text-shadow: none; + vertical-align: middle; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.attribute-edit .attribute-set .attribute-set-item::after, .attribute-tag-set.attribute-set .attribute-set-item::after, .selectize-control > .selectize-input > .item[data-value]::after, .selectize-control.multi > .selectize-input > .item[data-value]::after, .selectize-control > .selectize-input > .item[data-value].active::after, .selectize-control.multi > .selectize-input > .item[data-value].active::after { + content: ""; + margin-right: 0; +} +.selectize-control > .selectize-input.has-items:after, .selectize-control.multi > .selectize-input.has-items:after { + content: "+"; + display: inline-block; + font-size: 17px; + font-weight: bold; + color: #222; +} +.selectize-control > .selectize-input > .item[data-value], .selectize-control.multi > .selectize-input > .item[data-value] { + padding-right: 1.5em !important; + border: 1px solid #ddd; +} +.selectize-control > .selectize-input > .item[data-value] > .remove, .selectize-control.multi > .selectize-input > .item[data-value] > .remove { + padding-top: 0.3em; + border: none; +} diff --git a/css/light-grey.scss b/css/light-grey.scss index c1111d7863..37a7d0e4f6 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -117,11 +117,10 @@ table.listResults td .view-image { } table.listResults > tbody > tr.selected > * { - background-color: lighten($combodo-orange, 25%); } table.listResults > tbody > tr > * { - transition: background-color 200ms ease-in-out; + transition: background-color 400ms linear; } table.listResults > tbody > tr:hover > * { @@ -130,10 +129,14 @@ table.listResults > tbody > tr:hover > * { table.listResults > tbody > tr.selected:hover > * { /* hover on lines is currently done toggling td.hover, and having a rule for links */ - background-color: lighten($combodo-orange, 15%); + background-color: lighten($combodo-orange, 20%); + color: $text-color; } -table.listResults > tbody > tr.selected:hover a { - background-color: transparent; + +table.listResults > tbody > tr:hover > * { + /* hover on lines is currently done toggling td.hover, and having a rule for links */ + background-color: #fdf5d0; + color: $text-color; } .edit-image { @@ -245,7 +248,7 @@ tr.green td, .wizContainer tr.green td { } tr td.hover, tr.even td.hover, .hover a, .hover a:visited, .hover a:hover, .wizContainer tr.even td.hover, .wizContainer tr td.hover { - background-color: #fdf5d0; + //background-color: #fdf5d0; color: $text-color; } @@ -994,6 +997,10 @@ input.dp-applied { min-width: 100%; left: 0px; margin-top: -1px; + + .sfc_fg_buttons{ + white-space: nowrap; + } } } @@ -1307,6 +1314,22 @@ input.dp-applied { } } } + &.search_form_criteria_tag_set{ + .sfc_form_group{ + .sfc_fg_operator_in{ + > label{ + display: inline-block; + width: 100%; + line-height: initial; + white-space: nowrap; + + .sfc_op_content{ + width: 100%; + } + } + } + } + } &.search_form_criteria_numeric { //.sfc_form_group.advanced { // .sfc_fg_operator_between { @@ -2241,6 +2264,28 @@ fieldset .details>.field_container { } } } + + &.field_input_set{ + .selectize-control{ + width: 100%; + + .selectize-dropdown, + .selectize-input, + .selectize-input input{ + font-size: 12px; + } + .selectize-input{ + padding: 2px 2px 0px 2px; /* padding-bottom = padding-top - item margin-bottom */ + border: 1px solid #ABABAB; + border-radius: 0; + + .attribute-set-item.partial-code{ + color: transparentize($gray-darker, 0.4); + background-color: lighten($gray-lighter, 5%); + } + } + } + } } } } @@ -3262,4 +3307,100 @@ table.listResults .originColor{ } .menu-icon-select > .ui-menu-item{ padding: .3em 3%; -} \ No newline at end of file +} + +////////////////////// +// Set attribute // +// - Readonly (object viewing, objects list) +.attribute { + &.attribute-set { + .attribute-set-item{ + &::after{ + content: ","; + margin-right: 0.5em; + } + &:last-of-type::after{ + content: ""; + margin-right: 0; + } + } + + &.history-added { + .attribute-set-item { + font-weight: bold; + } + } + &.history-removed { + .attribute-set-item { + text-decoration: line-through; + font-style: italic; + } + } + } +} +// - Edit is always styled like the selectize items, see below. +%attribute-set-item-edition{ + display: inline-block; + margin-right: 3px; + margin-bottom: 3px; + padding: 4px 6px; + max-width: 120px; + + background: #fdfdfd none; + border-radius: 2px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb(241, 241, 241, 0.7); + + color: $gray-darker; + text-shadow: none; + vertical-align: middle; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &::after{ + content: ""; + margin-right: 0; + } +} +.attribute-edit .attribute-set .attribute-set-item{ + @extend %attribute-set-item-edition; +} +////////////////////// +// TagSet attribute // +// Always styled like the selectize items, see below. +.attribute-tag-set.attribute-set{ + .attribute-set-item{ + @extend %attribute-set-item-edition; + } +} + +////////////////////// +// Selectize widget // +// +.selectize-control, +.selectize-control.multi{ + > .selectize-input{ + &.has-items:after { + content: "+"; + display: inline-block; + font-size: 17px; + font-weight: bold; + color: $gray-darker; + } + + > .item[data-value]{ + @extend %attribute-set-item-edition; + + padding-right: 1.5em !important; + border: 1px solid $gray-lighter; + + &.active{ + @extend %attribute-set-item-edition; + } + > .remove { + padding-top: 0.3em; + border: none; + } + } + } +} diff --git a/css/selectize.default.css b/css/selectize.default.css new file mode 100644 index 0000000000..ab5309509e --- /dev/null +++ b/css/selectize.default.css @@ -0,0 +1,394 @@ +/** + * selectize.default.css (v0.12.4) - Default Theme + * Copyright (c) 2013–2015 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ +.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder { + visibility: visible !important; + background: #f2f2f2 !important; + background: rgba(0, 0, 0, 0.06) !important; + border: 0 none !important; + -webkit-box-shadow: inset 0 0 12px 4px #ffffff; + box-shadow: inset 0 0 12px 4px #ffffff; +} +.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after { + content: '!'; + visibility: hidden; +} +.selectize-control.plugin-drag_drop .ui-sortable-helper { + -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); +} +.selectize-dropdown-header { + position: relative; + padding: 5px 8px; + border-bottom: 1px solid #d0d0d0; + background: #f8f8f8; + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; +} +.selectize-dropdown-header-close { + position: absolute; + right: 8px; + top: 50%; + color: #303030; + opacity: 0.4; + margin-top: -12px; + line-height: 20px; + font-size: 20px !important; +} +.selectize-dropdown-header-close:hover { + color: #000000; +} +.selectize-dropdown.plugin-optgroup_columns .optgroup { + border-right: 1px solid #f2f2f2; + border-top: 0 none; + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child { + border-right: 0 none; +} +.selectize-dropdown.plugin-optgroup_columns .optgroup:before { + display: none; +} +.selectize-dropdown.plugin-optgroup_columns .optgroup-header { + border-top: 0 none; +} +.selectize-control.plugin-remove_button [data-value] { + position: relative; + padding-right: 24px !important; +} +.selectize-control.plugin-remove_button [data-value] .remove { + z-index: 1; + /* fixes ie bug (see #392) */ + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 17px; + text-align: center; + font-weight: bold; + font-size: 12px; + color: inherit; + text-decoration: none; + vertical-align: middle; + display: inline-block; + padding: 2px 0 0 0; + border-left: 1px solid #0073bb; + -webkit-border-radius: 0 2px 2px 0; + -moz-border-radius: 0 2px 2px 0; + border-radius: 0 2px 2px 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.selectize-control.plugin-remove_button [data-value] .remove:hover { + background: rgba(0, 0, 0, 0.05); +} +.selectize-control.plugin-remove_button [data-value].active .remove { + border-left-color: #00578d; +} +.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover { + background: none; +} +.selectize-control.plugin-remove_button .disabled [data-value] .remove { + border-left-color: #aaaaaa; +} +.selectize-control.plugin-remove_button .remove-single { + position: absolute; + right: 28px; + top: 6px; + font-size: 23px; +} +.selectize-control { + position: relative; +} +.selectize-dropdown, +.selectize-input, +.selectize-input input { + color: #303030; + font-family: inherit; + font-size: 13px; + line-height: 18px; + -webkit-font-smoothing: inherit; +} +.selectize-input, +.selectize-control.single .selectize-input.input-active { + background: #ffffff; + cursor: text; + display: inline-block; +} +.selectize-input { + border: 1px solid #d0d0d0; + padding: 8px 8px; + display: inline-block; + width: 100%; + overflow: hidden; + position: relative; + z-index: 1; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1); + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.selectize-control.multi .selectize-input.has-items { + padding: 5px 8px 2px; +} +.selectize-input.full { + background-color: #ffffff; +} +.selectize-input.disabled, +.selectize-input.disabled * { + cursor: default !important; +} +.selectize-input.focus { + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15); +} +.selectize-input.dropdown-active { + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; +} +.selectize-input > * { + vertical-align: baseline; + display: -moz-inline-stack; + display: inline-block; + zoom: 1; + *display: inline; +} +.selectize-control.multi .selectize-input > div { + cursor: pointer; + margin: 0 3px 3px 0; + padding: 2px 6px; + background: #1da7ee; + color: #ffffff; + border: 1px solid #0073bb; +} +.selectize-control.multi .selectize-input > div.active { + background: #92c836; + color: #ffffff; + border: 1px solid #00578d; +} +.selectize-control.multi .selectize-input.disabled > div, +.selectize-control.multi .selectize-input.disabled > div.active { + color: #ffffff; + background: #d2d2d2; + border: 1px solid #aaaaaa; +} +.selectize-input > input { + display: inline-block !important; + padding: 0 !important; + min-height: 0 !important; + max-height: none !important; + max-width: 100% !important; + margin: 0 1px !important; + text-indent: 0 !important; + border: 0 none !important; + background: none !important; + line-height: inherit !important; + -webkit-user-select: auto !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.selectize-input > input::-ms-clear { + display: none; +} +.selectize-input > input:focus { + outline: none !important; +} +.selectize-input::after { + content: ' '; + display: block; + clear: left; +} +.selectize-input.dropdown-active::before { + content: ' '; + display: block; + position: absolute; + background: #f0f0f0; + height: 1px; + bottom: 0; + left: 0; + right: 0; +} +.selectize-dropdown { + position: absolute; + z-index: 10; + border: 1px solid #d0d0d0; + background: #ffffff; + margin: -1px 0 0 0; + border-top: 0 none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + -webkit-border-radius: 0 0 3px 3px; + -moz-border-radius: 0 0 3px 3px; + border-radius: 0 0 3px 3px; +} +.selectize-dropdown [data-selectable] { + cursor: pointer; + overflow: hidden; +} +.selectize-dropdown [data-selectable] .highlight { + background: rgba(125, 168, 208, 0.2); + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; +} +.selectize-dropdown [data-selectable], +.selectize-dropdown .optgroup-header { + padding: 5px 8px; +} +.selectize-dropdown .optgroup:first-child .optgroup-header { + border-top: 0 none; +} +.selectize-dropdown .optgroup-header { + color: #303030; + background: #ffffff; + cursor: default; +} +.selectize-dropdown .active { + background-color: #f5fafd; + color: #495c68; +} +.selectize-dropdown .active.create { + color: #495c68; +} +.selectize-dropdown .create { + color: rgba(48, 48, 48, 0.5); +} +.selectize-dropdown-content { + overflow-y: auto; + overflow-x: hidden; + max-height: 200px; + -webkit-overflow-scrolling: touch; +} +.selectize-control.single .selectize-input, +.selectize-control.single .selectize-input input { + cursor: pointer; +} +.selectize-control.single .selectize-input.input-active, +.selectize-control.single .selectize-input.input-active input { + cursor: text; +} +.selectize-control.single .selectize-input:after { + content: ' '; + display: block; + position: absolute; + top: 50%; + right: 15px; + margin-top: -3px; + width: 0; + height: 0; + border-style: solid; + border-width: 5px 5px 0 5px; + border-color: #808080 transparent transparent transparent; +} +.selectize-control.single .selectize-input.dropdown-active:after { + margin-top: -4px; + border-width: 0 5px 5px 5px; + border-color: transparent transparent #808080 transparent; +} +.selectize-control.rtl.single .selectize-input:after { + left: 15px; + right: auto; +} +.selectize-control.rtl .selectize-input > input { + margin: 0 4px 0 -2px !important; +} +.selectize-control .selectize-input.disabled { + opacity: 0.5; + background-color: #fafafa; +} +.selectize-control.multi .selectize-input.has-items { + padding-left: 5px; + padding-right: 5px; +} +.selectize-control.multi .selectize-input.disabled [data-value] { + color: #999; + text-shadow: none; + background: none; + -webkit-box-shadow: none; + box-shadow: none; +} +.selectize-control.multi .selectize-input.disabled [data-value], +.selectize-control.multi .selectize-input.disabled [data-value] .remove { + border-color: #e6e6e6; +} +.selectize-control.multi .selectize-input.disabled [data-value] .remove { + background: none; +} +.selectize-control.multi .selectize-input [data-value] { + text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3); + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + background-color: #1b9dec; + background-image: -moz-linear-gradient(top, #1da7ee, #178ee9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#1da7ee), to(#178ee9)); + background-image: -webkit-linear-gradient(top, #1da7ee, #178ee9); + background-image: -o-linear-gradient(top, #1da7ee, #178ee9); + background-image: linear-gradient(to bottom, #1da7ee, #178ee9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1da7ee', endColorstr='#ff178ee9', GradientType=0); + -webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03); + box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03); +} +.selectize-control.multi .selectize-input [data-value].active { + background-color: #0085d4; + background-image: -moz-linear-gradient(top, #008fd8, #0075cf); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#008fd8), to(#0075cf)); + background-image: -webkit-linear-gradient(top, #008fd8, #0075cf); + background-image: -o-linear-gradient(top, #008fd8, #0075cf); + background-image: linear-gradient(to bottom, #008fd8, #0075cf); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff008fd8', endColorstr='#ff0075cf', GradientType=0); +} +.selectize-control.single .selectize-input { + -webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8); + box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8); + background-color: #f9f9f9; + background-image: -moz-linear-gradient(top, #fefefe, #f2f2f2); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2)); + background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2); + background-image: -o-linear-gradient(top, #fefefe, #f2f2f2); + background-image: linear-gradient(to bottom, #fefefe, #f2f2f2); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0); +} +.selectize-control.single .selectize-input, +.selectize-dropdown.single { + border-color: #b8b8b8; +} +.selectize-dropdown .optgroup-header { + padding-top: 7px; + font-weight: bold; + font-size: 0.85em; +} +.selectize-dropdown .optgroup { + border-top: 1px solid #f0f0f0; +} +.selectize-dropdown .optgroup:first-child { + border-top: 0 none; +} diff --git a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml index 53a1b60517..d91967942a 100755 --- a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml +++ b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml @@ -1,5 +1,5 @@ - + DBObject diff --git a/datamodels/2.x/itop-backup/datamodel.itop-backup.xml b/datamodels/2.x/itop-backup/datamodel.itop-backup.xml index 2a89ca50ae..8076ad7208 100644 --- a/datamodels/2.x/itop-backup/datamodel.itop-backup.xml +++ b/datamodels/2.x/itop-backup/datamodel.itop-backup.xml @@ -1,5 +1,5 @@ - + 15 diff --git a/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml b/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml index cf8a48ee51..0789ae6bea 100644 --- a/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml +++ b/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml index ab583cc253..81ca9de2ae 100755 --- a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml +++ b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml index d4667c05da..3ee83ab339 100755 --- a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml +++ b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml b/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml index 484bd9dcbb..f35b2abea1 100755 --- a/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml +++ b/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject @@ -8423,5 +8423,12 @@ + + 100 + Catalogs + $pages/tagadmin.php + TagSetFieldData + UR_ACTION_MODIFY + diff --git a/datamodels/2.x/itop-config/datamodel.itop-config.xml b/datamodels/2.x/itop-config/datamodel.itop-config.xml index 3b3224180d..28be1062a9 100644 --- a/datamodels/2.x/itop-config/datamodel.itop-config.xml +++ b/datamodels/2.x/itop-config/datamodel.itop-config.xml @@ -1,5 +1,5 @@ - + 50 diff --git a/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml b/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml index 5fb365d05c..e13611cef3 100755 --- a/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml +++ b/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml @@ -1,5 +1,5 @@ - + PhysicalDevice diff --git a/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml b/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml index e7ae6a50ad..dc6d526cf4 100644 --- a/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml +++ b/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml @@ -1,5 +1,5 @@ - + PhysicalDevice diff --git a/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml b/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml index 73c9d071b2..dcb52de72f 100644 --- a/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml +++ b/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml b/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml index 73d49712d3..46e44f66b8 100644 --- a/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml +++ b/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml index e4bb3960d7..8ef1302b64 100755 --- a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml +++ b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml b/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml index 60bb7f632f..692f62f5bb 100755 --- a/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml +++ b/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php b/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php index af67c409e6..38b25d4805 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php +++ b/datamodels/2.x/itop-portal-base/portal/src/forms/objectformmanager.class.inc.php @@ -32,7 +32,9 @@ use DBObjectSet; use DBSearch; use DBObjectSearch; use InlineImage; +use ormTagSet; use AttributeDateTime; +use AttributeTagSet; use AttachmentPlugIn; use Combodo\iTop\Form\FormManager; use Combodo\iTop\Form\Form; @@ -1120,7 +1122,18 @@ class ObjectFormManager extends FormManager // Setting value in the object $this->oObject->Set($sAttCode, $oLinkSet); } - else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime + elseif ($oAttDef instanceof AttributeTagSet) + { + /** @var \ormTagSet $oTagSet */ + $oTagSet = $this->oObject->Get($sAttCode); + if (is_null($oTagSet)) + { + $oTagSet = new ormTagSet(get_class($this->oObject), $sAttCode); + } + $oTagSet->ApplyDelta(json_decode($value, true)); + $this->oObject->Set($sAttCode, $oTagSet); + } + elseif ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime { if ($value != null) { diff --git a/datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig index 189a12116f..157cc510c2 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig @@ -45,6 +45,7 @@ {# - Misc libs #} + {# - Bootstrap theme #} @@ -85,6 +86,7 @@ + @@ -114,6 +116,9 @@ {# Typeahead files for autocomplete #} + {# Selectize for sets #} + + {# Form files #} @@ -123,6 +128,7 @@ + {# UI Extensions JS, in an undefined order #} {% if app['combodo.portal.instance.conf'].ui_extensions.js_files is defined %} {% for js_file in app['combodo.portal.instance.conf'].ui_extensions.js_files %} diff --git a/datamodels/2.x/itop-portal-base/portal/web/js/portal_form_field_set.js b/datamodels/2.x/itop-portal-base/portal/web/js/portal_form_field_set.js new file mode 100644 index 0000000000..fd2aad7d4e --- /dev/null +++ b/datamodels/2.x/itop-portal-base/portal/web/js/portal_form_field_set.js @@ -0,0 +1,55 @@ +//iTop Portal Form field Set +//Used for field containing tagset ... +; +$(function() +{ + // the widget definition, where 'itop' is the namespace, + // 'portal_form_field' the widget name + $.widget( 'itop.portal_form_field_set', $.itop.portal_form_field, + { + // the constructor + _create: function() + { + this.element + .addClass('portal_form_field_tagset'); + + this.element.find('input[type="hidden"]').set_widget(); + + this._super(); + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + this.element + .removeClass('portal_form_field_tagset'); + + this._super(); + }, + // _setOptions is called with a hash of all options that are changing + // always refresh when changing options + _setOptions: function() + { + this._superApply(arguments); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + this._super( key, value ); + }, + validate: function(oEvent, oData) + { + var oResult = { is_valid: true, error_messages: [] }; + + // Doing data validation + if(this.options.validators !== null) + { + // TODO + } + + this.options.on_validation_callback(this, oResult); + + return oResult; + } + }); +}); \ No newline at end of file diff --git a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml index 4514b87e05..a34c4b9f5d 100644 --- a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml +++ b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml index c9351b1ea5..adde16aa0b 100755 --- a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml +++ b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml index dfc7ac5b5a..ec98e4c500 100755 --- a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml +++ b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml @@ -1,5 +1,5 @@ - + @@ -27,6 +27,7 @@ + diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index 8a3afd3355..84c374074a 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1,5 +1,5 @@ - + @@ -1500,6 +1500,9 @@ 40 + + 76 + diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index 02666bdfc8..5803451fae 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -1,1158 +1,1168 @@ - - - - - - - - - - org_id AND caller_id = :contact->id]]> - org_id]]> - - - - Ticket - - + + + + + + + + + + + + + + + org_id AND caller_id = :contact->id]]> + + org_id]]> + + + + Ticket + + - bizmodel,searchable,requestmgmt - false - autoincrement - ticket_request - id - - - - - - - - images/user-request.png - - - - - - - - - - - - - - true - - new - waiting_for_approval - approved - rejected - assigned - pending - escalated_tto - escalated_ttr - resolved - closed - - status - new - false - - - - incident - service_request - - request_type - - true - list - - - - 1 - 2 - 3 - - impact - 1 - false - list - - - - 1 - 2 - 3 - 4 - - - - - - priority - 4 - false - - - - 1 - 2 - 3 - 4 - - urgency - 4 - false - - - - mail - phone - portal - monitoring - - origin - phone - true - - - org_id]]> - - - - approver_id - Person - true - DEL_MANUAL - false - - - - approver_id - email - true - - - org_id AND s.status != 'obsolete']]> - - - - service_id - Service - true - DEL_MANUAL - false - - - - service_id - name - true - - - service_id AND (ISNULL(:this->request_type) OR request_type = :this->request_type) AND status != 'obsolete']]> - - - - - servicesubcategory_id - ServiceSubcategory - true - DEL_MANUAL - false - - - - servicesubcategory_id - name - true - - - - yes - no - - escalation_flag - no - true - - - escalation_reason - - true - - - assignment_date - - true - - - resolution_date - - true - - - last_pending_date - - true - - - - - - - - - - - true - - - - - - - ResponseTicketTTO - - - - warning - false - - - - - - - critical - false - - - - ApplyStimulus - - ev_timeout - - - - - - - - true - - - - - - - - - ResponseTicketTTR - - - - warning - false - - - - - - - critical - false - - - - ApplyStimulus - - ev_timeout - - - - - - - - tto - 100_deadline - - - tto - 100_passed - - - tto - 100_overrun - - - ttr - 100_deadline - - - ttr - 100_passed - - - ttr - 100_overrun - - - time_spent - - true - - - - assistance - other - software patch - training - hardware repair - system update - bug fixed - - resolution_code - assistance - true - - - solution - - true - - - pending_reason - - true - - - id AND status NOT IN ('rejected','resolved','closed')]]> - - - parent_request_id - UserRequest - true - DEL_MANUAL - - - - parent_request_id - ref - true - - - parent_problem_id - Problem - true - DEL_MANUAL - - - - parent_problem_id - ref - true - - - - parent_change_id - Change - true - DEL_MANUAL - - - - parent_change_id - ref - true - - - UserRequest - parent_request_id - add_remove - 0 - 0 - - - public_log - - true - - - - 1 - 2 - 3 - 4 - - user_satisfaction - 1 - true - - - user_commment - - true - - - - - - 1 - HIGHLIGHT_CLASS_WARNING - images/user-request-deadline.png - - - 2 - HIGHLIGHT_CLASS_CRITICAL - images/user-request-escalated.png - - - 3 - HIGHLIGHT_CLASS_NONE - images/user-request-closed.png - - - status - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - assigned - - - SetCurrentDate - - assignment_date - - - - - - escalated_tto - - - - waiting_for_approval - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - - critical - - new - - - - - - assigned - - - SetCurrentDate - - assignment_date - - - - - - - - new - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - pending - - - SetCurrentDate - - last_pending_date - - - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - assigned - - - - - - - - - - - - escalated_ttr - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - - critical - - assigned - - - - pending - - - SetCurrentDate - - last_pending_date - - - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - assigned - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - new - - - - - - - - - approved - - - - rejected - - - - - - waiting_for_approval - - - - - - - - escalated_tto - - - - assigned - - - SetCurrentDate - - assignment_date - - - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - new - - - - - - - - - new - - - - - - assigned - - - - - - - - - - - - - - - assigned - - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - - closed - - assigned - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - closed - - - SetCurrentDate - - close_date - - - - - - assigned - - - - - resolved - - - SetCurrentDate - - resolution_date - - - - SetElapsedTime - - time_spent - start_date - DefaultWorkingTimeComputer - - - - ResolveChildTickets - - - - - - - - - closed - - resolved - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bizmodel,searchable,requestmgmt + false + autoincrement + ticket_request + id + + + + + + + + images/user-request.png + + + + + + + + + + + + + + true + + new + waiting_for_approval + approved + rejected + assigned + pending + escalated_tto + escalated_ttr + resolved + closed + + status + new + false + + + + incident + service_request + + request_type + + true + list + + + + 1 + 2 + 3 + + impact + 1 + false + list + + + + 1 + 2 + 3 + 4 + + + + + + priority + 4 + false + + + + 1 + 2 + 3 + 4 + + urgency + 4 + false + + + + mail + phone + portal + monitoring + + origin + phone + true + + + org_id]]> + + + + approver_id + Person + true + DEL_MANUAL + false + + + + approver_id + email + true + + + + org_id AND s.status != 'obsolete']]> + + + + service_id + Service + true + DEL_MANUAL + false + + + + service_id + name + true + + + + service_id AND (ISNULL(:this->request_type) OR request_type = :this->request_type) AND status != 'obsolete']]> + + + + + servicesubcategory_id + ServiceSubcategory + true + DEL_MANUAL + false + + + + servicesubcategory_id + name + true + + + + yes + no + + escalation_flag + no + true + + + escalation_reason + + true + + + assignment_date + + true + + + resolution_date + + true + + + last_pending_date + + true + + + + + + + + + + + true + + + + + + + ResponseTicketTTO + + + + warning + false + + + + + + + critical + false + + + + ApplyStimulus + + ev_timeout + + + + + + + + true + + + + + + + + + ResponseTicketTTR + + + + warning + false + + + + + + + critical + false + + + + ApplyStimulus + + ev_timeout + + + + + + + + tto + 100_deadline + + + tto + 100_passed + + + tto + 100_overrun + + + ttr + 100_deadline + + + ttr + 100_passed + + + ttr + 100_overrun + + + time_spent + + true + + + + assistance + other + software patch + training + hardware repair + system update + bug fixed + + resolution_code + assistance + true + + + solution + + true + + + pending_reason + + true + + + + id AND status NOT IN ('rejected','resolved','closed')]]> + + + parent_request_id + UserRequest + true + DEL_MANUAL + + + + parent_request_id + ref + true + + + parent_problem_id + Problem + true + DEL_MANUAL + + + + parent_problem_id + ref + true + + + + parent_change_id + Change + true + DEL_MANUAL + + + + parent_change_id + ref + true + + + UserRequest + parent_request_id + add_remove + 0 + 0 + + + public_log + + true + + + + 1 + 2 + 3 + 4 + + user_satisfaction + 1 + true + + + user_commment + + true + + + + + + 1 + HIGHLIGHT_CLASS_WARNING + images/user-request-deadline.png + + + 2 + HIGHLIGHT_CLASS_CRITICAL + images/user-request-escalated.png + + + 3 + HIGHLIGHT_CLASS_NONE + images/user-request-closed.png + + + status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + assigned + + + SetCurrentDate + + assignment_date + + + + + + escalated_tto + + + + waiting_for_approval + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + + critical + + new + + + + + + assigned + + + SetCurrentDate + + assignment_date + + + + + + + + new + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pending + + + SetCurrentDate + + last_pending_date + + + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + assigned + + + + + + + + + + + + escalated_ttr + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + + critical + + assigned + + + + pending + + + SetCurrentDate + + last_pending_date + + + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + assigned + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + new + + + + + + + + + approved + + + + rejected + + + + + + waiting_for_approval + + + + + + + + escalated_tto + + + + assigned + + + SetCurrentDate + + assignment_date + + + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + new + + + + + + + + + new + + + + + + assigned + + + + + + + + + + + + + + + assigned + + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + + closed + + assigned + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + closed + + + SetCurrentDate + + close_date + + + + + + assigned + + + + + resolved + + + SetCurrentDate + + resolution_date + + + + SetElapsedTime + + time_spent + start_date + DefaultWorkingTimeComputer + + + + ResolveChildTickets + + + + + + + + + closed + + resolved + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - false - public - LifecycleAction - false + public + LifecycleAction + Set('assignment_date', time()); return true; }]]> - - - + + - false - public - LifecycleAction - false + public + LifecycleAction + Set('last_pending_date', time()); return true; }]]> - - - + + - false - public - LifecycleAction - false + public + LifecycleAction + Set('resolution_date', time()); $iTimeSpent = time() - AttributeDateTime::GetAsUnixSeconds($this->Get('start_date')); $this->Set('time_spent', $iTimeSpent); return true; }]]> - - - + + - false - public - LifecycleAction - false + public + LifecycleAction + Set('close_date', time()); return true; }]]> - - - + + - false - public - LifecycleAction - false + public + LifecycleAction + Set('approver_id', UserRights::GetUserId()); return true; }]]> - - - false - public - Overload-DBObject - + + false + public + Overload-DBObject + IsNew() && ($this->Get('parent_request_id') == $this->GetKey())) @@ -1160,15 +1170,16 @@ $this->m_aCheckIssues[] = Dict::Format('Class:UserRequest/Error:CannotAssignParentRequestIdToSelf'); } }]]> - - - /** Compute the priority of the ticket based on its impact and urgency - * @return integer The priority of the ticket 1(high) .. 3(low) - */ - false - public - LifecycleAction - + + /** Compute the priority of the ticket based on its impact and urgency + * @return integer The priority of the ticket 1(high) .. 3(low) + */ + + false + public + LifecycleAction + - - - false - public - Overload-DBObject - + + false + public + Overload-DBObject + Set('priority', $this->ComputePriority()); @@ -1225,12 +1236,12 @@ return parent::ComputeValues(); }]]> - - - false - public - Overload-cmdbAbstractObject - + + false + public + Overload-cmdbAbstractObject + - - - + + - false - public - LifecycleAction - false + public + LifecycleAction + - - - + + - false - public - LifecycleAction - - - false + public + LifecycleAction + + + - - - + + - false - public - Internal - false + public + Internal + Get('status') != 'resolved') { @@ -1342,12 +1353,12 @@ $this->DBUpdate(); } }]]> - - - false - public - LifecycleAction - + + false + public + LifecycleAction + Get('public_log'); $sLogPublic = $oLog->GetModifiedEntry('html'); @@ -1387,12 +1398,12 @@ return true; }]]> - - - false - public - LifecycleAction - + + false + public + LifecycleAction + UpdateImpactedItems(); }]]> - - - false - protected - Overload-DBObject - + + false + protected + Overload-DBObject + Set('last_update', time()); $this->Set('start_date', time()); }]]> - - - false - protected - Overload-DBObject - + + false + protected + Overload-DBObject + Set('last_update', time()); $this->UpdateChildRequestLog(); }]]> - - - -
    - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - 10 - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - 60 - - - - - 20 - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - - - - - 60 - - - 10 - - - 10 - - - 20 - - - 30 - - - 40 - - - - - 20 - - - 10 - - - 20 - - - 30 - - - - - 30 - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - 60 - - - 70 - - - 80 - - - - - - - 70 - - - 10 - - - 10 - - - 20 - - - 30 - - - - - 20 - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - - - 30 - - - 10 - - - 20 - - - 30 - - - 40 - - - - - - -
    - - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - 70 - - - 80 - - - 90 - - - 95 - - - 100 - - - 110 - - - 120 - - - 130 - - - 140 - - - 150 - - - 160 - - - 170 - - - 180 - - - 190 - - - 200 - - - 210 - - - 220 - - - 230 - - - 240 - - - - - - - 10 - - - 20 - - - 30 - - - 40 - - - 50 - - - 60 - - - -
    -
    -
    - - - - - - 1 - - - 0 - Menu:RequestManagement - itop-welcome-itil/images/user-request-deadline.png - Menu:UserRequest:OpenRequests - SELECT UserRequest WHERE status != "closed" - status - new,assigned,escalated_tto,escalated_ttr,resolved - - - 1 - UI:WelcomeMenu:MyCalls - SELECT UserRequest AS i WHERE i.agent_id = :current_contact_id AND status NOT IN ("closed", "resolved") - true - - - - - - - - 30 - - - - 0 - RequestManagement - - DashboardLayoutTwoCols - UI:RequestMgmtMenuOverview:Title - - - 0 - - - 0 - UI-RequestManagementOverview-RequestByType-last-14-days - SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date - request_type - - - - - - 1 - - - 0 - UI-RequestManagementOverview-Last-14-days - SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date - start_date:day_of_month - - - - - - 2 - - - 0 - UI-RequestManagementOverview-OpenRequestByStatus - SELECT UserRequest WHERE status NOT IN ('closed','rejected') - status - - - - - - 3 - - - 0 - UI-RequestManagementOverview-OpenRequestByAgent - SELECT UserRequest WHERE status NOT IN ('closed','rejected') - agent_id - - - - - - 4 - - - 0 - UI-RequestManagementOverview-OpenRequestByType - SELECT UserRequest WHERE status NOT IN ('closed','rejected') - finalclass - - - - - - 5 - - - 0 - UI-RequestManagementOverview-OpenRequestByCustomer - SELECT UserRequest WHERE status NOT IN ('closed','rejected') - org_id - - - - - - - - - 1 - RequestManagement - UserRequest - - - 2 - RequestManagement - UserRequest - - - 3 - RequestManagement - - - - 0 - UserRequest:Shortcuts - - - fast - - - 1 - UserRequest:Shortcuts - - - fast - - - 2 - UserRequest:Shortcuts - - 1 - fast - - - 3 - UserRequest:Shortcuts - - - fast - - - - - - - - - - - id)]]> - Tickets:Related:OpenIncidents - itop-request-mgmt/images/incident-red.png - yes - - - - - - - - - - - id)]]> - Tickets:Related:OpenIncidents - itop-request-mgmt/images/incident-red.png - yes - - - - - - - - - - - - - - - - Tickets:Related:OpenIncidents - itop-request-mgmt/images/incident-red.png - yes - - - - - - - - Tickets:Related:OpenIncidents - itop-request-mgmt/images/incident-red.png - yes - - - - - - - - + +
    + +
    + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + + + 20 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + + + + + 60 + + + 10 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + + + 20 + + + 10 + + + 20 + + + 30 + + + + + 30 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + 70 + + + 80 + + + + + + + 70 + + + 10 + + + 10 + + + 20 + + + 30 + + + + + 20 + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + + + 30 + + + 10 + + + 20 + + + 30 + + + 40 + + + + + + +
    + + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 70 + + + 80 + + + 90 + + + 95 + + + 100 + + + 110 + + + 120 + + + 130 + + + 140 + + + 150 + + + 160 + + + 170 + + + 180 + + + 190 + + + 200 + + + 210 + + + 220 + + + 230 + + + 240 + + + + + + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + +
    +
    +
    + + + + + + 1 + + + 0 + Menu:RequestManagement + itop-welcome-itil/images/user-request-deadline.png + Menu:UserRequest:OpenRequests + SELECT UserRequest WHERE status != "closed" + status + new,assigned,escalated_tto,escalated_ttr,resolved + + + 1 + UI:WelcomeMenu:MyCalls + SELECT UserRequest AS i WHERE i.agent_id = :current_contact_id AND status NOT IN + ("closed", "resolved") + + true + + + + + + + + 30 + + + + 0 + RequestManagement + + DashboardLayoutTwoCols + UI:RequestMgmtMenuOverview:Title + + + 0 + + + 0 + UI-RequestManagementOverview-RequestByType-last-14-days + SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date + request_type + + + + + + 1 + + + 0 + UI-RequestManagementOverview-Last-14-days + SELECT UserRequest WHERE DATE_SUB(NOW(), INTERVAL 14 DAY) < start_date + start_date:day_of_month + + + + + + 2 + + + 0 + UI-RequestManagementOverview-OpenRequestByStatus + SELECT UserRequest WHERE status NOT IN ('closed','rejected') + status + + + + + + 3 + + + 0 + UI-RequestManagementOverview-OpenRequestByAgent + SELECT UserRequest WHERE status NOT IN ('closed','rejected') + agent_id + + + + + + 4 + + + 0 + UI-RequestManagementOverview-OpenRequestByType + SELECT UserRequest WHERE status NOT IN ('closed','rejected') + finalclass + + + + + + 5 + + + 0 + UI-RequestManagementOverview-OpenRequestByCustomer + SELECT UserRequest WHERE status NOT IN ('closed','rejected') + org_id + + + + + + + + + 1 + RequestManagement + UserRequest + + + 2 + RequestManagement + UserRequest + + + 3 + RequestManagement + + + + 0 + UserRequest:Shortcuts + + + + fast + + + 1 + UserRequest:Shortcuts + + + + fast + + + 2 + UserRequest:Shortcuts + + 1 + fast + + + 3 + UserRequest:Shortcuts + + + fast + + + + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + yes + + + + + + + + + + + + id)]]> + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + yes + + + + + + + + + + + + + + + + + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + yes + + + + + + + + + Tickets:Related:OpenIncidents + itop-request-mgmt/images/incident-red.png + yes + + + + + + + +
    diff --git a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml index 15479c3983..9cd3cec9f4 100755 --- a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml +++ b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml index 1e6d98ac22..fc3a16ebbc 100755 --- a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml +++ b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml b/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml index 538e8604e0..2e96229528 100644 --- a/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml +++ b/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml @@ -1,5 +1,5 @@ - + DatacenterDevice diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 2fc03de439..89862b4844 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -1,5 +1,5 @@ - + service_id AND sc.org_id = :this->org_id AND slt.request_type = :request_type AND slt.priority = :this->priority]]> @@ -198,6 +198,9 @@ tagfield + true + all + 12 @@ -282,6 +285,9 @@ 75 + + 76 + 80 diff --git a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php index 39f5a372c9..7a2754a302 100755 --- a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php @@ -74,8 +74,9 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Ticket/Attribute:close_date+' => '', 'Class:Ticket/Attribute:private_log' => 'Private log', 'Class:Ticket/Attribute:private_log+' => '', - 'Class:Ticket/Attribute:contacts_list' => 'Contacts', + 'Class:Ticket/Attribute:contacts_list' => 'Contacts', 'Class:Ticket/Attribute:contacts_list+' => 'All the contacts linked to this ticket', + 'Class:Ticket/Attribute:tagfield' => 'Tags', 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', 'Class:Ticket/Attribute:functionalcis_list+' => 'All the configuration items impacted by this ticket. Items marked as "Computed" have been automatically marked as impacted. Items marked as "Not impacted" are excluded from the impact.', 'Class:Ticket/Attribute:workorders_list' => 'Work orders', diff --git a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php index 2b84a7de9d..e30d28cfc6 100755 --- a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php @@ -63,7 +63,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:Ticket/Attribute:private_log+' => '', 'Class:Ticket/Attribute:contacts_list' => 'Contacts', 'Class:Ticket/Attribute:contacts_list+' => '', - 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', + 'Class:Ticket/Attribute:tagfield' => 'Etiquette', + 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', 'Class:Ticket/Attribute:functionalcis_list+' => 'Tous les éléments de configuration impactés par ce ticket. Les éléments marqués comme "Calculés" sont le résultat du calcul de l\'analyse d\'impact. Les éléments marqués comme "Non impactés" sont exclus de cette analyse.', 'Class:Ticket/Attribute:workorders_list' => 'Tâches', 'Class:Ticket/Attribute:workorders_list+' => '', diff --git a/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml b/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml index 03d219ba10..e7faa08dba 100644 --- a/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml +++ b/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml @@ -1,5 +1,5 @@ - + FunctionalCI diff --git a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml index 86c439bd42..63b19d8ae2 100644 --- a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml +++ b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index e67683343a..0d2cba08d1 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -34,7 +34,17 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass', - 'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)', + 'Core:AttributeTagSet' => 'List of tags', + 'Core:AttributeTagSet+' => '', + 'Core:AttributeSet:placeholder' => 'click to add', + + 'Core:AttributeCaseLog' => 'Log', + 'Core:AttributeCaseLog+' => '', + + 'Core:AttributeMetaEnum' => 'Computed enum', + 'Core:AttributeMetaEnum+' => '', + + 'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)', 'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class', 'Core:AttributeInteger' => 'Integer', @@ -578,6 +588,15 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class', )); +// +// Class: TriggerOnObjectDelete +// + +Dict::Add('EN US', 'English', 'English', array( + 'Class:TriggerOnObjectDelete' => 'Trigger (on object deletion)', + 'Class:TriggerOnObjectDelete+' => 'Trigger on object deletion of [a child class of] the given class', +)); + // // Class: TriggerOnObjectUpdate // @@ -585,7 +604,7 @@ Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array( 'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)', 'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class', - 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target attributes', + 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '', )); @@ -628,7 +647,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:name' => 'Name', 'Class:SynchroDataSource/Attribute:name+' => 'Name', 'Class:SynchroDataSource/Attribute:description' => 'Description', - 'Class:SynchroDataSource/Attribute:status' => 'Status', //TODO: enum values + 'Class:SynchroDataSource/Attribute:status' => 'Status', 'Class:SynchroDataSource/Attribute:scope_class' => 'Target class', 'Class:SynchroDataSource/Attribute:user_id' => 'User', 'Class:SynchroDataSource/Attribute:notify_contact_id' => 'Contact to notify', @@ -637,7 +656,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyperlink a (small) image representing the application with which iTop is synchronized', 'Class:SynchroDataSource/Attribute:url_application' => 'Application\'s hyperlink', 'Class:SynchroDataSource/Attribute:url_application+' => 'Hyperlink to the iTop object in the external application with which iTop is synchronized (if applicable). Possible placeholders: $this->attribute$ and $replica->primary_key$', - 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy', //TODO enum values + 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy', 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Full load interval', 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'A complete reload of all data must occur at least as often as specified here', 'Class:SynchroDataSource/Attribute:action_on_zero' => 'Action on zero', @@ -648,7 +667,6 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:action_on_multiple+' => 'Action taken when the search returns more than one object', 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed', 'Class:SynchroDataSource/Attribute:user_delete_policy+' => 'Who is allowed to delete synchronized objects', - 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed', 'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Nobody', 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Administrators only', 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'All allowed users', @@ -666,7 +684,7 @@ Dict::Add('EN US', 'English', 'English', array( 'SynchroDataSource:Definition' => 'Definition', 'Core:SynchroAttributes' => 'Attributes', 'Core:SynchroStatus' => 'Status', - 'Core:Synchro:ErrorsLabel' => 'Errors', + 'Core:Synchro:ErrorsLabel' => 'Errors', 'Core:Synchro:CreatedLabel' => 'Created', 'Core:Synchro:ModifiedLabel' => 'Modified', 'Core:Synchro:UnchangedLabel' => 'Unchanged', @@ -693,18 +711,17 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:Synchro:label_obj_disappeared_errors' => 'Errors (%1$s)', 'Core:Synchro:label_obj_disappeared_no_action' => 'No Action (%1$s)', 'Core:Synchro:label_obj_unchanged' => 'Unchanged (%1$s)', - 'Core:Synchro:label_obj_updated' => 'Updated (%1$s)', + 'Core:Synchro:label_obj_updated' => 'Updated (%1$s)', 'Core:Synchro:label_obj_updated_errors' => 'Errors (%1$s)', 'Core:Synchro:label_obj_new_unchanged' => 'Unchanged (%1$s)', 'Core:Synchro:label_obj_new_updated' => 'Updated (%1$s)', 'Core:Synchro:label_obj_created' => 'Created (%1$s)', 'Core:Synchro:label_obj_new_errors' => 'Errors (%1$s)', - 'Core:Synchro:History' => 'Synchronization History', 'Core:SynchroLogTitle' => '%1$s - %2$s', 'Core:Synchro:Nb_Replica' => 'Replica processed: %1$s', 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', - 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.', - 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete', + 'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.', + 'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete', 'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.', 'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'The table %1$s already exists in the database. Please use another name for the synchro data table.', 'Core:SynchroReplica:PublicData' => 'Public Data', @@ -833,16 +850,16 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)', // Attribute Duration - 'Core:Duration_Seconds' => '%1$ds', - 'Core:Duration_Minutes_Seconds' =>'%1$dmin %2$ds', - 'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds', - 'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds', + 'Core:Duration_Seconds' => '%1$ds', + 'Core:Duration_Minutes_Seconds' =>'%1$dmin %2$ds', + 'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds', + 'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds', // Explain working time computing 'Core:ExplainWTC:ElapsedTime' => 'Time elapsed (stored as "%1$s")', 'Core:ExplainWTC:StopWatch-TimeSpent' => 'Time spent for "%1$s"', 'Core:ExplainWTC:StopWatch-Deadline' => 'Deadline for "%1$s" at %2$d%%', - + // Bulk export 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter "%1$s"', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter "query". There is no Query Phrasebook corresponding to the id: "%1$s".', @@ -913,3 +930,29 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:Validator:MustBeInteger' => 'Must be an integer', 'Core:Validator:MustSelectOne' => 'Please, select one', )); + +// +// Class: TagSetFieldData +// +Dict::Add('EN US', 'English', 'English', array( + 'Class:TagSetFieldData' => '%2$s for class %1$s', + 'Class:TagSetFieldData+' => '', + + 'Class:TagSetFieldData/Attribute:code' => 'Code', + 'Class:TagSetFieldData/Attribute:code+' => 'Internal code. Must contain at least 3 alphanumeric characters', + 'Class:TagSetFieldData/Attribute:label' => 'Label', + 'Class:TagSetFieldData/Attribute:label+' => 'Displayed label', + 'Class:TagSetFieldData/Attribute:description' => 'Description', + 'Class:TagSetFieldData/Attribute:description+' => 'Description', + + 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Used tags cannot be deleted', + 'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique', + 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code must contain between 3 and %1$d alphanumeric characters', + 'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'The chosen tag code is a reserved word', + 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Tags label must not contain \'%1$s\' nor be empty', + 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags Code cannot be changed when used', + 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Tags "Object Class" cannot be changed', + 'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Tags "Attribute Code" cannot be changed', + 'Core:TagSetFieldData:WhereIsThisTagTab' => 'Tag usage (%1$d)', + 'Core:TagSetFieldData:NoEntryFound' => 'No entry found for this tag', +)); diff --git a/dictionaries/en.dictionary.itop.ui.php b/dictionaries/en.dictionary.itop.ui.php index 89b7269850..b5130f6061 100644 --- a/dictionaries/en.dictionary.itop.ui.php +++ b/dictionaries/en.dictionary.itop.ui.php @@ -956,7 +956,12 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:NotificationsMenu:OnStateLeave' => 'When an object leaves a given state', 'UI:NotificationsMenu:Actions' => 'Actions', 'UI:NotificationsMenu:AvailableActions' => 'Available actions', - + + 'Menu:TagAdminMenu' => 'Tags configuration', + 'Menu:TagAdminMenu+' => 'Tags values management', + 'UI:TagAdminMenu:Title' => 'Tags configuration', + 'UI:TagSetFieldData:Error' => 'Error: %1$s', + 'Menu:AuditCategories' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:AuditCategories+' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...) 'Menu:Notifications:Title' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...) @@ -1447,6 +1452,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Search:Criteria:Title:Enum:In' => '%1$s: %2$s', 'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s: %2$s and %3$s others', 'UI:Search:Criteria:Title:Enum:In:All' => '%1$s: Any', + // - TagSet widget + 'UI:Search:Criteria:Title:TagSet:Matches' => '%1$s: %2$s', // - External key widget 'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s is defined', 'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s is not defined', @@ -1480,6 +1487,8 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Less', // => '<', 'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Less / equals', // > '<=', 'UI:Search:Criteria:Operator:Numeric:Different' => 'Different', // => '≠', + // - Tag Set Widget + 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Matches', // - Other translations 'UI:Search:Value:Filter:Placeholder' => 'Filter...', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 2f8b77ffcf..762cd07a9e 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -61,6 +61,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:TriggerOnStateLeave+' => '', 'Class:TriggerOnObjectCreate' => 'Déclencheur sur la création d\'un objet', 'Class:TriggerOnObjectCreate+' => '', + 'Class:TriggerOnObjectDelete' => 'Déclencheur sur la suppression d\'un objet', + 'Class:TriggerOnObjectDelete+' => '', 'Class:TriggerOnObjectUpdate' => 'Déclencheur sur la modification d\'un objet', 'Class:TriggerOnObjectUpdate+' => '', 'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Attributs cible', @@ -430,6 +432,9 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:appUserPreferences/Attribute:preferences+' => '', 'Core:AttributeLinkedSet' => 'Objets liés (1-n)', 'Core:AttributeLinkedSet+' => 'Liste d\'objets d\'une classe donnée et pointant sur l\'objet courant', + 'Core:AttributeTagSet' => 'Liste d\'étiquettes', + 'Core:AttributeTagSet+' => '', + 'Core:AttributeSet:placeholder' => 'cliquer pour ajouter', 'Core:AttributeLinkedSetIndirect' => 'Objets liés (1-n)', 'Core:AttributeLinkedSetIndirect+' => 'Liste d\'objets d\'une classe donnée et liés à l\'objet courant via une classe intermédiaire', 'Core:AttributeInteger' => 'Nombre entier', @@ -766,4 +771,26 @@ Opérateurs :
    'Core:Validator:Mandatory' => 'Veuillez remplir ce champ', 'Core:Validator:MustBeInteger' => 'Ce champ ne peut contenir qu\'un nombre entier', 'Core:Validator:MustSelectOne' => 'Veuillez choisir une valeur', + + 'Class:TagSetFieldData' => '%2$s pour la classe %1$s', + 'Class:TagSetFieldData+' => '', + + 'Class:TagSetFieldData/Attribute:code' => 'Code', + 'Class:TagSetFieldData/Attribute:code+' => 'Code interne. Doit contenir au moins 3 caractères alphanumériques', + 'Class:TagSetFieldData/Attribute:label' => 'Label', + 'Class:TagSetFieldData/Attribute:label+' => 'Label', + 'Class:TagSetFieldData/Attribute:description' => 'Description', + 'Class:TagSetFieldData/Attribute:description+' => 'Description', + + 'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Impossible de supprimer une étiquette utilisée', + 'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Les codes et noms des étiquettes doivent être unique', + 'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Le code de l\'étiquette doit contenir entre 3 et %1$d caractères alphanumériques.', + 'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'Le code de l\'étiquette un mot réservé.', + 'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Le nom de l\'étiquette ne doit pas être vide ni contenir le caractère \'%1$s\'', + 'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Le code de l\'étiquette ne peut pas être changé', + 'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'La classe de l\'étiquette ne peut pas être changée', + 'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'L\'attribut de l\'étiquette ne peut pas être changé', + 'Core:TagSetFieldData:WhereIsThisTagTab' => 'Utilisation (%1$d)', + 'Core:TagSetFieldData:NoEntryFound' => 'Pas d\'utilisation de cette étiquette', + )); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index cac02df9d8..d9cc4d0775 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1280,6 +1280,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Search:Criteria:Title:Enum:In' => '%1$s : %2$s', 'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s : %2$s et %3$s autres', 'UI:Search:Criteria:Title:Enum:In:All' => '%1$s : Indifférent', + // - TagSet widget + 'UI:Search:Criteria:Title:TagSet:Matches' => '%1$s : %2$s', // - External key widget 'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s est renseigné', 'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s n\'est pas renseigné', @@ -1294,6 +1296,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Search:Criteria:Title:HierarchicalKey:In' => '%1$s : %2$s', 'UI:Search:Criteria:Title:HierarchicalKey:In:Many' => '%1$s : %2$s et %3$s autres', 'UI:Search:Criteria:Title:HierarchicalKey:In:All' => '%1$s : Indifférent', + // - Tag Set Widget + 'UI:Search:Criteria:Operator:TagSet:Matches' => 'Contient', /// - Criteria operators // - Default widget @@ -1339,6 +1343,12 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:Search:Criteria:Raw:Filtered' => 'Filtré', 'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s', + + // - Tags admin + 'Menu:TagAdminMenu' => 'Etiquettes', + 'Menu:TagAdminMenu+' => 'Gestion des étiquettes', + 'UI:TagAdminMenu:Title' => 'Gestion des étiquettes', + 'UI:TagSetFieldData:Error' => 'Erreur: %1$s', )); diff --git a/js/jquery.itop-set-widget.js b/js/jquery.itop-set-widget.js new file mode 100644 index 0000000000..8627ca0d2b --- /dev/null +++ b/js/jquery.itop-set-widget.js @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2010-2018 Combodo SARL + * + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with iTop. If not, see + * + */ + +/** + *

    To be applied on a field containing a JSON value. The value will be updated on every change.
    + * Exemple of JSON value : + * + * { + * "possible_values": [ + * { + * "code": "critical", + * "label": "Critical ticket" + * }, + * { + * "code": "high", + * "label": "don't forget it !" + * }, + * { + * "code": "normal", + * "label": "when time available" + * }, + * { + * "code": "low", + * "label": "don't worry ;)" + * } + * ], + * "max_items_allowed": 20, + * "partial_values": [], + * "orig_value": [ + * "critical" + * ], + * "added": [ + * "normal", + * "high", + * "low" + * ], + * "removed": ["critical"] + * } + * + * + *

    Needs js/selectize.js already loaded !! (https://github.com/selectize/selectize.js)
    + * In the future we could use WebPack... Or a solution like this : + * https://www.safaribooksonline.com/library/view/learning-javascript-design/9781449334840/ch13s09.html + */ +$.widget('itop.set_widget', + { + // default options + options: {isDebug: false}, + + PARENT_CSS_CLASS: "attribute-set", + ITEM_CSS_CLASS: "attribute-set-item", + + POSSIBLE_VAL_KEY: 'possible_values', + PARTIAL_VAL_KEY: "partial_values", + ORIG_VAL_KEY: "orig_value", + ADDED_VAL_KEY: "added", + REMOVED_VAL_KEY: "removed", + STATUS_ADDED: "added", + STATUS_REMOVED: "removed", + STATUS_NEUTRAL: "unchanged", + MAX_ITEMS_ALLOWED_KEY: "max_items_allowed", + + possibleValues: null, + partialValues: null, + originalValue: null, + /** will hold all interactions done : code as key and one of STATUS_* constant as value */ + setItemsCodesStatus: null, + + selectizeWidget: null, + maxItemsAllowed: null, + + // the constructor + _create: function () { + var $this = this.element; + + this._initWidgetData($this.val()); + this._generateSelectionWidget($this); + this._bindEvents($this); + }, + + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function () { + this.refresh(); + }, + + + _initWidgetData: function (originalFieldValue) { + var dataArray = JSON.parse(originalFieldValue), + setWidget = this; + this.possibleValues = dataArray[this.POSSIBLE_VAL_KEY]; + this.partialValues = ($.isArray(dataArray[this.PARTIAL_VAL_KEY])) ? dataArray[this.PARTIAL_VAL_KEY] : []; + this.originalValue = dataArray[this.ORIG_VAL_KEY]; + this.maxItemsAllowed = dataArray[this.MAX_ITEMS_ALLOWED_KEY]; + this.setItemsCodesStatus = {}; + + // load existing removed codes + // used for example in triggers update fields selection, after switching class + // class A + fields a,b selected, then switch to class B : the server sends fields a,b to as removed values + dataArray[this.REMOVED_VAL_KEY].forEach(function(setItemCode) { + setWidget.setItemsCodesStatus[setItemCode] = setWidget.STATUS_REMOVED; + }); + }, + + _generateSelectionWidget: function ($widgetElement) { + var $parentElement = $widgetElement.parent(), + isWidgetElementDisabled = $widgetElement.prop("disabled"), + inputId = $widgetElement.attr("id") + "-setwidget-values"; + + $parentElement.append(""); + var $inputWidget = $("#" + inputId); + if (isWidgetElementDisabled) { + $inputWidget.prop("disabled", true); + } + + // create closure to have both set widget and Selectize instances available in callbacks + // selectize instance could also be retrieve on the source input DOM node (selectize property) + // I think this is much clearer this way ! + var setWidget = this; + + $inputWidget.selectize({ + plugins: ['remove_button'], + delimiter: ' ', + maxItems: this.maxItemsAllowed, + hideSelected: true, + valueField: 'code', + labelField: 'label', + searchField: 'label', + options: this.possibleValues, + create: false, + placeholder: Dict.S("Core:AttributeSet:placeholder"), + onInitialize: function () { + var selectizeWidget = this; + setWidget._onInitialize(selectizeWidget); + }, + onItemAdd: function (value, $item) { + var selectizeWidget = this; + setWidget._onTagAdd(value, $item, selectizeWidget); + }, + onItemRemove: function (value) { + var selectizeWidget = this; + setWidget._onTagRemove(value, selectizeWidget); + } + }); + + this.selectizeWidget = $inputWidget[0].selectize; // keeping this for set widget public methods + }, + + _bindEvents: function($widgetElement) { + var setWidget = this; + $widgetElement.bind("update", function() { + console.debug("update event in Selectize !", this); + var $this = $(this); + if ($this.prop("disabled")) { + setWidget.disable(); + } else { + setWidget.enable(); + } + }); + }, + + refresh: function () { + if (this.options.isDebug) { + console.debug("refresh"); + } + var widgetPublicData = {}, addedValues = [], removedValues = []; + + widgetPublicData[this.POSSIBLE_VAL_KEY] = this.possibleValues; + widgetPublicData[this.PARTIAL_VAL_KEY] = this.partialValues; + widgetPublicData[this.ORIG_VAL_KEY] = this.originalValue; + + for (var setItemCode in this.setItemsCodesStatus) { + var setItemCodeStatus = this.setItemsCodesStatus[setItemCode]; + switch (setItemCodeStatus) { + case this.STATUS_ADDED: + addedValues.push(setItemCode); + break; + case this.STATUS_REMOVED: + removedValues.push(setItemCode); + break; + } + } + widgetPublicData[this.ADDED_VAL_KEY] = addedValues; + widgetPublicData[this.REMOVED_VAL_KEY] = removedValues; + + this.element.val(JSON.stringify(widgetPublicData, null, (this.options.isDebug ? 2 : null))); + }, + + disable: function () { + this.selectizeWidget.disable(); + }, + + enable: function () { + this.selectizeWidget.enable(); + }, + + /** + *

    Updating selection widget : + *

      + *
    • handles bulk edit disabling on widget opening + *
    • adding specific CSS class to parent node + *
    • adding specific CSS classes to item node + *
    • items to have a specific rendering for partial codes. + *
    + * + *

    For partial codes at first I was thinking about using the Selectize render callback, but it is called before onItemAdd/onItemRemove :(
    + * Indeed as we only need to have partial items on first display, this callback is the right place O:) + * + * @param inputWidget Selectize object + * @private + */ + _onInitialize: function (inputWidget) { + var setWidget = this; + if (this.options.isDebug) { + console.debug("onInit", inputWidget, setWidget); + } + + if (inputWidget.$input.prop("disabled")) { + inputWidget.disable(); // can't use this.selectizeWidget for now + } + + inputWidget.$control.addClass(setWidget.PARENT_CSS_CLASS); + + inputWidget.items.forEach(function (setItemCode) { + var $item = inputWidget.getItem(setItemCode); + $item.addClass(setWidget.ITEM_CSS_CLASS); + $item.addClass(setWidget.ITEM_CSS_CLASS + '-' + setItemCode); // no escape as codes are already pretty restrictive + + if (setWidget._isCodeInPartialValues(setItemCode)) { + inputWidget.getItem(setItemCode).addClass("partial-code"); + } + }); + }, + + _onTagAdd: function (setItemCode, $item, inputWidget) { + if (this.options.isDebug) { + console.debug("tagAdd"); + } + this.setItemsCodesStatus[setItemCode] = this.STATUS_ADDED; + + if (this._isCodeInPartialValues(setItemCode)) { + this.partialValues = this.partialValues.filter(item => (item !== setItemCode)); + } else { + if (this.originalValue.indexOf(setItemCode) !== -1) { + // do not add if was present initially and removed + this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL; + } + } + + this.refresh(); + }, + + _onTagRemove: function (setItemCode, inputWidget) { + this.setItemsCodesStatus[setItemCode] = this.STATUS_REMOVED; + + if (this._isCodeInPartialValues(setItemCode)) { + // force rendering items again, otherwise partial class will be kept + // can'be in the onItemAdd callback as it is called after the render callback... + inputWidget.clearCache("item"); + } + + if (this.originalValue.indexOf(setItemCode) === -1) { + // do not remove if wasn't present initially + this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL; + } + + this.refresh(); + }, + + _isCodeInPartialValues: function (setItemCode) { + return (this.partialValues.indexOf(setItemCode) >= 0); + } + }); \ No newline at end of file diff --git a/js/search/search_form_criteria_tag_set.js b/js/search/search_form_criteria_tag_set.js new file mode 100644 index 0000000000..0e77f7c3bb --- /dev/null +++ b/js/search/search_form_criteria_tag_set.js @@ -0,0 +1,91 @@ +//iTop Search form criteria tag_set +; +$(function() +{ + // the widget definition, where 'itop' is the namespace, + // 'search_form_criteria_tag_set' the widget name + $.widget( 'itop.search_form_criteria_tag_set', $.itop.search_form_criteria_enum, + { + // default options + options: + { + // Overload default operator + 'operator': 'MATCHES', + // Available operators + 'available_operators': { + 'MATCHES': { + 'label': Dict.S('UI:Search:Criteria:Operator:TagSet:MATCHES'), + 'code': 'matches', + 'rank': 10, + }, + 'IN': null, + '=': null, // Remove this one from tag_set widget. + 'empty': null, // Remove as it will be handle by the "null" value in the "MATCHES" operator + 'not_empty': null, // Remove as it will be handle by the "null" value in the "MATCHES" operator + }, + // Null value + 'null_value': { + 'code': '', + 'label': Dict.S('Enum:Undefined'), + }, + }, + + + // the constructor + _create: function() + { + var me = this; + + this._super(); + this.element.addClass('search_form_criteria_tag_set'); + }, + // called when created, and later when changing options + _refresh: function() + { + + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + this.element.removeClass('search_form_criteria_tag_set'); + this._super(); + }, + // _setOptions is called with a hash of all options that are changing + // always refresh when changing options + _setOptions: function() + { + this._superApply(arguments); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + this._super( key, value ); + }, + + //------------------ + // Inherited methods + //------------------ + _prepareMatchesOperator: function(oOpElem, sOpIdx, oOp) + { + this._prepareInOperator(oOpElem, sOpIdx, oOp); + }, + + // Operators helpers + // Reset operator's state + _resetMatchesOperator: function(oOpElem) + { + this._resetInOperator(oOpElem); + }, + // Get operator's values + _getMatchesOperatorValues: function(oOpElem) + { + return this._getInOperatorValues(oOpElem); + }, + // Set operator's values + _setMatchesOperatorValues: function(oOpElem, aValues) + { + return this._setInOperatorValues(oOpElem, aValues); + }, + }); +}); diff --git a/js/selectize.js b/js/selectize.js new file mode 100644 index 0000000000..0ccd2185a6 --- /dev/null +++ b/js/selectize.js @@ -0,0 +1,3829 @@ +/** + * sifter.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('sifter', factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.Sifter = factory(); + } +}(this, function() { + + /** + * Textually searches arrays and hashes of objects + * by property (or multiple properties). Designed + * specifically for autocomplete. + * + * @constructor + * @param {array|object} items + * @param {object} items + */ + var Sifter = function(items, settings) { + this.items = items; + this.settings = settings || {diacritics: true}; + }; + + /** + * Splits a search string into an array of individual + * regexps to be used to match results. + * + * @param {string} query + * @returns {array} + */ + Sifter.prototype.tokenize = function(query) { + query = trim(String(query || '').toLowerCase()); + if (!query || !query.length) return []; + + var i, n, regex, letter; + var tokens = []; + var words = query.split(/ +/); + + for (i = 0, n = words.length; i < n; i++) { + regex = escape_regex(words[i]); + if (this.settings.diacritics) { + for (letter in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(letter)) { + regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]); + } + } + } + tokens.push({ + string : words[i], + regex : new RegExp(regex, 'i') + }); + } + + return tokens; + }; + + /** + * Iterates over arrays and hashes. + * + * ``` + * this.iterator(this.items, function(item, id) { + * // invoked for each item + * }); + * ``` + * + * @param {array|object} object + */ + Sifter.prototype.iterator = function(object, callback) { + var iterator; + if (is_array(object)) { + iterator = Array.prototype.forEach || function(callback) { + for (var i = 0, n = this.length; i < n; i++) { + callback(this[i], i, this); + } + }; + } else { + iterator = function(callback) { + for (var key in this) { + if (this.hasOwnProperty(key)) { + callback(this[key], key, this); + } + } + }; + } + + iterator.apply(object, [callback]); + }; + + /** + * Returns a function to be used to score individual results. + * + * Good matches will have a higher score than poor matches. + * If an item is not a match, 0 will be returned by the function. + * + * @param {object|string} search + * @param {object} options (optional) + * @returns {function} + */ + Sifter.prototype.getScoreFunction = function(search, options) { + var self, fields, tokens, token_count, nesting; + + self = this; + search = self.prepareSearch(search, options); + tokens = search.tokens; + fields = search.options.fields; + token_count = tokens.length; + nesting = search.options.nesting; + + /** + * Calculates how close of a match the + * given value is against a search token. + * + * @param {mixed} value + * @param {object} token + * @return {number} + */ + var scoreValue = function(value, token) { + var score, pos; + + if (!value) return 0; + value = String(value || ''); + pos = value.search(token.regex); + if (pos === -1) return 0; + score = token.string.length / value.length; + if (pos === 0) score += 0.5; + return score; + }; + + /** + * Calculates the score of an object + * against the search query. + * + * @param {object} token + * @param {object} data + * @return {number} + */ + var scoreObject = (function() { + var field_count = fields.length; + if (!field_count) { + return function() { return 0; }; + } + if (field_count === 1) { + return function(token, data) { + return scoreValue(getattr(data, fields[0], nesting), token); + }; + } + return function(token, data) { + for (var i = 0, sum = 0; i < field_count; i++) { + sum += scoreValue(getattr(data, fields[i], nesting), token); + } + return sum / field_count; + }; + })(); + + if (!token_count) { + return function() { return 0; }; + } + if (token_count === 1) { + return function(data) { + return scoreObject(tokens[0], data); + }; + } + + if (search.options.conjunction === 'and') { + return function(data) { + var score; + for (var i = 0, sum = 0; i < token_count; i++) { + score = scoreObject(tokens[i], data); + if (score <= 0) return 0; + sum += score; + } + return sum / token_count; + }; + } else { + return function(data) { + for (var i = 0, sum = 0; i < token_count; i++) { + sum += scoreObject(tokens[i], data); + } + return sum / token_count; + }; + } + }; + + /** + * Returns a function that can be used to compare two + * results, for sorting purposes. If no sorting should + * be performed, `null` will be returned. + * + * @param {string|object} search + * @param {object} options + * @return function(a,b) + */ + Sifter.prototype.getSortFunction = function(search, options) { + var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort; + + self = this; + search = self.prepareSearch(search, options); + sort = (!search.query && options.sort_empty) || options.sort; + + /** + * Fetches the specified sort field value + * from a search result item. + * + * @param {string} name + * @param {object} result + * @return {mixed} + */ + get_field = function(name, result) { + if (name === '$score') return result.score; + return getattr(self.items[result.id], name, options.nesting); + }; + + // parse options + fields = []; + if (sort) { + for (i = 0, n = sort.length; i < n; i++) { + if (search.query || sort[i].field !== '$score') { + fields.push(sort[i]); + } + } + } + + // the "$score" field is implied to be the primary + // sort field, unless it's manually specified + if (search.query) { + implicit_score = true; + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + implicit_score = false; + break; + } + } + if (implicit_score) { + fields.unshift({field: '$score', direction: 'desc'}); + } + } else { + for (i = 0, n = fields.length; i < n; i++) { + if (fields[i].field === '$score') { + fields.splice(i, 1); + break; + } + } + } + + multipliers = []; + for (i = 0, n = fields.length; i < n; i++) { + multipliers.push(fields[i].direction === 'desc' ? -1 : 1); + } + + // build function + fields_count = fields.length; + if (!fields_count) { + return null; + } else if (fields_count === 1) { + field = fields[0].field; + multiplier = multipliers[0]; + return function(a, b) { + return multiplier * cmp( + get_field(field, a), + get_field(field, b) + ); + }; + } else { + return function(a, b) { + var i, result, a_value, b_value, field; + for (i = 0; i < fields_count; i++) { + field = fields[i].field; + result = multipliers[i] * cmp( + get_field(field, a), + get_field(field, b) + ); + if (result) return result; + } + return 0; + }; + } + }; + + /** + * Parses a search query and returns an object + * with tokens and fields ready to be populated + * with results. + * + * @param {string} query + * @param {object} options + * @returns {object} + */ + Sifter.prototype.prepareSearch = function(query, options) { + if (typeof query === 'object') return query; + + options = extend({}, options); + + var option_fields = options.fields; + var option_sort = options.sort; + var option_sort_empty = options.sort_empty; + + if (option_fields && !is_array(option_fields)) options.fields = [option_fields]; + if (option_sort && !is_array(option_sort)) options.sort = [option_sort]; + if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty]; + + return { + options : options, + query : String(query || '').toLowerCase(), + tokens : this.tokenize(query), + total : 0, + items : [] + }; + }; + + /** + * Searches through all items and returns a sorted array of matches. + * + * The `options` parameter can contain: + * + * - fields {string|array} + * - sort {array} + * - score {function} + * - filter {bool} + * - limit {integer} + * + * Returns an object containing: + * + * - options {object} + * - query {string} + * - tokens {array} + * - total {int} + * - items {array} + * + * @param {string} query + * @param {object} options + * @returns {object} + */ + Sifter.prototype.search = function(query, options) { + var self = this, value, score, search, calculateScore; + var fn_sort; + var fn_score; + + search = this.prepareSearch(query, options); + options = search.options; + query = search.query; + + // generate result scoring function + fn_score = options.score || self.getScoreFunction(search); + + // perform search and sort + if (query.length) { + self.iterator(self.items, function(item, id) { + score = fn_score(item); + if (options.filter === false || score > 0) { + search.items.push({'score': score, 'id': id}); + } + }); + } else { + self.iterator(self.items, function(item, id) { + search.items.push({'score': 1, 'id': id}); + }); + } + + fn_sort = self.getSortFunction(search, options); + if (fn_sort) search.items.sort(fn_sort); + + // apply limits + search.total = search.items.length; + if (typeof options.limit === 'number') { + search.items = search.items.slice(0, options.limit); + } + + return search; + }; + + // utilities + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + var cmp = function(a, b) { + if (typeof a === 'number' && typeof b === 'number') { + return a > b ? 1 : (a < b ? -1 : 0); + } + a = asciifold(String(a || '')); + b = asciifold(String(b || '')); + if (a > b) return 1; + if (b > a) return -1; + return 0; + }; + + var extend = function(a, b) { + var i, n, k, object; + for (i = 1, n = arguments.length; i < n; i++) { + object = arguments[i]; + if (!object) continue; + for (k in object) { + if (object.hasOwnProperty(k)) { + a[k] = object[k]; + } + } + } + return a; + }; + + /** + * A property getter resolving dot-notation + * @param {Object} obj The root object to fetch property on + * @param {String} name The optionally dotted property name to fetch + * @param {Boolean} nesting Handle nesting or not + * @return {Object} The resolved property value + */ + var getattr = function(obj, name, nesting) { + if (!obj || !name) return; + if (!nesting) return obj[name]; + var names = name.split("."); + while(names.length && (obj = obj[names.shift()])); + return obj; + }; + + var trim = function(str) { + return (str + '').replace(/^\s+|\s+$|/g, ''); + }; + + var escape_regex = function(str) { + return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + }; + + var is_array = Array.isArray || (typeof $ !== 'undefined' && $.isArray) || function(object) { + return Object.prototype.toString.call(object) === '[object Array]'; + }; + + var DIACRITICS = { + 'a': '[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]', + 'b': '[b␢βΒB฿𐌁ᛒ]', + 'c': '[cĆćĈĉČčĊċC̄c̄ÇçḈḉȻȼƇƈɕᴄCc]', + 'd': '[dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]', + 'e': '[eÉéÈèÊêḘḙĚěĔĕẼẽḚḛẺẻĖėËëĒēȨȩĘęᶒɆɇȄȅẾếỀềỄễỂểḜḝḖḗḔḕȆȇẸẹỆệⱸᴇEeɘǝƏƐε]', + 'f': '[fƑƒḞḟ]', + 'g': '[gɢ₲ǤǥĜĝĞğĢģƓɠĠġ]', + 'h': '[hĤĥĦħḨḩẖẖḤḥḢḣɦʰǶƕ]', + 'i': '[iÍíÌìĬĭÎîǏǐÏïḮḯĨĩĮįĪīỈỉȈȉȊȋỊịḬḭƗɨɨ̆ᵻᶖİiIıɪIi]', + 'j': '[jȷĴĵɈɉʝɟʲ]', + 'k': '[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭]', + 'l': '[lŁłĽľĻļĹĺḶḷḸḹḼḽḺḻĿŀȽƚⱠⱡⱢɫɬᶅɭȴʟLl]', + 'n': '[nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]', + 'o': '[oØøÖöÓóÒòÔôǑǒŐőŎŏȮȯỌọƟɵƠơỎỏŌōÕõǪǫȌȍՕօ]', + 'p': '[pṔṕṖṗⱣᵽƤƥᵱ]', + 'q': '[qꝖꝗʠɊɋꝘꝙq̃]', + 'r': '[rŔŕɌɍŘřŖŗṘṙȐȑȒȓṚṛⱤɽ]', + 's': '[sŚśṠṡṢṣꞨꞩŜŝŠšŞşȘșS̈s̈]', + 't': '[tŤťṪṫŢţṬṭƮʈȚțṰṱṮṯƬƭ]', + 'u': '[uŬŭɄʉỤụÜüÚúÙùÛûǓǔŰűŬŭƯưỦủŪūŨũŲųȔȕ∪]', + 'v': '[vṼṽṾṿƲʋꝞꝟⱱʋ]', + 'w': '[wẂẃẀẁŴŵẄẅẆẇẈẉ]', + 'x': '[xẌẍẊẋχ]', + 'y': '[yÝýỲỳŶŷŸÿỸỹẎẏỴỵɎɏƳƴ]', + 'z': '[zŹźẐẑŽžŻżẒẓẔẕƵƶ]' + }; + + var asciifold = (function() { + var i, n, k, chunk; + var foreignletters = ''; + var lookup = {}; + for (k in DIACRITICS) { + if (DIACRITICS.hasOwnProperty(k)) { + chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1); + foreignletters += chunk; + for (i = 0, n = chunk.length; i < n; i++) { + lookup[chunk.charAt(i)] = k; + } + } + } + var regexp = new RegExp('[' + foreignletters + ']', 'g'); + return function(str) { + return str.replace(regexp, function(foreignletter) { + return lookup[foreignletter]; + }).toLowerCase(); + }; + })(); + + + // export + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + return Sifter; +})); + + + +/** + * microplugin.js + * Copyright (c) 2013 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('microplugin', factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.MicroPlugin = factory(); + } +}(this, function() { + var MicroPlugin = {}; + + MicroPlugin.mixin = function(Interface) { + Interface.plugins = {}; + + /** + * Initializes the listed plugins (with options). + * Acceptable formats: + * + * List (without options): + * ['a', 'b', 'c'] + * + * List (with options): + * [{'name': 'a', options: {}}, {'name': 'b', options: {}}] + * + * Hash (with options): + * {'a': { ... }, 'b': { ... }, 'c': { ... }} + * + * @param {mixed} plugins + */ + Interface.prototype.initializePlugins = function(plugins) { + var i, n, key; + var self = this; + var queue = []; + + self.plugins = { + names : [], + settings : {}, + requested : {}, + loaded : {} + }; + + if (utils.isArray(plugins)) { + for (i = 0, n = plugins.length; i < n; i++) { + if (typeof plugins[i] === 'string') { + queue.push(plugins[i]); + } else { + self.plugins.settings[plugins[i].name] = plugins[i].options; + queue.push(plugins[i].name); + } + } + } else if (plugins) { + for (key in plugins) { + if (plugins.hasOwnProperty(key)) { + self.plugins.settings[key] = plugins[key]; + queue.push(key); + } + } + } + + while (queue.length) { + self.require(queue.shift()); + } + }; + + Interface.prototype.loadPlugin = function(name) { + var self = this; + var plugins = self.plugins; + var plugin = Interface.plugins[name]; + + if (!Interface.plugins.hasOwnProperty(name)) { + throw new Error('Unable to find "' + name + '" plugin'); + } + + plugins.requested[name] = true; + plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]); + plugins.names.push(name); + }; + + /** + * Initializes a plugin. + * + * @param {string} name + */ + Interface.prototype.require = function(name) { + var self = this; + var plugins = self.plugins; + + if (!self.plugins.loaded.hasOwnProperty(name)) { + if (plugins.requested[name]) { + throw new Error('Plugin has circular dependency ("' + name + '")'); + } + self.loadPlugin(name); + } + + return plugins.loaded[name]; + }; + + /** + * Registers a plugin. + * + * @param {string} name + * @param {function} fn + */ + Interface.define = function(name, fn) { + Interface.plugins[name] = { + 'name' : name, + 'fn' : fn + }; + }; + }; + + var utils = { + isArray: Array.isArray || function(vArg) { + return Object.prototype.toString.call(vArg) === '[object Array]'; + } + }; + + return MicroPlugin; +})); + +/** + * selectize.js (v0.12.4) + * Copyright (c) 2013–2015 Brian Reavis & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + */ + +/*jshint curly:false */ +/*jshint browser:true */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define('selectize', ['jquery','sifter','microplugin'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); + } else { + root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); + } +}(this, function($, Sifter, MicroPlugin) { + 'use strict'; + + var highlight = function($element, pattern) { + if (typeof pattern === 'string' && !pattern.length) return; + var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; + + var highlight = function(node) { + var skip = 0; + if (node.nodeType === 3) { + var pos = node.data.search(regex); + if (pos >= 0 && node.data.length > 0) { + var match = node.data.match(regex); + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(pos); + var endbit = middlebit.splitText(match[0].length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + middlebit.parentNode.replaceChild(spannode, middlebit); + skip = 1; + } + } else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) { + for (var i = 0; i < node.childNodes.length; ++i) { + i += highlight(node.childNodes[i]); + } + } + return skip; + }; + + return $element.each(function() { + highlight(this); + }); + }; + + /** + * removeHighlight fn copied from highlight v5 and + * edited to remove with() and pass js strict mode + */ + $.fn.removeHighlight = function() { + return this.find("span.highlight").each(function() { + this.parentNode.firstChild.nodeName; + var parent = this.parentNode; + parent.replaceChild(this.firstChild, this); + parent.normalize(); + }).end(); + }; + + + var MicroEvent = function() {}; + MicroEvent.prototype = { + on: function(event, fct){ + this._events = this._events || {}; + this._events[event] = this._events[event] || []; + this._events[event].push(fct); + }, + off: function(event, fct){ + var n = arguments.length; + if (n === 0) return delete this._events; + if (n === 1) return delete this._events[event]; + + this._events = this._events || {}; + if (event in this._events === false) return; + this._events[event].splice(this._events[event].indexOf(fct), 1); + }, + trigger: function(event /* , args... */){ + this._events = this._events || {}; + if (event in this._events === false) return; + for (var i = 0; i < this._events[event].length; i++){ + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); + } + } + }; + + /** + * Mixin will delegate all MicroEvent.js function in the destination object. + * + * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent + * + * @param {object} the object which will support MicroEvent + */ + MicroEvent.mixin = function(destObject){ + var props = ['on', 'off', 'trigger']; + for (var i = 0; i < props.length; i++){ + destObject.prototype[props[i]] = MicroEvent.prototype[props[i]]; + } + }; + + var IS_MAC = /Mac/.test(navigator.userAgent); + + var KEY_A = 65; + var KEY_COMMA = 188; + var KEY_RETURN = 13; + var KEY_ESC = 27; + var KEY_LEFT = 37; + var KEY_UP = 38; + var KEY_P = 80; + var KEY_RIGHT = 39; + var KEY_DOWN = 40; + var KEY_N = 78; + var KEY_BACKSPACE = 8; + var KEY_DELETE = 46; + var KEY_SHIFT = 16; + var KEY_CMD = IS_MAC ? 91 : 17; + var KEY_CTRL = IS_MAC ? 18 : 17; + var KEY_TAB = 9; + + var TAG_SELECT = 1; + var TAG_INPUT = 2; + + // for now, android support in general is too spotty to support validity + var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('input').validity; + + + var isset = function(object) { + return typeof object !== 'undefined'; + }; + + /** + * Converts a scalar to its best string representation + * for hash keys and HTML attribute values. + * + * Transformations: + * 'str' -> 'str' + * null -> '' + * undefined -> '' + * true -> '1' + * false -> '0' + * 0 -> '0' + * 1 -> '1' + * + * @param {string} value + * @returns {string|null} + */ + var hash_key = function(value) { + if (typeof value === 'undefined' || value === null) return null; + if (typeof value === 'boolean') return value ? '1' : '0'; + return value + ''; + }; + + /** + * Escapes a string for use within HTML. + * + * @param {string} str + * @returns {string} + */ + var escape_html = function(str) { + return (str + '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + + /** + * Escapes "$" characters in replacement strings. + * + * @param {string} str + * @returns {string} + */ + var escape_replace = function(str) { + return (str + '').replace(/\$/g, '$$$$'); + }; + + var hook = {}; + + /** + * Wraps `method` on `self` so that `fn` + * is invoked before the original method. + * + * @param {object} self + * @param {string} method + * @param {function} fn + */ + hook.before = function(self, method, fn) { + var original = self[method]; + self[method] = function() { + fn.apply(self, arguments); + return original.apply(self, arguments); + }; + }; + + /** + * Wraps `method` on `self` so that `fn` + * is invoked after the original method. + * + * @param {object} self + * @param {string} method + * @param {function} fn + */ + hook.after = function(self, method, fn) { + var original = self[method]; + self[method] = function() { + var result = original.apply(self, arguments); + fn.apply(self, arguments); + return result; + }; + }; + + /** + * Wraps `fn` so that it can only be invoked once. + * + * @param {function} fn + * @returns {function} + */ + var once = function(fn) { + var called = false; + return function() { + if (called) return; + called = true; + fn.apply(this, arguments); + }; + }; + + /** + * Wraps `fn` so that it can only be called once + * every `delay` milliseconds (invoked on the falling edge). + * + * @param {function} fn + * @param {int} delay + * @returns {function} + */ + var debounce = function(fn, delay) { + var timeout; + return function() { + var self = this; + var args = arguments; + window.clearTimeout(timeout); + timeout = window.setTimeout(function() { + fn.apply(self, args); + }, delay); + }; + }; + + /** + * Debounce all fired events types listed in `types` + * while executing the provided `fn`. + * + * @param {object} self + * @param {array} types + * @param {function} fn + */ + var debounce_events = function(self, types, fn) { + var type; + var trigger = self.trigger; + var event_args = {}; + + // override trigger method + self.trigger = function() { + var type = arguments[0]; + if (types.indexOf(type) !== -1) { + event_args[type] = arguments; + } else { + return trigger.apply(self, arguments); + } + }; + + // invoke provided function + fn.apply(self, []); + self.trigger = trigger; + + // trigger queued events + for (type in event_args) { + if (event_args.hasOwnProperty(type)) { + trigger.apply(self, event_args[type]); + } + } + }; + + /** + * A workaround for http://bugs.jquery.com/ticket/6696 + * + * @param {object} $parent - Parent element to listen on. + * @param {string} event - Event name. + * @param {string} selector - Descendant selector to filter by. + * @param {function} fn - Event handler. + */ + var watchChildEvent = function($parent, event, selector, fn) { + $parent.on(event, selector, function(e) { + var child = e.target; + while (child && child.parentNode !== $parent[0]) { + child = child.parentNode; + } + e.currentTarget = child; + return fn.apply(this, [e]); + }); + }; + + /** + * Determines the current selection within a text input control. + * Returns an object containing: + * - start + * - length + * + * @param {object} input + * @returns {object} + */ + var getSelection = function(input) { + var result = {}; + if ('selectionStart' in input) { + result.start = input.selectionStart; + result.length = input.selectionEnd - result.start; + } else if (document.selection) { + input.focus(); + var sel = document.selection.createRange(); + var selLen = document.selection.createRange().text.length; + sel.moveStart('character', -input.value.length); + result.start = sel.text.length - selLen; + result.length = selLen; + } + return result; + }; + + /** + * Copies CSS properties from one element to another. + * + * @param {object} $from + * @param {object} $to + * @param {array} properties + */ + var transferStyles = function($from, $to, properties) { + var i, n, styles = {}; + if (properties) { + for (i = 0, n = properties.length; i < n; i++) { + styles[properties[i]] = $from.css(properties[i]); + } + } else { + styles = $from.css(); + } + $to.css(styles); + }; + + /** + * Measures the width of a string within a + * parent element (in pixels). + * + * @param {string} str + * @param {object} $parent + * @returns {int} + */ + var measureString = function(str, $parent) { + if (!str) { + return 0; + } + + var $test = $('').css({ + position: 'absolute', + top: -99999, + left: -99999, + width: 'auto', + padding: 0, + whiteSpace: 'pre' + }).text(str).appendTo('body'); + + transferStyles($parent, $test, [ + 'letterSpacing', + 'fontSize', + 'fontFamily', + 'fontWeight', + 'textTransform' + ]); + + var width = $test.width(); + $test.remove(); + + return width; + }; + + /** + * Sets up an input to grow horizontally as the user + * types. If the value is changed manually, you can + * trigger the "update" handler to resize: + * + * $input.trigger('update'); + * + * @param {object} $input + */ + var autoGrow = function($input) { + var currentWidth = null; + + var update = function(e, options) { + var value, keyCode, printable, placeholder, width; + var shift, character, selection; + e = e || window.event || {}; + options = options || {}; + + if (e.metaKey || e.altKey) return; + if (!options.force && $input.data('grow') === false) return; + + value = $input.val(); + if (e.type && e.type.toLowerCase() === 'keydown') { + keyCode = e.keyCode; + printable = ( + (keyCode >= 97 && keyCode <= 122) || // a-z + (keyCode >= 65 && keyCode <= 90) || // A-Z + (keyCode >= 48 && keyCode <= 57) || // 0-9 + keyCode === 32 // space + ); + + if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) { + selection = getSelection($input[0]); + if (selection.length) { + value = value.substring(0, selection.start) + value.substring(selection.start + selection.length); + } else if (keyCode === KEY_BACKSPACE && selection.start) { + value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1); + } else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') { + value = value.substring(0, selection.start) + value.substring(selection.start + 1); + } + } else if (printable) { + shift = e.shiftKey; + character = String.fromCharCode(e.keyCode); + if (shift) character = character.toUpperCase(); + else character = character.toLowerCase(); + value += character; + } + } + + placeholder = $input.attr('placeholder'); + if (!value && placeholder) { + value = placeholder; + } + + width = measureString(value, $input) + 4; + if (width !== currentWidth) { + currentWidth = width; + $input.width(width); + $input.triggerHandler('resize'); + } + }; + + $input.on('keydown keyup update blur', update); + update(); + }; + + var domToString = function(d) { + var tmp = document.createElement('div'); + + tmp.appendChild(d.cloneNode(true)); + + return tmp.innerHTML; + }; + + var logError = function(message, options){ + if(!options) options = {}; + var component = "Selectize"; + + console.error(component + ": " + message) + + if(options.explanation){ + // console.group is undefined in ').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode); + $control = $('

    ').addClass(settings.inputClass).addClass('items').appendTo($wrapper); + $control_input = $('').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex); + $dropdown_parent = $(settings.dropdownParent || $wrapper); + $dropdown = $('
    ').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent); + $dropdown_content = $('
    ').addClass(settings.dropdownContentClass).appendTo($dropdown); + + if(inputId = $input.attr('id')) { + $control_input.attr('id', inputId + '-selectized'); + $("label[for='"+inputId+"']").attr('for', inputId + '-selectized'); + } + + if(self.settings.copyClassesToDropdown) { + $dropdown.addClass(classes); + } + + $wrapper.css({ + width: $input[0].style.width + }); + + if (self.plugins.names.length) { + classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-'); + $wrapper.addClass(classes_plugins); + $dropdown.addClass(classes_plugins); + } + + if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) { + $input.attr('multiple', 'multiple'); + } + + if (self.settings.placeholder) { + $control_input.attr('placeholder', settings.placeholder); + } + + // if splitOn was not passed in, construct it from the delimiter to allow pasting universally + if (!self.settings.splitOn && self.settings.delimiter) { + var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*'); + } + + if ($input.attr('autocorrect')) { + $control_input.attr('autocorrect', $input.attr('autocorrect')); + } + + if ($input.attr('autocapitalize')) { + $control_input.attr('autocapitalize', $input.attr('autocapitalize')); + } + + self.$wrapper = $wrapper; + self.$control = $control; + self.$control_input = $control_input; + self.$dropdown = $dropdown; + self.$dropdown_content = $dropdown_content; + + $dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); }); + $dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); }); + watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); }); + autoGrow($control_input); + + $control.on({ + mousedown : function() { return self.onMouseDown.apply(self, arguments); }, + click : function() { return self.onClick.apply(self, arguments); } + }); + + $control_input.on({ + mousedown : function(e) { e.stopPropagation(); }, + keydown : function() { return self.onKeyDown.apply(self, arguments); }, + keyup : function() { return self.onKeyUp.apply(self, arguments); }, + keypress : function() { return self.onKeyPress.apply(self, arguments); }, + resize : function() { self.positionDropdown.apply(self, []); }, + blur : function() { return self.onBlur.apply(self, arguments); }, + focus : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); }, + paste : function() { return self.onPaste.apply(self, arguments); } + }); + + $document.on('keydown' + eventNS, function(e) { + self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey']; + self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey']; + self.isShiftDown = e.shiftKey; + }); + + $document.on('keyup' + eventNS, function(e) { + if (e.keyCode === KEY_CTRL) self.isCtrlDown = false; + if (e.keyCode === KEY_SHIFT) self.isShiftDown = false; + if (e.keyCode === KEY_CMD) self.isCmdDown = false; + }); + + $document.on('mousedown' + eventNS, function(e) { + if (self.isFocused) { + // prevent events on the dropdown scrollbar from causing the control to blur + if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) { + return false; + } + // blur on click outside + if (!self.$control.has(e.target).length && e.target !== self.$control[0]) { + self.blur(e.target); + } + } + }); + + $window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() { + if (self.isOpen) { + self.positionDropdown.apply(self, arguments); + } + }); + $window.on('mousemove' + eventNS, function() { + self.ignoreHover = false; + }); + + // store original children and tab index so that they can be + // restored when the destroy() method is called. + this.revertSettings = { + $children : $input.children().detach(), + tabindex : $input.attr('tabindex') + }; + + $input.attr('tabindex', -1).hide().after(self.$wrapper); + + if ($.isArray(settings.items)) { + self.setValue(settings.items); + delete settings.items; + } + + // feature detect for the validation API + if (SUPPORTS_VALIDITY_API) { + $input.on('invalid' + eventNS, function(e) { + e.preventDefault(); + self.isInvalid = true; + self.refreshState(); + }); + } + + self.updateOriginalInput(); + self.refreshItems(); + self.refreshState(); + self.updatePlaceholder(); + self.isSetup = true; + + if ($input.is(':disabled')) { + self.disable(); + } + + self.on('change', this.onChange); + + $input.data('selectize', self); + $input.addClass('selectized'); + self.trigger('initialize'); + + // preload options + if (settings.preload === true) { + self.onSearchChange(''); + } + + }, + + /** + * Sets up default rendering functions. + */ + setupTemplates: function() { + var self = this; + var field_label = self.settings.labelField; + var field_optgroup = self.settings.optgroupLabelField; + + var templates = { + 'optgroup': function(data) { + return '
    ' + data.html + '
    '; + }, + 'optgroup_header': function(data, escape) { + return '
    ' + escape(data[field_optgroup]) + '
    '; + }, + 'option': function(data, escape) { + return '
    ' + escape(data[field_label]) + '
    '; + }, + 'item': function(data, escape) { + return '
    ' + escape(data[field_label]) + '
    '; + }, + 'option_create': function(data, escape) { + return '
    Add ' + escape(data.input) + '
    '; + } + }; + + self.settings.render = $.extend({}, templates, self.settings.render); + }, + + /** + * Maps fired events to callbacks provided + * in the settings used when creating the control. + */ + setupCallbacks: function() { + var key, fn, callbacks = { + 'initialize' : 'onInitialize', + 'change' : 'onChange', + 'item_add' : 'onItemAdd', + 'item_remove' : 'onItemRemove', + 'clear' : 'onClear', + 'option_add' : 'onOptionAdd', + 'option_remove' : 'onOptionRemove', + 'option_clear' : 'onOptionClear', + 'optgroup_add' : 'onOptionGroupAdd', + 'optgroup_remove' : 'onOptionGroupRemove', + 'optgroup_clear' : 'onOptionGroupClear', + 'dropdown_open' : 'onDropdownOpen', + 'dropdown_close' : 'onDropdownClose', + 'type' : 'onType', + 'load' : 'onLoad', + 'focus' : 'onFocus', + 'blur' : 'onBlur' + }; + + for (key in callbacks) { + if (callbacks.hasOwnProperty(key)) { + fn = this.settings[callbacks[key]]; + if (fn) this.on(key, fn); + } + } + }, + + /** + * Triggered when the main control element + * has a click event. + * + * @param {object} e + * @return {boolean} + */ + onClick: function(e) { + var self = this; + + // necessary for mobile webkit devices (manual focus triggering + // is ignored unless invoked within a click event) + if (!self.isFocused) { + self.focus(); + e.preventDefault(); + } + }, + + /** + * Triggered when the main control element + * has a mouse down event. + * + * @param {object} e + * @return {boolean} + */ + onMouseDown: function(e) { + var self = this; + var defaultPrevented = e.isDefaultPrevented(); + var $target = $(e.target); + + if (self.isFocused) { + // retain focus by preventing native handling. if the + // event target is the input it should not be modified. + // otherwise, text selection within the input won't work. + if (e.target !== self.$control_input[0]) { + if (self.settings.mode === 'single') { + // toggle dropdown + self.isOpen ? self.close() : self.open(); + } else if (!defaultPrevented) { + self.setActiveItem(null); + } + return false; + } + } else { + // give control focus + if (!defaultPrevented) { + window.setTimeout(function() { + self.focus(); + }, 0); + } + } + }, + + /** + * Triggered when the value of the control has been changed. + * This should propagate the event to the original DOM + * input / select element. + */ + onChange: function() { + this.$input.trigger('change'); + }, + + /** + * Triggered on paste. + * + * @param {object} e + * @returns {boolean} + */ + onPaste: function(e) { + var self = this; + + if (self.isFull() || self.isInputHidden || self.isLocked) { + e.preventDefault(); + return; + } + + // If a regex or string is included, this will split the pasted + // input and create Items for each separate value + if (self.settings.splitOn) { + + // Wait for pasted text to be recognized in value + setTimeout(function() { + var pastedText = self.$control_input.val(); + if(!pastedText.match(self.settings.splitOn)){ return } + + var splitInput = $.trim(pastedText).split(self.settings.splitOn); + for (var i = 0, n = splitInput.length; i < n; i++) { + self.createItem(splitInput[i]); + } + }, 0); + } + }, + + /** + * Triggered on keypress. + * + * @param {object} e + * @returns {boolean} + */ + onKeyPress: function(e) { + if (this.isLocked) return e && e.preventDefault(); + var character = String.fromCharCode(e.keyCode || e.which); + if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) { + this.createItem(); + e.preventDefault(); + return false; + } + }, + + /** + * Triggered on keydown. + * + * @param {object} e + * @returns {boolean} + */ + onKeyDown: function(e) { + var isInput = e.target === this.$control_input[0]; + var self = this; + + if (self.isLocked) { + if (e.keyCode !== KEY_TAB) { + e.preventDefault(); + } + return; + } + + switch (e.keyCode) { + case KEY_A: + if (self.isCmdDown) { + self.selectAll(); + return; + } + break; + case KEY_ESC: + if (self.isOpen) { + e.preventDefault(); + e.stopPropagation(); + self.close(); + } + return; + case KEY_N: + if (!e.ctrlKey || e.altKey) break; + case KEY_DOWN: + if (!self.isOpen && self.hasOptions) { + self.open(); + } else if (self.$activeOption) { + self.ignoreHover = true; + var $next = self.getAdjacentOption(self.$activeOption, 1); + if ($next.length) self.setActiveOption($next, true, true); + } + e.preventDefault(); + return; + case KEY_P: + if (!e.ctrlKey || e.altKey) break; + case KEY_UP: + if (self.$activeOption) { + self.ignoreHover = true; + var $prev = self.getAdjacentOption(self.$activeOption, -1); + if ($prev.length) self.setActiveOption($prev, true, true); + } + e.preventDefault(); + return; + case KEY_RETURN: + if (self.isOpen && self.$activeOption) { + self.onOptionSelect({currentTarget: self.$activeOption}); + e.preventDefault(); + } + return; + case KEY_LEFT: + self.advanceSelection(-1, e); + return; + case KEY_RIGHT: + self.advanceSelection(1, e); + return; + case KEY_TAB: + if (self.settings.selectOnTab && self.isOpen && self.$activeOption) { + self.onOptionSelect({currentTarget: self.$activeOption}); + + // Default behaviour is to jump to the next field, we only want this + // if the current field doesn't accept any more entries + if (!self.isFull()) { + e.preventDefault(); + } + } + if (self.settings.create && self.createItem()) { + e.preventDefault(); + } + return; + case KEY_BACKSPACE: + case KEY_DELETE: + self.deleteSelection(e); + return; + } + + if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) { + e.preventDefault(); + return; + } + }, + + /** + * Triggered on keyup. + * + * @param {object} e + * @returns {boolean} + */ + onKeyUp: function(e) { + var self = this; + + if (self.isLocked) return e && e.preventDefault(); + var value = self.$control_input.val() || ''; + if (self.lastValue !== value) { + self.lastValue = value; + self.onSearchChange(value); + self.refreshOptions(); + self.trigger('type', value); + } + }, + + /** + * Invokes the user-provide option provider / loader. + * + * Note: this function is debounced in the Selectize + * constructor (by `settings.loadThrottle` milliseconds) + * + * @param {string} value + */ + onSearchChange: function(value) { + var self = this; + var fn = self.settings.load; + if (!fn) return; + if (self.loadedSearches.hasOwnProperty(value)) return; + self.loadedSearches[value] = true; + self.load(function(callback) { + fn.apply(self, [value, callback]); + }); + }, + + /** + * Triggered on focus. + * + * @param {object} e (optional) + * @returns {boolean} + */ + onFocus: function(e) { + var self = this; + var wasFocused = self.isFocused; + + if (self.isDisabled) { + self.blur(); + e && e.preventDefault(); + return false; + } + + if (self.ignoreFocus) return; + self.isFocused = true; + if (self.settings.preload === 'focus') self.onSearchChange(''); + + if (!wasFocused) self.trigger('focus'); + + if (!self.$activeItems.length) { + self.showInput(); + self.setActiveItem(null); + self.refreshOptions(!!self.settings.openOnFocus); + } + + self.refreshState(); + }, + + /** + * Triggered on blur. + * + * @param {object} e + * @param {Element} dest + */ + onBlur: function(e, dest) { + var self = this; + if (!self.isFocused) return; + self.isFocused = false; + + if (self.ignoreFocus) { + return; + } else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) { + // necessary to prevent IE closing the dropdown when the scrollbar is clicked + self.ignoreBlur = true; + self.onFocus(e); + return; + } + + var deactivate = function() { + self.close(); + self.setTextboxValue(''); + self.setActiveItem(null); + self.setActiveOption(null); + self.setCaret(self.items.length); + self.refreshState(); + + // IE11 bug: element still marked as active + dest && dest.focus && dest.focus(); + + self.ignoreFocus = false; + self.trigger('blur'); + }; + + self.ignoreFocus = true; + if (self.settings.create && self.settings.createOnBlur) { + self.createItem(null, false, deactivate); + } else { + deactivate(); + } + }, + + /** + * Triggered when the user rolls over + * an option in the autocomplete dropdown menu. + * + * @param {object} e + * @returns {boolean} + */ + onOptionHover: function(e) { + if (this.ignoreHover) return; + this.setActiveOption(e.currentTarget, false); + }, + + /** + * Triggered when the user clicks on an option + * in the autocomplete dropdown menu. + * + * @param {object} e + * @returns {boolean} + */ + onOptionSelect: function(e) { + var value, $target, $option, self = this; + + if (e.preventDefault) { + e.preventDefault(); + e.stopPropagation(); + } + + $target = $(e.currentTarget); + if ($target.hasClass('create')) { + self.createItem(null, function() { + if (self.settings.closeAfterSelect) { + self.close(); + } + }); + } else { + value = $target.attr('data-value'); + if (typeof value !== 'undefined') { + self.lastQuery = null; + self.setTextboxValue(''); + self.addItem(value); + if (self.settings.closeAfterSelect) { + self.close(); + } else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) { + self.setActiveOption(self.getOption(value)); + } + } + } + }, + + /** + * Triggered when the user clicks on an item + * that has been selected. + * + * @param {object} e + * @returns {boolean} + */ + onItemSelect: function(e) { + var self = this; + + if (self.isLocked) return; + if (self.settings.mode === 'multi') { + e.preventDefault(); + self.setActiveItem(e.currentTarget, e); + } + }, + + /** + * Invokes the provided method that provides + * results to a callback---which are then added + * as options to the control. + * + * @param {function} fn + */ + load: function(fn) { + var self = this; + var $wrapper = self.$wrapper.addClass(self.settings.loadingClass); + + self.loading++; + fn.apply(self, [function(results) { + self.loading = Math.max(self.loading - 1, 0); + if (results && results.length) { + self.addOption(results); + self.refreshOptions(self.isFocused && !self.isInputHidden); + } + if (!self.loading) { + $wrapper.removeClass(self.settings.loadingClass); + } + self.trigger('load', results); + }]); + }, + + /** + * Sets the input field of the control to the specified value. + * + * @param {string} value + */ + setTextboxValue: function(value) { + var $input = this.$control_input; + var changed = $input.val() !== value; + if (changed) { + $input.val(value).triggerHandler('update'); + this.lastValue = value; + } + }, + + /** + * Returns the value of the control. If multiple items + * can be selected (e.g. or + * element to reflect the current state. + */ + updateOriginalInput: function(opts) { + var i, n, options, label, self = this; + opts = opts || {}; + + if (self.tagType === TAG_SELECT) { + options = []; + for (i = 0, n = self.items.length; i < n; i++) { + label = self.options[self.items[i]][self.settings.labelField] || ''; + options.push(''); + } + if (!options.length && !this.$input.attr('multiple')) { + options.push(''); + } + self.$input.html(options.join('')); + } else { + self.$input.val(self.getValue()); + self.$input.attr('value',self.$input.val()); + } + + if (self.isSetup) { + if (!opts.silent) { + self.trigger('change', self.$input.val()); + } + } + }, + + /** + * Shows/hide the input placeholder depending + * on if there items in the list already. + */ + updatePlaceholder: function() { + if (!this.settings.placeholder) return; + var $input = this.$control_input; + + if (this.items.length) { + $input.removeAttr('placeholder'); + } else { + $input.attr('placeholder', this.settings.placeholder); + } + $input.triggerHandler('update', {force: true}); + }, + + /** + * Shows the autocomplete dropdown containing + * the available options. + */ + open: function() { + var self = this; + + if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return; + self.focus(); + self.isOpen = true; + self.refreshState(); + self.$dropdown.css({visibility: 'hidden', display: 'block'}); + self.positionDropdown(); + self.$dropdown.css({visibility: 'visible'}); + self.trigger('dropdown_open', self.$dropdown); + }, + + /** + * Closes the autocomplete dropdown menu. + */ + close: function() { + var self = this; + var trigger = self.isOpen; + + if (self.settings.mode === 'single' && self.items.length) { + self.hideInput(); + self.$control_input.blur(); // close keyboard on iOS + } + + self.isOpen = false; + self.$dropdown.hide(); + self.setActiveOption(null); + self.refreshState(); + + if (trigger) self.trigger('dropdown_close', self.$dropdown); + }, + + /** + * Calculates and applies the appropriate + * position of the dropdown. + */ + positionDropdown: function() { + var $control = this.$control; + var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position(); + offset.top += $control.outerHeight(true); + + this.$dropdown.css({ + width : $control.outerWidth(), + top : offset.top, + left : offset.left + }); + }, + + /** + * Resets / clears all selected items + * from the control. + * + * @param {boolean} silent + */ + clear: function(silent) { + var self = this; + + if (!self.items.length) return; + self.$control.children(':not(input)').remove(); + self.items = []; + self.lastQuery = null; + self.setCaret(0); + self.setActiveItem(null); + self.updatePlaceholder(); + self.updateOriginalInput({silent: silent}); + self.refreshState(); + self.showInput(); + self.trigger('clear'); + }, + + /** + * A helper method for inserting an element + * at the current caret position. + * + * @param {object} $el + */ + insertAtCaret: function($el) { + var caret = Math.min(this.caretPos, this.items.length); + if (caret === 0) { + this.$control.prepend($el); + } else { + $(this.$control[0].childNodes[caret]).before($el); + } + this.setCaret(caret + 1); + }, + + /** + * Removes the current selected item(s). + * + * @param {object} e (optional) + * @returns {boolean} + */ + deleteSelection: function(e) { + var i, n, direction, selection, values, caret, option_select, $option_select, $tail; + var self = this; + + direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1; + selection = getSelection(self.$control_input[0]); + + if (self.$activeOption && !self.settings.hideSelected) { + option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value'); + } + + // determine items that will be removed + values = []; + + if (self.$activeItems.length) { + $tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first')); + caret = self.$control.children(':not(input)').index($tail); + if (direction > 0) { caret++; } + + for (i = 0, n = self.$activeItems.length; i < n; i++) { + values.push($(self.$activeItems[i]).attr('data-value')); + } + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + } else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) { + if (direction < 0 && selection.start === 0 && selection.length === 0) { + values.push(self.items[self.caretPos - 1]); + } else if (direction > 0 && selection.start === self.$control_input.val().length) { + values.push(self.items[self.caretPos]); + } + } + + // allow the callback to abort + if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) { + return false; + } + + // perform removal + if (typeof caret !== 'undefined') { + self.setCaret(caret); + } + while (values.length) { + self.removeItem(values.pop()); + } + + self.showInput(); + self.positionDropdown(); + self.refreshOptions(true); + + // select previous option + if (option_select) { + $option_select = self.getOption(option_select); + if ($option_select.length) { + self.setActiveOption($option_select); + } + } + + return true; + }, + + /** + * Selects the previous / next item (depending + * on the `direction` argument). + * + * > 0 - right + * < 0 - left + * + * @param {int} direction + * @param {object} e (optional) + */ + advanceSelection: function(direction, e) { + var tail, selection, idx, valueLength, cursorAtEdge, $tail; + var self = this; + + if (direction === 0) return; + if (self.rtl) direction *= -1; + + tail = direction > 0 ? 'last' : 'first'; + selection = getSelection(self.$control_input[0]); + + if (self.isFocused && !self.isInputHidden) { + valueLength = self.$control_input.val().length; + cursorAtEdge = direction < 0 + ? selection.start === 0 && selection.length === 0 + : selection.start === valueLength; + + if (cursorAtEdge && !valueLength) { + self.advanceCaret(direction, e); + } + } else { + $tail = self.$control.children('.active:' + tail); + if ($tail.length) { + idx = self.$control.children(':not(input)').index($tail); + self.setActiveItem(null); + self.setCaret(direction > 0 ? idx + 1 : idx); + } + } + }, + + /** + * Moves the caret left / right. + * + * @param {int} direction + * @param {object} e (optional) + */ + advanceCaret: function(direction, e) { + var self = this, fn, $adj; + + if (direction === 0) return; + + fn = direction > 0 ? 'next' : 'prev'; + if (self.isShiftDown) { + $adj = self.$control_input[fn](); + if ($adj.length) { + self.hideInput(); + self.setActiveItem($adj); + e && e.preventDefault(); + } + } else { + self.setCaret(self.caretPos + direction); + } + }, + + /** + * Moves the caret to the specified index. + * + * @param {int} i + */ + setCaret: function(i) { + var self = this; + + if (self.settings.mode === 'single') { + i = self.items.length; + } else { + i = Math.max(0, Math.min(self.items.length, i)); + } + + if(!self.isPending) { + // the input must be moved by leaving it in place and moving the + // siblings, due to the fact that focus cannot be restored once lost + // on mobile webkit devices + var j, n, fn, $children, $child; + $children = self.$control.children(':not(input)'); + for (j = 0, n = $children.length; j < n; j++) { + $child = $($children[j]).detach(); + if (j < i) { + self.$control_input.before($child); + } else { + self.$control.append($child); + } + } + } + + self.caretPos = i; + }, + + /** + * Disables user input on the control. Used while + * items are being asynchronously created. + */ + lock: function() { + this.close(); + this.isLocked = true; + this.refreshState(); + }, + + /** + * Re-enables user input on the control. + */ + unlock: function() { + this.isLocked = false; + this.refreshState(); + }, + + /** + * Disables user input on the control completely. + * While disabled, it cannot receive focus. + */ + disable: function() { + var self = this; + self.$input.prop('disabled', true); + self.$control_input.prop('disabled', true).prop('tabindex', -1); + self.isDisabled = true; + self.lock(); + }, + + /** + * Enables the control so that it can respond + * to focus and user input. + */ + enable: function() { + var self = this; + self.$input.prop('disabled', false); + self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex); + self.isDisabled = false; + self.unlock(); + }, + + /** + * Completely destroys the control and + * unbinds all event listeners so that it can + * be garbage collected. + */ + destroy: function() { + var self = this; + var eventNS = self.eventNS; + var revertSettings = self.revertSettings; + + self.trigger('destroy'); + self.off(); + self.$wrapper.remove(); + self.$dropdown.remove(); + + self.$input + .html('') + .append(revertSettings.$children) + .removeAttr('tabindex') + .removeClass('selectized') + .attr({tabindex: revertSettings.tabindex}) + .show(); + + self.$control_input.removeData('grow'); + self.$input.removeData('selectize'); + + $(window).off(eventNS); + $(document).off(eventNS); + $(document.body).off(eventNS); + + delete self.$input[0].selectize; + }, + + /** + * A helper method for rendering "item" and + * "option" templates, given the data. + * + * @param {string} templateName + * @param {object} data + * @returns {string} + */ + render: function(templateName, data) { + var value, id, label; + var html = ''; + var cache = false; + var self = this; + var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i; + + if (templateName === 'option' || templateName === 'item') { + value = hash_key(data[self.settings.valueField]); + cache = !!value; + } + + // pull markup from cache if it exists + if (cache) { + if (!isset(self.renderCache[templateName])) { + self.renderCache[templateName] = {}; + } + if (self.renderCache[templateName].hasOwnProperty(value)) { + return self.renderCache[templateName][value]; + } + } + + // render markup + html = $(self.settings.render[templateName].apply(this, [data, escape_html])); + + // add mandatory attributes + if (templateName === 'option' || templateName === 'option_create') { + html.attr('data-selectable', ''); + } + else if (templateName === 'optgroup') { + id = data[self.settings.optgroupValueField] || ''; + html.attr('data-group', id); + } + if (templateName === 'option' || templateName === 'item') { + html.attr('data-value', value || ''); + } + + // update cache + if (cache) { + self.renderCache[templateName][value] = html[0]; + } + + return html[0]; + }, + + /** + * Clears the render cache for a template. If + * no template is given, clears all render + * caches. + * + * @param {string} templateName + */ + clearCache: function(templateName) { + var self = this; + if (typeof templateName === 'undefined') { + self.renderCache = {}; + } else { + delete self.renderCache[templateName]; + } + }, + + /** + * Determines whether or not to display the + * create item prompt, given a user input. + * + * @param {string} input + * @return {boolean} + */ + canCreate: function(input) { + var self = this; + if (!self.settings.create) return false; + var filter = self.settings.createFilter; + return input.length + && (typeof filter !== 'function' || filter.apply(self, [input])) + && (typeof filter !== 'string' || new RegExp(filter).test(input)) + && (!(filter instanceof RegExp) || filter.test(input)); + } + + }); + + + Selectize.count = 0; + Selectize.defaults = { + options: [], + optgroups: [], + + plugins: [], + delimiter: ',', + splitOn: null, // regexp or string for splitting up values from a paste command + persist: true, + diacritics: true, + create: false, + createOnBlur: false, + createFilter: null, + highlight: true, + openOnFocus: true, + maxOptions: 1000, + maxItems: null, + hideSelected: null, + addPrecedence: false, + selectOnTab: false, + preload: false, + allowEmptyOption: false, + closeAfterSelect: false, + + scrollDuration: 60, + loadThrottle: 300, + loadingClass: 'loading', + + dataAttr: 'data-data', + optgroupField: 'optgroup', + valueField: 'value', + labelField: 'text', + optgroupLabelField: 'label', + optgroupValueField: 'value', + lockOptgroupOrder: false, + + sortField: '$order', + searchField: ['text'], + searchConjunction: 'and', + + mode: null, + wrapperClass: 'selectize-control', + inputClass: 'selectize-input', + dropdownClass: 'selectize-dropdown', + dropdownContentClass: 'selectize-dropdown-content', + + dropdownParent: null, + + copyClassesToDropdown: true, + + /* + load : null, // function(query, callback) { ... } + score : null, // function(search) { ... } + onInitialize : null, // function() { ... } + onChange : null, // function(value) { ... } + onItemAdd : null, // function(value, $item) { ... } + onItemRemove : null, // function(value) { ... } + onClear : null, // function() { ... } + onOptionAdd : null, // function(value, data) { ... } + onOptionRemove : null, // function(value) { ... } + onOptionClear : null, // function() { ... } + onOptionGroupAdd : null, // function(id, data) { ... } + onOptionGroupRemove : null, // function(id) { ... } + onOptionGroupClear : null, // function() { ... } + onDropdownOpen : null, // function($dropdown) { ... } + onDropdownClose : null, // function($dropdown) { ... } + onType : null, // function(str) { ... } + onDelete : null, // function(values) { ... } + */ + + render: { + /* + item: null, + optgroup: null, + optgroup_header: null, + option: null, + option_create: null + */ + } + }; + + + $.fn.selectize = function(settings_user) { + var defaults = $.fn.selectize.defaults; + var settings = $.extend({}, defaults, settings_user); + var attr_data = settings.dataAttr; + var field_label = settings.labelField; + var field_value = settings.valueField; + var field_optgroup = settings.optgroupField; + var field_optgroup_label = settings.optgroupLabelField; + var field_optgroup_value = settings.optgroupValueField; + + /** + * Initializes selectize from a element. + * + * @param {object} $input + * @param {object} settings_element + */ + var init_textbox = function($input, settings_element) { + var i, n, values, option; + + var data_raw = $input.attr(attr_data); + + if (!data_raw) { + var value = $.trim($input.val() || ''); + if (!settings.allowEmptyOption && !value.length) return; + values = value.split(settings.delimiter); + for (i = 0, n = values.length; i < n; i++) { + option = {}; + option[field_label] = values[i]; + option[field_value] = values[i]; + settings_element.options.push(option); + } + settings_element.items = values; + } else { + settings_element.options = JSON.parse(data_raw); + for (i = 0, n = settings_element.options.length; i < n; i++) { + settings_element.items.push(settings_element.options[i][field_value]); + } + } + }; + + /** + * Initializes selectize from a ').appendTo(c).attr("tabindex", u.is(":disabled") ? "-1" : m.tabIndex), h = a(n.dropdownParent || b), e = a("
    ").addClass(n.dropdownClass).addClass(i).hide().appendTo(h), g = a("
    ").addClass(n.dropdownContentClass).appendTo(e), (l = u.attr("id")) && (d.attr("id", l + "-selectized"), a("label[for='" + l + "']").attr("for", l + "-selectized")), m.settings.copyClassesToDropdown && e.addClass(j), b.css({width: u[0].style.width}), m.plugins.names.length && (k = "plugin-" + m.plugins.names.join(" plugin-"), b.addClass(k), e.addClass(k)), (null === n.maxItems || n.maxItems > 1) && m.tagType === v && u.attr("multiple", "multiple"), m.settings.placeholder && d.attr("placeholder", n.placeholder), !m.settings.splitOn && m.settings.delimiter) { + var w = m.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); + m.settings.splitOn = new RegExp("\\s*" + w + "+\\s*") + } + u.attr("autocorrect") && d.attr("autocorrect", u.attr("autocorrect")), u.attr("autocapitalize") && d.attr("autocapitalize", u.attr("autocapitalize")), m.$wrapper = b, m.$control = c, m.$control_input = d, m.$dropdown = e, m.$dropdown_content = g, e.on("mouseenter", "[data-selectable]", function () { + return m.onOptionHover.apply(m, arguments) + }), e.on("mousedown click", "[data-selectable]", function () { + return m.onOptionSelect.apply(m, arguments) + }), F(c, "mousedown", "*:not(input)", function () { + return m.onItemSelect.apply(m, arguments) + }), J(d), c.on({ + mousedown: function () { + return m.onMouseDown.apply(m, arguments) + }, click: function () { + return m.onClick.apply(m, arguments) + } + }), d.on({ + mousedown: function (a) { + a.stopPropagation() + }, keydown: function () { + return m.onKeyDown.apply(m, arguments) + }, keyup: function () { + return m.onKeyUp.apply(m, arguments) + }, keypress: function () { + return m.onKeyPress.apply(m, arguments) + }, resize: function () { + m.positionDropdown.apply(m, []) + }, blur: function () { + return m.onBlur.apply(m, arguments) + }, focus: function () { + return m.ignoreBlur = !1, m.onFocus.apply(m, arguments) + }, paste: function () { + return m.onPaste.apply(m, arguments) + } + }), q.on("keydown" + o, function (a) { + m.isCmdDown = a[f ? "metaKey" : "ctrlKey"], m.isCtrlDown = a[f ? "altKey" : "ctrlKey"], m.isShiftDown = a.shiftKey + }), q.on("keyup" + o, function (a) { + a.keyCode === t && (m.isCtrlDown = !1), a.keyCode === r && (m.isShiftDown = !1), a.keyCode === s && (m.isCmdDown = !1) + }), q.on("mousedown" + o, function (a) { + if (m.isFocused) { + if (a.target === m.$dropdown[0] || a.target.parentNode === m.$dropdown[0]) return !1; + m.$control.has(a.target).length || a.target === m.$control[0] || m.blur(a.target) + } + }), p.on(["scroll" + o, "resize" + o].join(" "), function () { + m.isOpen && m.positionDropdown.apply(m, arguments) + }), p.on("mousemove" + o, function () { + m.ignoreHover = !1 + }), this.revertSettings = { + $children: u.children().detach(), + tabindex: u.attr("tabindex") + }, u.attr("tabindex", -1).hide().after(m.$wrapper), a.isArray(n.items) && (m.setValue(n.items), delete n.items), x && u.on("invalid" + o, function (a) { + a.preventDefault(), m.isInvalid = !0, m.refreshState() + }), m.updateOriginalInput(), m.refreshItems(), m.refreshState(), m.updatePlaceholder(), m.isSetup = !0, u.is(":disabled") && m.disable(), m.on("change", this.onChange), u.data("selectize", m), u.addClass("selectized"), m.trigger("initialize"), n.preload === !0 && m.onSearchChange("") + }, setupTemplates: function () { + var b = this, c = b.settings.labelField, d = b.settings.optgroupLabelField, e = { + optgroup: function (a) { + return '
    ' + a.html + "
    " + }, optgroup_header: function (a, b) { + return '
    ' + b(a[d]) + "
    " + }, option: function (a, b) { + return '
    ' + b(a[c]) + "
    " + }, item: function (a, b) { + return '
    ' + b(a[c]) + "
    " + }, option_create: function (a, b) { + return '
    Add ' + b(a.input) + "
    " + } + }; + b.settings.render = a.extend({}, e, b.settings.render) + }, setupCallbacks: function () { + var a, b, c = { + initialize: "onInitialize", + change: "onChange", + item_add: "onItemAdd", + item_remove: "onItemRemove", + clear: "onClear", + option_add: "onOptionAdd", + option_remove: "onOptionRemove", + option_clear: "onOptionClear", + optgroup_add: "onOptionGroupAdd", + optgroup_remove: "onOptionGroupRemove", + optgroup_clear: "onOptionGroupClear", + dropdown_open: "onDropdownOpen", + dropdown_close: "onDropdownClose", + type: "onType", + load: "onLoad", + focus: "onFocus", + blur: "onBlur" + }; + for (a in c) c.hasOwnProperty(a) && (b = this.settings[c[a]], b && this.on(a, b)) + }, onClick: function (a) { + var b = this; + b.isFocused || (b.focus(), a.preventDefault()) + }, onMouseDown: function (b) { + var c = this, d = b.isDefaultPrevented(); + a(b.target); + if (c.isFocused) { + if (b.target !== c.$control_input[0]) return "single" === c.settings.mode ? c.isOpen ? c.close() : c.open() : d || c.setActiveItem(null), !1 + } else d || window.setTimeout(function () { + c.focus() + }, 0) + }, onChange: function () { + this.$input.trigger("change") + }, onPaste: function (b) { + var c = this; + return c.isFull() || c.isInputHidden || c.isLocked ? void b.preventDefault() : void(c.settings.splitOn && setTimeout(function () { + var b = c.$control_input.val(); + if (b.match(c.settings.splitOn)) for (var d = a.trim(b).split(c.settings.splitOn), e = 0, f = d.length; e < f; e++) c.createItem(d[e]) + }, 0)) + }, onKeyPress: function (a) { + if (this.isLocked) return a && a.preventDefault(); + var b = String.fromCharCode(a.keyCode || a.which); + return this.settings.create && "multi" === this.settings.mode && b === this.settings.delimiter ? (this.createItem(), a.preventDefault(), !1) : void 0 + }, onKeyDown: function (a) { + var b = (a.target === this.$control_input[0], this); + if (b.isLocked) return void(a.keyCode !== u && a.preventDefault()); + switch (a.keyCode) { + case g: + if (b.isCmdDown) return void b.selectAll(); + break; + case i: + return void(b.isOpen && (a.preventDefault(), a.stopPropagation(), b.close())); + case o: + if (!a.ctrlKey || a.altKey) break; + case n: + if (!b.isOpen && b.hasOptions) b.open(); else if (b.$activeOption) { + b.ignoreHover = !0; + var c = b.getAdjacentOption(b.$activeOption, 1); + c.length && b.setActiveOption(c, !0, !0) + } + return void a.preventDefault(); + case l: + if (!a.ctrlKey || a.altKey) break; + case k: + if (b.$activeOption) { + b.ignoreHover = !0; + var d = b.getAdjacentOption(b.$activeOption, -1); + d.length && b.setActiveOption(d, !0, !0) + } + return void a.preventDefault(); + case h: + return void(b.isOpen && b.$activeOption && (b.onOptionSelect({currentTarget: b.$activeOption}), a.preventDefault())); + case j: + return void b.advanceSelection(-1, a); + case m: + return void b.advanceSelection(1, a); + case u: + return b.settings.selectOnTab && b.isOpen && b.$activeOption && (b.onOptionSelect({currentTarget: b.$activeOption}), b.isFull() || a.preventDefault()), void(b.settings.create && b.createItem() && a.preventDefault()); + case p: + case q: + return void b.deleteSelection(a) + } + return !b.isFull() && !b.isInputHidden || (f ? a.metaKey : a.ctrlKey) ? void 0 : void a.preventDefault() + }, onKeyUp: function (a) { + var b = this; + if (b.isLocked) return a && a.preventDefault(); + var c = b.$control_input.val() || ""; + b.lastValue !== c && (b.lastValue = c, b.onSearchChange(c), b.refreshOptions(), b.trigger("type", c)) + }, onSearchChange: function (a) { + var b = this, c = b.settings.load; + c && (b.loadedSearches.hasOwnProperty(a) || (b.loadedSearches[a] = !0, b.load(function (d) { + c.apply(b, [a, d]) + }))) + }, onFocus: function (a) { + var b = this, c = b.isFocused; + return b.isDisabled ? (b.blur(), a && a.preventDefault(), !1) : void(b.ignoreFocus || (b.isFocused = !0, "focus" === b.settings.preload && b.onSearchChange(""), c || b.trigger("focus"), b.$activeItems.length || (b.showInput(), b.setActiveItem(null), b.refreshOptions(!!b.settings.openOnFocus)), b.refreshState())) + }, onBlur: function (a, b) { + var c = this; + if (c.isFocused && (c.isFocused = !1, !c.ignoreFocus)) { + if (!c.ignoreBlur && document.activeElement === c.$dropdown_content[0]) return c.ignoreBlur = !0, void c.onFocus(a); + var d = function () { + c.close(), c.setTextboxValue(""), c.setActiveItem(null), c.setActiveOption(null), c.setCaret(c.items.length), c.refreshState(), b && b.focus && b.focus(), c.ignoreFocus = !1, c.trigger("blur") + }; + c.ignoreFocus = !0, c.settings.create && c.settings.createOnBlur ? c.createItem(null, !1, d) : d() + } + }, onOptionHover: function (a) { + this.ignoreHover || this.setActiveOption(a.currentTarget, !1) + }, onOptionSelect: function (b) { + var c, d, e = this; + b.preventDefault && (b.preventDefault(), b.stopPropagation()), d = a(b.currentTarget), d.hasClass("create") ? e.createItem(null, function () { + e.settings.closeAfterSelect && e.close() + }) : (c = d.attr("data-value"), "undefined" != typeof c && (e.lastQuery = null, e.setTextboxValue(""), e.addItem(c), e.settings.closeAfterSelect ? e.close() : !e.settings.hideSelected && b.type && /mouse/.test(b.type) && e.setActiveOption(e.getOption(c)))) + }, onItemSelect: function (a) { + var b = this; + b.isLocked || "multi" === b.settings.mode && (a.preventDefault(), b.setActiveItem(a.currentTarget, a)) + }, load: function (a) { + var b = this, c = b.$wrapper.addClass(b.settings.loadingClass); + b.loading++, a.apply(b, [function (a) { + b.loading = Math.max(b.loading - 1, 0), a && a.length && (b.addOption(a), b.refreshOptions(b.isFocused && !b.isInputHidden)), b.loading || c.removeClass(b.settings.loadingClass), b.trigger("load", a) + }]) + }, setTextboxValue: function (a) { + var b = this.$control_input, c = b.val() !== a; + c && (b.val(a).triggerHandler("update"), this.lastValue = a) + }, getValue: function () { + return this.tagType === v && this.$input.attr("multiple") ? this.items : this.items.join(this.settings.delimiter) + }, setValue: function (a, b) { + var c = b ? [] : ["change"]; + E(this, c, function () { + this.clear(b), this.addItems(a, b) + }) + }, setActiveItem: function (b, c) { + var d, e, f, g, h, i, j, k, l = this; + if ("single" !== l.settings.mode) { + if (b = a(b), !b.length) return a(l.$activeItems).removeClass("active"), l.$activeItems = [], void(l.isFocused && l.showInput()); + if (d = c && c.type.toLowerCase(), "mousedown" === d && l.isShiftDown && l.$activeItems.length) { + for (k = l.$control.children(".active:last"), g = Array.prototype.indexOf.apply(l.$control[0].childNodes, [k[0]]), h = Array.prototype.indexOf.apply(l.$control[0].childNodes, [b[0]]), g > h && (j = g, g = h, h = j), e = g; e <= h; e++) i = l.$control[0].childNodes[e], l.$activeItems.indexOf(i) === -1 && (a(i).addClass("active"), l.$activeItems.push(i)); + c.preventDefault() + } else "mousedown" === d && l.isCtrlDown || "keydown" === d && this.isShiftDown ? b.hasClass("active") ? (f = l.$activeItems.indexOf(b[0]), l.$activeItems.splice(f, 1), b.removeClass("active")) : l.$activeItems.push(b.addClass("active")[0]) : (a(l.$activeItems).removeClass("active"), l.$activeItems = [b.addClass("active")[0]]); + l.hideInput(), this.isFocused || l.focus() + } + }, setActiveOption: function (b, c, d) { + var e, f, g, h, i, j = this; + j.$activeOption && j.$activeOption.removeClass("active"), j.$activeOption = null, b = a(b), b.length && (j.$activeOption = b.addClass("active"), !c && y(c) || (e = j.$dropdown_content.height(), f = j.$activeOption.outerHeight(!0), c = j.$dropdown_content.scrollTop() || 0, g = j.$activeOption.offset().top - j.$dropdown_content.offset().top + c, h = g, i = g - e + f, g + f > e + c ? j.$dropdown_content.stop().animate({scrollTop: i}, d ? j.settings.scrollDuration : 0) : g < c && j.$dropdown_content.stop().animate({scrollTop: h}, d ? j.settings.scrollDuration : 0))) + }, selectAll: function () { + var a = this; + "single" !== a.settings.mode && (a.$activeItems = Array.prototype.slice.apply(a.$control.children(":not(input)").addClass("active")), a.$activeItems.length && (a.hideInput(), a.close()), a.focus()) + }, hideInput: function () { + var a = this; + a.setTextboxValue(""), a.$control_input.css({ + opacity: 0, + position: "absolute", + left: a.rtl ? 1e4 : -1e4 + }), a.isInputHidden = !0 + }, showInput: function () { + this.$control_input.css({opacity: 1, position: "relative", left: 0}), this.isInputHidden = !1 + }, focus: function () { + var a = this; + a.isDisabled || (a.ignoreFocus = !0, a.$control_input[0].focus(), window.setTimeout(function () { + a.ignoreFocus = !1, a.onFocus() + }, 0)) + }, blur: function (a) { + this.$control_input[0].blur(), this.onBlur(null, a) + }, getScoreFunction: function (a) { + return this.sifter.getScoreFunction(a, this.getSearchOptions()) + }, getSearchOptions: function () { + var a = this.settings, b = a.sortField; + return "string" == typeof b && (b = [{field: b}]), { + fields: a.searchField, + conjunction: a.searchConjunction, + sort: b + } + }, search: function (b) { + var c, d, e, f = this, g = f.settings, h = this.getSearchOptions(); + if (g.score && (e = f.settings.score.apply(this, [b]), "function" != typeof e)) throw new Error('Selectize "score" setting must be a function that returns a function'); + if (b !== f.lastQuery ? (f.lastQuery = b, d = f.sifter.search(b, a.extend(h, {score: e})), f.currentResults = d) : d = a.extend(!0, {}, f.currentResults), g.hideSelected) for (c = d.items.length - 1; c >= 0; c--) f.items.indexOf(z(d.items[c].id)) !== -1 && d.items.splice(c, 1); + return d + }, refreshOptions: function (b) { + var c, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s; + "undefined" == typeof b && (b = !0); + var t = this, u = a.trim(t.$control_input.val()), v = t.search(u), w = t.$dropdown_content, + x = t.$activeOption && z(t.$activeOption.attr("data-value")); + for (g = v.items.length, "number" == typeof t.settings.maxOptions && (g = Math.min(g, t.settings.maxOptions)), h = {}, i = [], c = 0; c < g; c++) for (j = t.options[v.items[c].id], k = t.render("option", j), l = j[t.settings.optgroupField] || "", m = a.isArray(l) ? l : [l], e = 0, f = m && m.length; e < f; e++) l = m[e], t.optgroups.hasOwnProperty(l) || (l = ""), h.hasOwnProperty(l) || (h[l] = document.createDocumentFragment(), i.push(l)), h[l].appendChild(k); + for (this.settings.lockOptgroupOrder && i.sort(function (a, b) { + var c = t.optgroups[a].$order || 0, d = t.optgroups[b].$order || 0; + return c - d + }), n = document.createDocumentFragment(), c = 0, g = i.length; c < g; c++) l = i[c], t.optgroups.hasOwnProperty(l) && h[l].childNodes.length ? (o = document.createDocumentFragment(), o.appendChild(t.render("optgroup_header", t.optgroups[l])), o.appendChild(h[l]), n.appendChild(t.render("optgroup", a.extend({}, t.optgroups[l], { + html: K(o), + dom: o + })))) : n.appendChild(h[l]); + if (w.html(n), t.settings.highlight && v.query.length && v.tokens.length) for (w.removeHighlight(), c = 0, g = v.tokens.length; c < g; c++) d(w, v.tokens[c].regex); + if (!t.settings.hideSelected) for (c = 0, g = t.items.length; c < g; c++) t.getOption(t.items[c]).addClass("selected"); + p = t.canCreate(u), p && (w.prepend(t.render("option_create", {input: u})), s = a(w[0].childNodes[0])), t.hasOptions = v.items.length > 0 || p, t.hasOptions ? (v.items.length > 0 ? (r = x && t.getOption(x), r && r.length ? q = r : "single" === t.settings.mode && t.items.length && (q = t.getOption(t.items[0])), q && q.length || (q = s && !t.settings.addPrecedence ? t.getAdjacentOption(s, 1) : w.find("[data-selectable]:first"))) : q = s, t.setActiveOption(q), b && !t.isOpen && t.open()) : (t.setActiveOption(null), b && t.isOpen && t.close()) + }, addOption: function (b) { + var c, d, e, f = this; + if (a.isArray(b)) for (c = 0, d = b.length; c < d; c++) f.addOption(b[c]); else (e = f.registerOption(b)) && (f.userOptions[e] = !0, f.lastQuery = null, f.trigger("option_add", e, b)) + }, registerOption: function (a) { + var b = z(a[this.settings.valueField]); + return "undefined" != typeof b && null !== b && !this.options.hasOwnProperty(b) && (a.$order = a.$order || ++this.order, this.options[b] = a, b) + }, registerOptionGroup: function (a) { + var b = z(a[this.settings.optgroupValueField]); + return !!b && (a.$order = a.$order || ++this.order, this.optgroups[b] = a, b) + }, addOptionGroup: function (a, b) { + b[this.settings.optgroupValueField] = a, (a = this.registerOptionGroup(b)) && this.trigger("optgroup_add", a, b) + }, removeOptionGroup: function (a) { + this.optgroups.hasOwnProperty(a) && (delete this.optgroups[a], this.renderCache = {}, this.trigger("optgroup_remove", a)) + }, clearOptionGroups: function () { + this.optgroups = {}, this.renderCache = {}, this.trigger("optgroup_clear") + }, updateOption: function (b, c) { + var d, e, f, g, h, i, j, k = this; + if (b = z(b), f = z(c[k.settings.valueField]), null !== b && k.options.hasOwnProperty(b)) { + if ("string" != typeof f) throw new Error("Value must be set in option data"); + j = k.options[b].$order, f !== b && (delete k.options[b], g = k.items.indexOf(b), g !== -1 && k.items.splice(g, 1, f)), c.$order = c.$order || j, k.options[f] = c, h = k.renderCache.item, i = k.renderCache.option, h && (delete h[b], delete h[f]), i && (delete i[b], delete i[f]), k.items.indexOf(f) !== -1 && (d = k.getItem(b), e = a(k.render("item", c)), d.hasClass("active") && e.addClass("active"), d.replaceWith(e)), k.lastQuery = null, k.isOpen && k.refreshOptions(!1) + } + }, removeOption: function (a, b) { + var c = this; + a = z(a); + var d = c.renderCache.item, e = c.renderCache.option; + d && delete d[a], e && delete e[a], delete c.userOptions[a], delete c.options[a], c.lastQuery = null, c.trigger("option_remove", a), c.removeItem(a, b) + }, clearOptions: function () { + var a = this; + a.loadedSearches = {}, a.userOptions = {}, a.renderCache = {}, a.options = a.sifter.items = {}, a.lastQuery = null, a.trigger("option_clear"), a.clear() + }, getOption: function (a) { + return this.getElementWithValue(a, this.$dropdown_content.find("[data-selectable]")) + }, getAdjacentOption: function (b, c) { + var d = this.$dropdown.find("[data-selectable]"), e = d.index(b) + c; + return e >= 0 && e < d.length ? d.eq(e) : a() + }, getElementWithValue: function (b, c) { + if (b = z(b), "undefined" != typeof b && null !== b) for (var d = 0, e = c.length; d < e; d++) if (c[d].getAttribute("data-value") === b) return a(c[d]); + return a() + }, getItem: function (a) { + return this.getElementWithValue(a, this.$control.children()) + }, addItems: function (b, c) { + for (var d = a.isArray(b) ? b : [b], e = 0, f = d.length; e < f; e++) this.isPending = e < f - 1, this.addItem(d[e], c) + }, addItem: function (b, c) { + var d = c ? [] : ["change"]; + E(this, d, function () { + var d, e, f, g, h, i = this, j = i.settings.mode; + return b = z(b), i.items.indexOf(b) !== -1 ? void("single" === j && i.close()) : void(i.options.hasOwnProperty(b) && ("single" === j && i.clear(c), "multi" === j && i.isFull() || (d = a(i.render("item", i.options[b])), h = i.isFull(), i.items.splice(i.caretPos, 0, b), i.insertAtCaret(d), (!i.isPending || !h && i.isFull()) && i.refreshState(), i.isSetup && (f = i.$dropdown_content.find("[data-selectable]"), i.isPending || (e = i.getOption(b), g = i.getAdjacentOption(e, 1).attr("data-value"), i.refreshOptions(i.isFocused && "single" !== j), g && i.setActiveOption(i.getOption(g))), !f.length || i.isFull() ? i.close() : i.positionDropdown(), i.updatePlaceholder(), i.trigger("item_add", b, d), i.updateOriginalInput({silent: c}))))) + }) + }, removeItem: function (b, c) { + var d, e, f, g = this; + d = b instanceof a ? b : g.getItem(b), b = z(d.attr("data-value")), e = g.items.indexOf(b), e !== -1 && (d.remove(), d.hasClass("active") && (f = g.$activeItems.indexOf(d[0]), g.$activeItems.splice(f, 1)), g.items.splice(e, 1), g.lastQuery = null, !g.settings.persist && g.userOptions.hasOwnProperty(b) && g.removeOption(b, c), e < g.caretPos && g.setCaret(g.caretPos - 1), g.refreshState(), g.updatePlaceholder(), g.updateOriginalInput({silent: c}), g.positionDropdown(), g.trigger("item_remove", b, d)) + }, createItem: function (b, c) { + var d = this, e = d.caretPos; + b = b || a.trim(d.$control_input.val() || ""); + var f = arguments[arguments.length - 1]; + if ("function" != typeof f && (f = function () { + }), "boolean" != typeof c && (c = !0), !d.canCreate(b)) return f(), !1; + d.lock(); + var g = "function" == typeof d.settings.create ? this.settings.create : function (a) { + var b = {}; + return b[d.settings.labelField] = a, b[d.settings.valueField] = a, b + }, h = C(function (a) { + if (d.unlock(), !a || "object" != typeof a) return f(); + var b = z(a[d.settings.valueField]); + return "string" != typeof b ? f() : (d.setTextboxValue(""), d.addOption(a), d.setCaret(e), d.addItem(b), d.refreshOptions(c && "single" !== d.settings.mode), void f(a)) + }), i = g.apply(this, [b, h]); + return "undefined" != typeof i && h(i), !0 + }, refreshItems: function () { + this.lastQuery = null, this.isSetup && this.addItem(this.items), this.refreshState(), this.updateOriginalInput() + }, refreshState: function () { + this.refreshValidityState(), this.refreshClasses() + }, refreshValidityState: function () { + if (!this.isRequired) return !1; + var a = !this.items.length; + this.isInvalid = a, this.$control_input.prop("required", a), this.$input.prop("required", !a) + }, refreshClasses: function () { + var b = this, c = b.isFull(), d = b.isLocked; + b.$wrapper.toggleClass("rtl", b.rtl), b.$control.toggleClass("focus", b.isFocused).toggleClass("disabled", b.isDisabled).toggleClass("required", b.isRequired).toggleClass("invalid", b.isInvalid).toggleClass("locked", d).toggleClass("full", c).toggleClass("not-full", !c).toggleClass("input-active", b.isFocused && !b.isInputHidden).toggleClass("dropdown-active", b.isOpen).toggleClass("has-options", !a.isEmptyObject(b.options)).toggleClass("has-items", b.items.length > 0), b.$control_input.data("grow", !c && !d) + }, isFull: function () { + return null !== this.settings.maxItems && this.items.length >= this.settings.maxItems + }, updateOriginalInput: function (a) { + var b, c, d, e, f = this; + if (a = a || {}, f.tagType === v) { + for (d = [], b = 0, c = f.items.length; b < c; b++) e = f.options[f.items[b]][f.settings.labelField] || "", d.push('"); + d.length || this.$input.attr("multiple") || d.push(''), + f.$input.html(d.join("")) + } else f.$input.val(f.getValue()), f.$input.attr("value", f.$input.val()); + f.isSetup && (a.silent || f.trigger("change", f.$input.val())) + }, updatePlaceholder: function () { + if (this.settings.placeholder) { + var a = this.$control_input; + this.items.length ? a.removeAttr("placeholder") : a.attr("placeholder", this.settings.placeholder), a.triggerHandler("update", {force: !0}) + } + }, open: function () { + var a = this; + a.isLocked || a.isOpen || "multi" === a.settings.mode && a.isFull() || (a.focus(), a.isOpen = !0, a.refreshState(), a.$dropdown.css({ + visibility: "hidden", + display: "block" + }), a.positionDropdown(), a.$dropdown.css({visibility: "visible"}), a.trigger("dropdown_open", a.$dropdown)) + }, close: function () { + var a = this, b = a.isOpen; + "single" === a.settings.mode && a.items.length && (a.hideInput(), a.$control_input.blur()), a.isOpen = !1, a.$dropdown.hide(), a.setActiveOption(null), a.refreshState(), b && a.trigger("dropdown_close", a.$dropdown) + }, positionDropdown: function () { + var a = this.$control, b = "body" === this.settings.dropdownParent ? a.offset() : a.position(); + b.top += a.outerHeight(!0), this.$dropdown.css({width: a.outerWidth(), top: b.top, left: b.left}) + }, clear: function (a) { + var b = this; + b.items.length && (b.$control.children(":not(input)").remove(), b.items = [], b.lastQuery = null, b.setCaret(0), b.setActiveItem(null), b.updatePlaceholder(), b.updateOriginalInput({silent: a}), b.refreshState(), b.showInput(), b.trigger("clear")) + }, insertAtCaret: function (b) { + var c = Math.min(this.caretPos, this.items.length); + 0 === c ? this.$control.prepend(b) : a(this.$control[0].childNodes[c]).before(b), this.setCaret(c + 1) + }, deleteSelection: function (b) { + var c, d, e, f, g, h, i, j, k, l = this; + if (e = b && b.keyCode === p ? -1 : 1, f = G(l.$control_input[0]), l.$activeOption && !l.settings.hideSelected && (i = l.getAdjacentOption(l.$activeOption, -1).attr("data-value")), g = [], l.$activeItems.length) { + for (k = l.$control.children(".active:" + (e > 0 ? "last" : "first")), h = l.$control.children(":not(input)").index(k), e > 0 && h++, c = 0, d = l.$activeItems.length; c < d; c++) g.push(a(l.$activeItems[c]).attr("data-value")); + b && (b.preventDefault(), b.stopPropagation()) + } else (l.isFocused || "single" === l.settings.mode) && l.items.length && (e < 0 && 0 === f.start && 0 === f.length ? g.push(l.items[l.caretPos - 1]) : e > 0 && f.start === l.$control_input.val().length && g.push(l.items[l.caretPos])); + if (!g.length || "function" == typeof l.settings.onDelete && l.settings.onDelete.apply(l, [g]) === !1) return !1; + for ("undefined" != typeof h && l.setCaret(h); g.length;) l.removeItem(g.pop()); + return l.showInput(), l.positionDropdown(), l.refreshOptions(!0), i && (j = l.getOption(i), j.length && l.setActiveOption(j)), !0 + }, advanceSelection: function (a, b) { + var c, d, e, f, g, h, i = this; + 0 !== a && (i.rtl && (a *= -1), c = a > 0 ? "last" : "first", d = G(i.$control_input[0]), i.isFocused && !i.isInputHidden ? (f = i.$control_input.val().length, g = a < 0 ? 0 === d.start && 0 === d.length : d.start === f, g && !f && i.advanceCaret(a, b)) : (h = i.$control.children(".active:" + c), h.length && (e = i.$control.children(":not(input)").index(h), i.setActiveItem(null), i.setCaret(a > 0 ? e + 1 : e)))) + }, advanceCaret: function (a, b) { + var c, d, e = this; + 0 !== a && (c = a > 0 ? "next" : "prev", e.isShiftDown ? (d = e.$control_input[c](), d.length && (e.hideInput(), e.setActiveItem(d), b && b.preventDefault())) : e.setCaret(e.caretPos + a)) + }, setCaret: function (b) { + var c = this; + if (b = "single" === c.settings.mode ? c.items.length : Math.max(0, Math.min(c.items.length, b)), !c.isPending) { + var d, e, f, g; + for (f = c.$control.children(":not(input)"), d = 0, e = f.length; d < e; d++) g = a(f[d]).detach(), d < b ? c.$control_input.before(g) : c.$control.append(g) + } + c.caretPos = b + }, lock: function () { + this.close(), this.isLocked = !0, this.refreshState() + }, unlock: function () { + this.isLocked = !1, this.refreshState() + }, disable: function () { + var a = this; + a.$input.prop("disabled", !0), a.$control_input.prop("disabled", !0).prop("tabindex", -1), a.isDisabled = !0, a.lock() + }, enable: function () { + var a = this; + a.$input.prop("disabled", !1), a.$control_input.prop("disabled", !1).prop("tabindex", a.tabIndex), a.isDisabled = !1, a.unlock() + }, destroy: function () { + var b = this, c = b.eventNS, d = b.revertSettings; + b.trigger("destroy"), b.off(), b.$wrapper.remove(), b.$dropdown.remove(), b.$input.html("").append(d.$children).removeAttr("tabindex").removeClass("selectized").attr({tabindex: d.tabindex}).show(), b.$control_input.removeData("grow"), b.$input.removeData("selectize"), a(window).off(c), a(document).off(c), a(document.body).off(c), delete b.$input[0].selectize + }, render: function (b, c) { + var d, e, f = "", g = !1, h = this; + return "option" !== b && "item" !== b || (d = z(c[h.settings.valueField]), g = !!d), g && (y(h.renderCache[b]) || (h.renderCache[b] = {}), h.renderCache[b].hasOwnProperty(d)) ? h.renderCache[b][d] : (f = a(h.settings.render[b].apply(this, [c, A])), "option" === b || "option_create" === b ? f.attr("data-selectable", "") : "optgroup" === b && (e = c[h.settings.optgroupValueField] || "", f.attr("data-group", e)), "option" !== b && "item" !== b || f.attr("data-value", d || ""), g && (h.renderCache[b][d] = f[0]), f[0]) + }, clearCache: function (a) { + var b = this; + "undefined" == typeof a ? b.renderCache = {} : delete b.renderCache[a] + }, canCreate: function (a) { + var b = this; + if (!b.settings.create) return !1; + var c = b.settings.createFilter; + return a.length && ("function" != typeof c || c.apply(b, [a])) && ("string" != typeof c || new RegExp(c).test(a)) && (!(c instanceof RegExp) || c.test(a)) + } + }), M.count = 0, M.defaults = { + options: [], + optgroups: [], + plugins: [], + delimiter: ",", + splitOn: null, + persist: !0, + diacritics: !0, + create: !1, + createOnBlur: !1, + createFilter: null, + highlight: !0, + openOnFocus: !0, + maxOptions: 1e3, + maxItems: null, + hideSelected: null, + addPrecedence: !1, + selectOnTab: !1, + preload: !1, + allowEmptyOption: !1, + closeAfterSelect: !1, + scrollDuration: 60, + loadThrottle: 300, + loadingClass: "loading", + dataAttr: "data-data", + optgroupField: "optgroup", + valueField: "value", + labelField: "text", + optgroupLabelField: "label", + optgroupValueField: "value", + lockOptgroupOrder: !1, + sortField: "$order", + searchField: ["text"], + searchConjunction: "and", + mode: null, + wrapperClass: "selectize-control", + inputClass: "selectize-input", + dropdownClass: "selectize-dropdown", + dropdownContentClass: "selectize-dropdown-content", + dropdownParent: null, + copyClassesToDropdown: !0, + render: {} + }, a.fn.selectize = function (b) { + var c = a.fn.selectize.defaults, d = a.extend({}, c, b), e = d.dataAttr, f = d.labelField, g = d.valueField, + h = d.optgroupField, i = d.optgroupLabelField, j = d.optgroupValueField, k = function (b, c) { + var h, i, j, k, l = b.attr(e); + if (l) for (c.options = JSON.parse(l), h = 0, i = c.options.length; h < i; h++) c.items.push(c.options[h][g]); else { + var m = a.trim(b.val() || ""); + if (!d.allowEmptyOption && !m.length) return; + for (j = m.split(d.delimiter), h = 0, i = j.length; h < i; h++) k = {}, k[f] = j[h], k[g] = j[h], c.options.push(k); + c.items = j + } + }, l = function (b, c) { + var k, l, m, n, o = c.options, p = {}, q = function (a) { + var b = e && a.attr(e); + return "string" == typeof b && b.length ? JSON.parse(b) : null + }, r = function (b, e) { + b = a(b); + var i = z(b.val()); + if (i || d.allowEmptyOption) if (p.hasOwnProperty(i)) { + if (e) { + var j = p[i][h]; + j ? a.isArray(j) ? j.push(e) : p[i][h] = [j, e] : p[i][h] = e + } + } else { + var k = q(b) || {}; + k[f] = k[f] || b.text(), k[g] = k[g] || i, k[h] = k[h] || e, p[i] = k, o.push(k), b.is(":selected") && c.items.push(i) + } + }, s = function (b) { + var d, e, f, g, h; + for (b = a(b), f = b.attr("label"), f && (g = q(b) || {}, g[i] = f, g[j] = f, c.optgroups.push(g)), h = a("option", b), d = 0, e = h.length; d < e; d++) r(h[d], f) + }; + for (c.maxItems = b.attr("multiple") ? null : 1, n = b.children(), k = 0, l = n.length; k < l; k++) m = n[k].tagName.toLowerCase(), "optgroup" === m ? s(n[k]) : "option" === m && r(n[k]) + }; + return this.each(function () { + if (!this.selectize) { + var e, f = a(this), g = this.tagName.toLowerCase(), + h = f.attr("placeholder") || f.attr("data-placeholder"); + h || d.allowEmptyOption || (h = f.children('option[value=""]').text()); + var i = {placeholder: h, options: [], optgroups: [], items: []}; + "select" === g ? l(f, i) : k(f, i), e = new M(f, a.extend(!0, {}, c, i, b)) + } + }) + }, a.fn.selectize.defaults = M.defaults, a.fn.selectize.support = {validity: x}, M.define("drag_drop", function (b) { + if (!a.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".'); + if ("multi" === this.settings.mode) { + var c = this; + c.lock = function () { + var a = c.lock; + return function () { + var b = c.$control.data("sortable"); + return b && b.disable(), a.apply(c, arguments) + } + }(), c.unlock = function () { + var a = c.unlock; + return function () { + var b = c.$control.data("sortable"); + return b && b.enable(), a.apply(c, arguments) + } + }(), c.setup = function () { + var b = c.setup; + return function () { + b.apply(this, arguments); + var d = c.$control.sortable({ + items: "[data-value]", + forcePlaceholderSize: !0, + disabled: c.isLocked, + start: function (a, b) { + b.placeholder.css("width", b.helper.css("width")), d.css({overflow: "visible"}) + }, + stop: function () { + d.css({overflow: "hidden"}); + var b = c.$activeItems ? c.$activeItems.slice() : null, e = []; + d.children("[data-value]").each(function () { + e.push(a(this).attr("data-value")) + }), c.setValue(e), c.setActiveItem(b) + } + }) + } + }() + } + }), M.define("dropdown_header", function (b) { + var c = this; + b = a.extend({ + title: "Untitled", + headerClass: "selectize-dropdown-header", + titleRowClass: "selectize-dropdown-header-title", + labelClass: "selectize-dropdown-header-label", + closeClass: "selectize-dropdown-header-close", + html: function (a) { + return '
    ' + a.title + '×
    ' + } + }, b), c.setup = function () { + var d = c.setup; + return function () { + d.apply(c, arguments), c.$dropdown_header = a(b.html(b)), c.$dropdown.prepend(c.$dropdown_header) + } + }() + }), M.define("optgroup_columns", function (b) { + var c = this; + b = a.extend({equalizeWidth: !0, equalizeHeight: !0}, b), this.getAdjacentOption = function (b, c) { + var d = b.closest("[data-group]").find("[data-selectable]"), e = d.index(b) + c; + return e >= 0 && e < d.length ? d.eq(e) : a() + }, this.onKeyDown = function () { + var a = c.onKeyDown; + return function (b) { + var d, e, f, g; + return !this.isOpen || b.keyCode !== j && b.keyCode !== m ? a.apply(this, arguments) : (c.ignoreHover = !0, g = this.$activeOption.closest("[data-group]"), d = g.find("[data-selectable]").index(this.$activeOption), g = b.keyCode === j ? g.prev("[data-group]") : g.next("[data-group]"), f = g.find("[data-selectable]"), e = f.eq(Math.min(f.length - 1, d)), void(e.length && this.setActiveOption(e))) + } + }(); + var d = function () { + var a, b = d.width, c = document; + return "undefined" == typeof b && (a = c.createElement("div"), a.innerHTML = '
    ', a = a.firstChild, c.body.appendChild(a), b = d.width = a.offsetWidth - a.clientWidth, c.body.removeChild(a)), b + }, e = function () { + var e, f, g, h, i, j, k; + if (k = a("[data-group]", c.$dropdown_content), f = k.length, f && c.$dropdown_content.width()) { + if (b.equalizeHeight) { + for (g = 0, e = 0; e < f; e++) g = Math.max(g, k.eq(e).height()); + k.css({height: g}) + } + b.equalizeWidth && (j = c.$dropdown_content.innerWidth() - d(), h = Math.round(j / f), k.css({width: h}), f > 1 && (i = j - h * (f - 1), k.eq(f - 1).css({width: i}))) + } + }; + (b.equalizeHeight || b.equalizeWidth) && (B.after(this, "positionDropdown", e), B.after(this, "refreshOptions", e)) + }), M.define("remove_button", function (b) { + b = a.extend({label: "×", title: "Remove", className: "remove", append: !0}, b); + var c = function (b, c) { + c.className = "remove-single"; + var d = b, + e = '' + c.label + "", + f = function (a, b) { + return a + b + }; + b.setup = function () { + var g = d.setup; + return function () { + if (c.append) { + var h = a(d.$input.context).attr("id"), i = (a("#" + h), d.settings.render.item); + d.settings.render.item = function (a) { + return f(i.apply(b, arguments), e) + } + } + g.apply(b, arguments), b.$control.on("click", "." + c.className, function (a) { + a.preventDefault(), d.isLocked || d.clear() + }) + } + }() + }, d = function (b, c) { + var d = b, + e = '' + c.label + "", + f = function (a, b) { + var c = a.search(/(<\/[^>]+>\s*)$/); + return a.substring(0, c) + b + a.substring(c) + }; + b.setup = function () { + var g = d.setup; + return function () { + if (c.append) { + var h = d.settings.render.item; + d.settings.render.item = function (a) { + return f(h.apply(b, arguments), e) + } + } + g.apply(b, arguments), b.$control.on("click", "." + c.className, function (b) { + if (b.preventDefault(), !d.isLocked) { + var c = a(b.currentTarget).parent(); + d.setActiveItem(c), d.deleteSelection() && d.setCaret(d.items.length) + } + }) + } + }() + }; + return "single" === this.settings.mode ? void c(this, b) : void d(this, b) + }), M.define("restore_on_backspace", function (a) { + var b = this; + a.text = a.text || function (a) { + return a[this.settings.labelField] + }, this.onKeyDown = function () { + var c = b.onKeyDown; + return function (b) { + var d, e; + return b.keyCode === p && "" === this.$control_input.val() && !this.$activeItems.length && (d = this.caretPos - 1, d >= 0 && d < this.items.length) ? (e = this.options[this.items[d]], this.deleteSelection(b) && (this.setTextboxValue(a.text.apply(this, [e])), this.refreshOptions(!0)), void b.preventDefault()) : c.apply(this, arguments) + } + }() + }), M +}); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index 3dc96ef26a..44e2a959c7 100644 --- a/js/utils.js +++ b/js/utils.js @@ -356,7 +356,7 @@ function CheckAll(sSelector, bValue) { /** * Toggle (enabled/disabled) the specified field of a form */ -function ToogleField(value, field_id) { +function ToggleField(value, field_id) { if (value) { $('#'+field_id).prop('disabled', false); // In case the field is rendered as a div containing several inputs (e.g. RedundancySettings) @@ -410,7 +410,7 @@ function PropagateCheckBox(bCurrValue, aFieldsList, bCheck) { if (bCurrValue == bCheck) { for (var i = 0; i < aFieldsList.length; i++) { $('#enable_'+aFieldsList[i]).prop('checked', bCheck); - ToogleField(bCheck, aFieldsList[i]); + ToggleField(bCheck, aFieldsList[i]); } } } diff --git a/pages/UI.php b/pages/UI.php index 2a664d567a..cbc02ec194 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -1244,7 +1244,7 @@ EOF } $aArgs = array('this' => $oObj); $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sAttCode, '', $iExpectCode, $aArgs); - $sComments = ''; + $sComments = ''; if (!isset($aValues[$sAttCode])) { $aValues[$sAttCode] = array(); diff --git a/pages/tagadmin.php b/pages/tagadmin.php new file mode 100644 index 0000000000..7652ae9ffa --- /dev/null +++ b/pages/tagadmin.php @@ -0,0 +1,142 @@ + + + +/** + * Page to configuration the tag sets + * + * @copyright Copyright (C) 2010-2018 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +require_once('../approot.inc.php'); +require_once(APPROOT.'application/application.inc.php'); +require_once(APPROOT.'application/itopwebpage.class.inc.php'); +require_once(APPROOT.'application/startup.inc.php'); +require_once(APPROOT.'application/loginwebpage.class.inc.php'); + +try +{ + LoginWebPage::DoLogin(); + // Check user rights and prompt if needed + ApplicationMenu::CheckMenuIdEnabled("TagAdminMenu"); + + $oAppContext = new ApplicationContext(); + + // Main program + // + $oP = new iTopWebPage(Dict::S('Menu:TagAdminMenu+')); + $oP->add_linked_script("../js/json.js"); + $oP->add_linked_script("../js/forms-json-utils.js"); + $oP->add_linked_script("../js/wizardhelper.js"); + $oP->add_linked_script("../js/wizard.utils.js"); + $oP->add_linked_script("../js/linkswidget.js"); + $oP->add_linked_script("../js/extkeywidget.js"); + $oP->add_linked_script("../js/jquery.blockUI.js"); + + $sBaseClass = 'TagSetFieldData'; + $sClass = utils::ReadParam('class', '', false, 'class'); + $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sOperation = utils::ReadParam('operation', ''); + + $oP->add(''); + + $oP->SetBreadCrumbEntry('ui-tool-tag-admin', Dict::S('Menu:TagAdminMenu'), Dict::S('Menu:TagAdminMenu+'), '', utils::GetAbsoluteUrlAppRoot().'images/wrench.png'); + + $sSearchHeaderForceDropdown = '\n"; + + try + { + if ($sOperation == 'search_form') + { + $sOQL = "SELECT $sClass $sOQLClause"; + $oFilter = DBObjectSearch::FromOQL($sOQL); + } + else + { + // Second part: advanced search form: + if (!empty($sFilter)) + { + $oFilter = DBSearch::unserialize($sFilter); + } + else if (!empty($sClass)) + { + $oFilter = new DBObjectSearch($sClass); + } + } + } + catch (CoreException $e) + { + $oFilter = new DBObjectSearch($sClass); + $oP->P("".Dict::Format('UI:TagSetFieldData:Error', $e->getHtmlDesc()).""); + } + + if ($oFilter != null) + { + $oSet = new CMDBObjectSet($oFilter); + $oBlock = new DisplayBlock($oFilter, 'search', false); + $aExtraParams = $oAppContext->GetAsHash(); + $aExtraParams['open'] = true; + $aExtraParams['class'] = $sClass; + $aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php'; + $aExtraParams['table_id'] = '1'; + $aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown; + $oBlock->Display($oP, 0, $aExtraParams); + + // Search results + $oResultBlock = new DisplayBlock($oFilter, 'list', false); + $oResultBlock->Display($oP, 1); + + // Menu node + $sFilter = $oFilter->ToOQL(); + $oP->add("\n\n"); + } + $oP->add("
    \n"); + + $oP->output(); +} +catch (Exception $e) +{ + require_once(APPROOT.'setup/setuppage.class.inc.php'); + + $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); + $oP->add("

    ".Dict::S('UI:FatalErrorMessage')."

    \n"); + //$oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); + $oP->output(); + + IssueLog::Error($e->getMessage()); +} \ No newline at end of file diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 8adb472ee4..dd57f53bae 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1388,18 +1388,47 @@ EOF { $aParameters['handler_class'] = $this->GetMandatoryPropString($oField, 'handler_class'); } - else - { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; - } - - if ($sAttType == 'AttributeTagSet') + elseif ($sAttType == 'AttributeTagSet') { $aTagFieldsInfo[] = $sAttCode; + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; + $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); + $aParameters['tag_code_max_len'] = $this->GetPropNumber($oField, 'tag_code_max_len', 20); + if ($aParameters['tag_code_max_len'] > 255) + { + $aParameters['tag_code_max_len'] = 255; + } + } + elseif ($sAttType == 'AttributeClassAttCodeSet') + { + $aTagFieldsInfo[] = $sAttCode; + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; + $aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12); + $aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field'); + $aParameters['attribute_definition_list'] = $this->GetPropString($oField, 'attribute_definition_list', ''); + } + elseif ($sAttType == 'AttributeClassState') + { + $aTagFieldsInfo[] = $sAttCode; + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; + $aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field'); + } + else + { + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; } // Optional parameters (more for historical reasons) @@ -1837,17 +1866,16 @@ EOF; ( 'category' => 'bizmodel', 'key_type' => 'autoincrement', - 'name_attcode' => array('tag_label'), + 'name_attcode' => array('label'), 'state_attcode' => '', - 'reconc_keys' => array('tag_code'), + 'reconc_keys' => array('code'), 'db_table' => '', // no need to have a corresponding table : this class exists only for rights, no additional field 'db_key_field' => 'id', 'db_finalclass_field' => 'finalclass', ); foreach ($aTagFieldsInfo as $sTagFieldName) { - $sTagSuffix = $sClassName.'_'.$sTagFieldName; - $sTagClassName = 'TagSetFieldDataFor_'.$sTagSuffix; + $sTagClassName = static::GetTagDataClassName($sClassName, $sTagFieldName); $sTagClassParams = var_export($aTagClassParams, true); $sPHP .= $this->GeneratePhpCodeForClass($sTagClassName, $sTagClassParentClass, $sTagClassParams); } @@ -1856,6 +1884,12 @@ EOF; return $sPHP; } + private static function GetTagDataClassName($sClass, $sAttCode) + { + $sTagSuffix = $sClass.'__'.$sAttCode; + + return 'TagSetFieldDataFor_'.$sTagSuffix; + } /** * @param $oMenu @@ -2668,7 +2702,7 @@ EOF; * @param bool $bIsAbstractClass * @param string $sMethods * - * @param string $aRequiredFiles + * @param array $aRequiredFiles * @param string $sCodeComment * * @return string php code for the class diff --git a/setup/itopdesignformat.class.inc.php b/setup/itopdesignformat.class.inc.php index 089b1657bb..cbddfe07d9 100644 --- a/setup/itopdesignformat.class.inc.php +++ b/setup/itopdesignformat.class.inc.php @@ -34,8 +34,8 @@ * echo "Error, failed to upgrade the format, reason(s):\n".implode("\n", $oFormat->GetErrors()); * } */ - -define('ITOP_DESIGN_LATEST_VERSION', '1.5'); // iTop >= 2.5.0 + +define('ITOP_DESIGN_LATEST_VERSION', '1.6'); // iTop >= 2.6.0 class iTopDesignFormat { @@ -73,6 +73,12 @@ class iTopDesignFormat '1.5' => array( 'previous' => '1.4', 'go_to_previous' => 'From15To14', + 'next' => '1.6', + 'go_to_next' => 'From15To16', + ), + '1.6' => array( + 'previous' => '1.5', + 'go_to_previous' => 'From16To15', 'next' => null, 'go_to_next' => null, ), @@ -629,6 +635,38 @@ class iTopDesignFormat { } + /** + * @param $oFactory + * + * @return void (Errors are logged) + */ + protected function From15To16($oFactory) + { + // nothing changed ! + } + + /** + * @param $oFactory + * + * @return void (Errors are logged) + */ + protected function From16To15($oFactory) + { + $oXPath = new DOMXPath($this->oDocument); + + // Remove AttributeTagSet nodes + // + $sPath = "/itop_design/classes/class/fields/field[@xsi:type='AttributeTagSet']"; + $oNodeList = $oXPath->query($sPath); + foreach ($oNodeList as $oNode) + { + $this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be removed.'); + $this->DeleteNode($oNode); + } + } + + + /** * Delete a node from the DOM and make sure to also remove the immediately following line break (DOMText), if any. * This prevents generating empty lines in the middle of the XML diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index 25eeeac974..b5cd4c6750 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -273,6 +273,10 @@ class XMLDataLoader $oDoc = new ormDocument($data, $sMimeType, $sFileName); $oTargetObj->Set($sAttCode, $oDoc); } + elseif ($oAttDef instanceof AttributeTagSet) + { + // TODO + } else { $value = (string)$oSubNode; diff --git a/sources/application/search/criterionconversion/criteriontooql.class.inc.php b/sources/application/search/criterionconversion/criteriontooql.class.inc.php index 025fe51e3c..a97d8d2bea 100644 --- a/sources/application/search/criterionconversion/criteriontooql.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontooql.class.inc.php @@ -74,6 +74,7 @@ class CriterionToOQL extends CriterionConversionAbstract self::OP_BETWEEN => 'BetweenToOql', self::OP_REGEXP => 'RegexpToOql', self::OP_IN => 'InToOql', + self::OP_MATCHES => 'MatchesToOql', self::OP_ALL => 'AllToOql', ); @@ -118,7 +119,10 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (empty($sValue)) return "1"; + if (empty($sValue)) + { + return "1"; + } return "({$sRef} LIKE '%{$sValue}%')"; } @@ -128,7 +132,10 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (empty($sValue)) return "1"; + if (empty($sValue)) + { + return "1"; + } return "({$sRef} LIKE '{$sValue}%')"; } @@ -138,7 +145,10 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (empty($sValue)) return "1"; + if (empty($sValue)) + { + return "1"; + } return "({$sRef} LIKE '%{$sValue}')"; } @@ -147,8 +157,10 @@ class CriterionToOQL extends CriterionConversionAbstract { $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - - if (empty($sValue)) return "1"; + if (empty($sValue) && (!(isset($aCriteria['has_undefined'])) || !($aCriteria['has_undefined']))) + { + return "1"; + } return "({$sRef} = '{$sValue}')"; } @@ -158,11 +170,49 @@ class CriterionToOQL extends CriterionConversionAbstract $aValues = self::GetValues($aCriteria); $sValue = self::GetValue($aValues, 0); - if (empty($sValue)) return "1"; + if (empty($sValue)) + { + return "1"; + } return "({$sRef} REGEXP '{$sValue}')"; } + protected static function MatchesToOql($oSearch, $sRef, $aCriteria) + { + $aValues = self::GetValues($aCriteria); + $aRawValues = array(); + $bHasUnDefined = isset($aCriteria['has_undefined']) ? $aCriteria['has_undefined'] : false; + for($i = 0; $i < count($aValues); $i++) + { + $sRawValue = self::GetValue($aValues, $i); + if (strlen($sRawValue) == 0) + { + $bHasUnDefined = true; + } + else + { + $aRawValues[] = $sRawValue; + } + } + $sValue = implode(' ', $aRawValues); + + if (empty($sValue)) + { + if ($bHasUnDefined) + { + return "({$sRef} = '')"; + } + return "1"; + } + + if ($bHasUnDefined) + { + return "((({$sRef} MATCHES '{$sValue}') OR ({$sRef} = '')) AND 1)"; + } + return "({$sRef} MATCHES '{$sValue}')"; + } + protected static function EmptyToOql($oSearch, $sRef, $aCriteria) { if (isset($aCriteria['widget'])) @@ -197,18 +247,18 @@ class CriterionToOQL extends CriterionConversionAbstract return "({$sRef} != '')"; } - /** - * @param \DBObjectSearch $oSearch - * @param string $sRef - * @param array $aCriteria - * - * @return mixed|string - * - * @throws \CoreException - * @throws \MissingQueryArgument - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ + /** + * @param \DBObjectSearch $oSearch + * @param string $sRef + * @param array $aCriteria + * + * @return mixed|string + * + * @throws \CoreException + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + */ protected static function InToOql($oSearch, $sRef, $aCriteria) { $sAttCode = $aCriteria['code']; @@ -225,8 +275,7 @@ class CriterionToOQL extends CriterionConversionAbstract try { $aAttributeDefs = MetaModel::ListAttributeDefs($sClass); - } - catch (\CoreException $e) + } catch (\CoreException $e) { return "1"; } @@ -254,8 +303,7 @@ class CriterionToOQL extends CriterionConversionAbstract try { $sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass); - } - catch (\CoreException $e) + } catch (\CoreException $e) { } } @@ -371,9 +419,10 @@ class CriterionToOQL extends CriterionConversionAbstract else { // Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing - $sCondition = "(({$sCondition} OR {$sFilterOnUndefined}) AND 1)"; + $sCondition = "(({$sCondition} OR {$sFilterOnUndefined}) AND 1)"; } } + return $sCondition; } @@ -406,8 +455,7 @@ class CriterionToOQL extends CriterionConversionAbstract $oDate = $oFormat->parse($sStartDate); $sStartDate = $oDate->format($sAttributeClass::GetSQLFormat()); $aOQL[] = "({$sRef} >= '$sStartDate')"; - } - catch (Exception $e) + } catch (Exception $e) { } } @@ -420,8 +468,7 @@ class CriterionToOQL extends CriterionConversionAbstract $oDate = $oFormat->parse($sEndDate); $sEndDate = $oDate->format($sAttributeClass::GetSQLFormat()); $aOQL[] = "({$sRef} <= '$sEndDate')"; - } - catch (Exception $e) + } catch (Exception $e) { } } diff --git a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php index f02394abdf..f3798f0a8d 100644 --- a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php +++ b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php @@ -71,6 +71,8 @@ class CriterionToSearchForm extends CriterionConversionAbstract AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'ExternalKeyToSearchForm', AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY => 'ExternalKeyToSearchForm', AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'EnumToSearchForm', + AttributeDefinition::SEARCH_WIDGET_TYPE_SET => 'SetToSearchForm', + AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET => 'TagSetToSearchForm', ); foreach($aAndCriterionRaw as $aCriteria) @@ -666,6 +668,67 @@ class CriterionToSearchForm extends CriterionConversionAbstract return $aCriteria; } + protected static function TagSetToSearchForm($aCriteria, $aFields) + { + $sOperator = $aCriteria['operator']; + switch ($sOperator) + { + case 'MATCHES': + // Nothing special to do + if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined']) + { + if (!isset($aCriteria['values'])) + { + $aCriteria['values'] = array(); + } + // Convention for 'undefined' tag set + $aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined')); + } + break; + + case 'OR': + case 'ISNULL': + $aCriteria['operator'] = CriterionConversionAbstract::OP_EQUALS; + if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined']) + { + if (!isset($aCriteria['values'])) + { + $aCriteria['values'] = array(); + } + // Convention for 'undefined' tag set + $aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined')); + } + break; + + case '=': + // TODO BUG SPLIT INTO AN 'AND' LIST OF MATCHES + $aCriteria['operator'] = CriterionConversionAbstract::OP_EQUALS; + if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined']) + { + if (!isset($aCriteria['values'])) + { + $aCriteria['values'] = array(); + } + // Convention for 'undefined' tag set + $aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined')); + } + break; + + default: + // Unknown operator + $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW; + break; + } + return $aCriteria; + } + + // TODO New widget based on String but with match + protected static function SetToSearchForm($aCriteria, $aFields) + { + $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW; + return $aCriteria; + } + protected static function ExternalKeyToSearchForm($aCriteria, $aFields) { $sOperator = $aCriteria['operator']; diff --git a/sources/application/search/criterionconversionabstract.class.inc.php b/sources/application/search/criterionconversionabstract.class.inc.php index d4296edbff..b1f41d7e15 100644 --- a/sources/application/search/criterionconversionabstract.class.inc.php +++ b/sources/application/search/criterionconversionabstract.class.inc.php @@ -37,6 +37,7 @@ abstract class CriterionConversionAbstract const OP_BETWEEN = 'between'; const OP_REGEXP = 'REGEXP'; const OP_ALL = 'all'; + const OP_MATCHES = 'MATCHES'; } diff --git a/sources/application/search/searchform.class.inc.php b/sources/application/search/searchform.class.inc.php index db3bb7e09e..9342301df9 100644 --- a/sources/application/search/searchform.class.inc.php +++ b/sources/application/search/searchform.class.inc.php @@ -46,7 +46,6 @@ use WebPage; class SearchForm { - /** * @param \WebPage $oPage * @param \CMDBObjectSet $oSet @@ -55,6 +54,7 @@ class SearchForm * @return string * @throws \CoreException * @throws \DictExceptionMissingString + * @throws \Exception */ public function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { @@ -136,7 +136,8 @@ class SearchForm ksort($aOptions); $sContext = $oAppContext->GetForLink(); $sJsonExtraParams = htmlentities(json_encode($aListParams), ENT_QUOTES); - $sClassesCombo = "\n".implode('', $aOptions)."\n"; } else @@ -497,7 +498,7 @@ class SearchForm { try { - $sOQL = $oExpression->Render($aArgs); + $sOQL = $oExpression->RenderExpression(false, $aArgs); $oExpression = Expression::FromOQL($sOQL); } catch (MissingQueryArgument $e) @@ -515,7 +516,7 @@ class SearchForm foreach($aAndExpressions as $oAndSubExpr) { /** @var Expression $oAndSubExpr */ - if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->Render() == 1)) + if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->RenderExpression(false) == 1)) { continue; } @@ -595,21 +596,22 @@ class SearchForm return $aFields; } - /** - * @param string $sClass - * @param string $sClassAlias - * @param string $sAttCode - * @param \AttributeDefinition $oAttDef - * @param array $aFields - * @param bool $bHasIndex - * - * @return mixed - * - * @throws \CoreException - * @throws \MissingQueryArgument - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - */ + /** + * @param string $sClass + * @param string $sClassAlias + * @param string $sAttCode + * @param \AttributeDefinition $oAttDef + * @param array $aFields + * @param bool $bHasIndex + * + * @return mixed + * + * @throws \CoreException + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \Exception + */ private function AppendField($sClass, $sClassAlias, $sAttCode, $oAttDef, $aFields, $bHasIndex = false) { if (!is_null($oAttDef) && ($oAttDef->GetSearchType() != AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)) diff --git a/sources/autoload.php b/sources/autoload.php index 4a9499ba35..612f727c53 100644 --- a/sources/autoload.php +++ b/sources/autoload.php @@ -45,6 +45,8 @@ require_once APPROOT . 'sources/form/field/multipleselectfield.class.inc.php'; require_once APPROOT . 'sources/form/field/selectobjectfield.class.inc.php'; require_once APPROOT . 'sources/form/field/checkboxfield.class.inc.php'; require_once APPROOT . 'sources/form/field/radiofield.class.inc.php'; +require_once APPROOT . 'sources/form/field/setfield.class.inc.php'; +require_once APPROOT . 'sources/form/field/tagsetfield.class.inc.php'; require_once APPROOT . 'sources/form/field/linkedsetfield.class.inc.php'; require_once APPROOT . 'sources/form/validator/validator.class.inc.php'; require_once APPROOT . 'sources/form/validator/mandatoryvalidator.class.inc.php'; @@ -56,6 +58,7 @@ require_once APPROOT . 'sources/renderer/renderingoutput.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/bsformrenderer.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php'; +require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssetfieldrenderer.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssubformfieldrenderer.class.inc.php'; require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php'; diff --git a/sources/form/field/setfield.class.inc.php b/sources/form/field/setfield.class.inc.php new file mode 100644 index 0000000000..dde521bd49 --- /dev/null +++ b/sources/form/field/setfield.class.inc.php @@ -0,0 +1,30 @@ + + +namespace Combodo\iTop\Form\Field; + +/** + * Description of SetField + * + * @author Guillaume Lajarige + */ +class SetField extends Field +{ + +} diff --git a/sources/form/field/tagsetfield.class.inc.php b/sources/form/field/tagsetfield.class.inc.php new file mode 100644 index 0000000000..48410bd33f --- /dev/null +++ b/sources/form/field/tagsetfield.class.inc.php @@ -0,0 +1,30 @@ + + +namespace Combodo\iTop\Form\Field; + +/** + * Description of TagSetField + * + * @author Guillaume Lajarige + */ +class TagSetField extends Field +{ + +} diff --git a/sources/renderer/bootstrap/bsformrenderer.class.inc.php b/sources/renderer/bootstrap/bsformrenderer.class.inc.php index 821d179ca2..34a623990a 100644 --- a/sources/renderer/bootstrap/bsformrenderer.class.inc.php +++ b/sources/renderer/bootstrap/bsformrenderer.class.inc.php @@ -55,6 +55,8 @@ class BsFormRenderer extends FormRenderer $this->AddSupportedField('SubFormField', 'BsSubFormFieldRenderer'); $this->AddSupportedField('SelectObjectField', 'BsSelectObjectFieldRenderer'); $this->AddSupportedField('LinkedSetField', 'BsLinkedSetFieldRenderer'); + $this->AddSupportedField('SetField', 'BsSetFieldRenderer'); + $this->AddSupportedField('TagSetField', 'BsSetFieldRenderer'); $this->AddSupportedField('DateTimeField', 'BsSimpleFieldRenderer'); $this->AddSupportedField('DurationField', 'BsSimpleFieldRenderer'); $this->AddSupportedField('FileUploadField', 'BsFileUploadFieldRenderer'); diff --git a/sources/renderer/bootstrap/fieldrenderer/bssetfieldrenderer.class.inc.php b/sources/renderer/bootstrap/fieldrenderer/bssetfieldrenderer.class.inc.php new file mode 100644 index 0000000000..2a68a5a101 --- /dev/null +++ b/sources/renderer/bootstrap/fieldrenderer/bssetfieldrenderer.class.inc.php @@ -0,0 +1,133 @@ + + +namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer; + +use MetaModel; +use Combodo\iTop\Renderer\FieldRenderer; +use Combodo\iTop\Renderer\RenderingOutput; + +/** + * Description of BsSetFieldRenderer + * + * @author Guillaume Lajarige + */ +class BsSetFieldRenderer extends FieldRenderer +{ + /** + * @inheritdoc + */ + public function Render() + { + $oOutput = new RenderingOutput(); + $oOutput->AddCssClass('form_field_' . $this->oField->GetDisplayMode()); + + $sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : ''; + // Vars to build the table +// $sAttributesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay()); +// $sAttCodesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay(true)); +// $aItems = array(); +// $aItemIds = array(); +// $this->PrepareItems($aItems, $aItemIds); +// $sItemsAsJson = json_encode($aItems); +// $sItemIdsAsJson = htmlentities(json_encode(array('current' => $aItemIds)), ENT_QUOTES, 'UTF-8'); + + // Rendering field + if (!$this->oField->GetHidden()) + { + /** @var \ormSet $oOrmItemSet */ + $oOrmItemSet = $this->oField->GetCurrentValue(); + + // Opening container + $oOutput->AddHtml('
    '); + + // Label + $oOutput->AddHtml('
    '); + if ($this->oField->GetLabel() !== '') + { + $oOutput->AddHtml(''); + } + $oOutput->AddHtml('
    '); + + // Value + $oOutput->AddHtml('
    '); + // ... in edit mode + if(!$this->oField->GetReadOnly()) + { + $oAttDef = MetaModel::GetAttributeDef($oOrmItemSet->GetClass(), $oOrmItemSet->GetAttCode()); + $sJSONForWidget = $oAttDef->GetJsonForWidget($oOrmItemSet); + + // - Help block + $oOutput->AddHtml('
    '); + + // - Value regarding the field type + $oOutput->AddHtml(''); + + // Attaching JS widget only if field is hidden or NOT read only + // JS Form field widget construct + $aValidators = array(); + $sValidators = json_encode($aValidators); + $oOutput->AddJs( +<<oField->GetGlobalId()}').val(); + + return value; + }, + }); +EOF + ); + } + // ... in view mode + else + { + $aItems = $oOrmItemSet->GetTags(); + $oOutput->AddHtml('
    ') + ->AddHtml(''); + foreach($aItems as $sItemCode => $oItem) + { + $sItemLabel = $oItem->Get('label'); + $sItemDescription = $oItem->Get('description'); + $oOutput->AddHtml('') + ->AddHtml($sItemLabel, true) + ->AddHtml(''); + } + $oOutput->AddHtml('') + ->AddHtml('
    '); + } + $oOutput->AddHtml('
    '); + } + + return $oOutput; + } +} diff --git a/sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php b/sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php index 5e92f847f0..f702937e6a 100644 --- a/sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php +++ b/sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php @@ -288,7 +288,7 @@ EOF { // TODO } - // ... clasic rendering for fields with only one value + // ... classic rendering for fields with only one value else { switch ($sFieldClass) diff --git a/test/ItopDataTestCase.php b/test/ItopDataTestCase.php index b542f2c623..7711ed32cb 100644 --- a/test/ItopDataTestCase.php +++ b/test/ItopDataTestCase.php @@ -38,12 +38,16 @@ use lnkContactToFunctionalCI; use MetaModel; use Person; use Server; +use TagSetFieldData; use Ticket; use URP_UserProfile; use VirtualHost; use VirtualMachine; +define('TAG_CLASS', 'Ticket'); +define('TAG_ATTCODE', 'tagfield'); + /** * @runTestsInSeparateProcesses * @preserveGlobalState disabled @@ -51,7 +55,11 @@ use VirtualMachine; */ class ItopDataTestCase extends ItopTestCase { - protected $testOrgId; + private $iTestOrgId; + // For cleanup + private $aCreatedObjects = array(); + + const USE_TRANSACTION = true; /** * @throws Exception @@ -68,11 +76,11 @@ class ItopDataTestCase extends ItopTestCase $sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE; MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv); - CMDBSource::Query('START TRANSACTION'); - - // Create a specific organization for the tests - $oOrg = $this->CreateOrganization('UnitTestOrganization'); - $this->testOrgId = $oOrg->GetKey(); + if (static::USE_TRANSACTION) + { + CMDBSource::Query('START TRANSACTION'); + } + $this->CreateTestOrganization(); } /** @@ -80,7 +88,30 @@ class ItopDataTestCase extends ItopTestCase */ protected function tearDown() { - CMDBSource::Query('ROLLBACK'); + if (static::USE_TRANSACTION) + { + $this->debug("ROLLBACK !!!"); + CMDBSource::Query('ROLLBACK'); + } + else + { + $this->debug(""); + $this->aCreatedObjects = array_reverse($this->aCreatedObjects); + foreach($this->aCreatedObjects as $oObject) + { + /** @var DBObject $oObject */ + try + { + $sClass = get_class($oObject); + $iKey = $oObject->GetKey(); + $this->debug("Removing $sClass::$iKey"); + $oObject->DBDelete(); + } catch (Exception $e) + { + $this->debug($e->getMessage()); + } + } + } } /** @@ -88,7 +119,7 @@ class ItopDataTestCase extends ItopTestCase */ public function getTestOrgId() { - return $this->testOrgId; + return $this->iTestOrgId; } ///////////////////////////////////////////////////////////////////////////// @@ -101,7 +132,7 @@ class ItopDataTestCase extends ItopTestCase * @return DBObject * @throws Exception */ - protected static function createObject($sClass, $aParams) + protected function createObject($sClass, $aParams) { $oMyObj = MetaModel::NewObject($sClass); foreach($aParams as $sAttCode => $oValue) @@ -109,6 +140,9 @@ class ItopDataTestCase extends ItopTestCase $oMyObj->Set($sAttCode, $oValue); } $oMyObj->DBInsert(); + $iKey = $oMyObj->GetKey(); + $this->debug("Created $sClass::$iKey"); + $this->aCreatedObjects[] = $oMyObj; return $oMyObj; } @@ -137,16 +171,16 @@ class ItopDataTestCase extends ItopTestCase * Create an Organization in database * * @param string $sName - * @return Organization + * @return \Organization * @throws Exception */ protected function CreateOrganization($sName) { - /** @var Organization $oObj */ - $oObj = self::createObject('Organization', array( + /** @var \Organization $oObj */ + $oObj = $this->createObject('Organization', array( 'name' => $sName, )); - $this->debug("\nCreated Organization {$oObj->Get('name')}"); + $this->debug("Created Organization {$oObj->Get('name')}"); return $oObj; } @@ -160,31 +194,95 @@ class ItopDataTestCase extends ItopTestCase protected function CreateTicket($iNum) { /** @var Ticket $oTicket */ - $oTicket = self::createObject('UserRequest', array( + $oTicket = $this->createObject('UserRequest', array( 'ref' => 'Ticket_'.$iNum, - 'title' => 'BUG 789_'.$iNum, + 'title' => 'TICKET_'.$iNum, //'request_type' => 'incident', - 'description' => 'method UpdateImpactedItems() reconstruit le lnkContactToTicket donc impossible de rajouter des champs dans cette classe', + 'description' => 'Created for unit tests.', 'org_id' => $this->getTestOrgId(), )); - $this->debug("\nCreated {$oTicket->Get('title')} ({$oTicket->Get('ref')})"); + $this->debug("Created {$oTicket->Get('title')} ({$oTicket->Get('ref')})"); return $oTicket; } + protected function RemoveTicket($iNum) + { + $this->RemoveObjects('UserRequest', "SELECT UserRequest WHERE ref = 'Ticket_$iNum'"); + } + /** + * Create a Ticket in database + * + * @param string $sClass + * @param string $sAttCode + * @param string $sTagCode + * @param string $sTagLabel + * @param string $sTagDescription + * + * @return \TagSetFieldData + * @throws \CoreException + */ + protected function CreateTagData($sClass, $sAttCode, $sTagCode, $sTagLabel, $sTagDescription = '') + { + $sTagClass = TagSetFieldData::GetTagDataClassName($sClass, $sAttCode); + $oTagData = $this->createObject($sTagClass, array( + 'code' => $sTagCode, + 'label' => $sTagLabel, + 'obj_class' => $sClass, + 'obj_attcode' => $sAttCode, + 'description' => $sTagDescription, + )); + $this->debug("Created {$oTagData->Get('code')} ({$oTagData->Get('label')})"); + + /** @var \TagSetFieldData $oTagData */ + return $oTagData; + } + + /** + * Create a Ticket in database + * + * @param string $sClass + * @param string $sAttCode + * @param string $sTagCode + * + * @throws \CoreException + */ + protected function RemoveTagData($sClass, $sAttCode, $sTagCode) + { + $sTagClass = TagSetFieldData::GetTagDataClassName($sClass, $sAttCode); + $this->RemoveObjects($sTagClass, "SELECT $sTagClass WHERE code = '$sTagCode'"); + } + + private function RemoveObjects($sClass, $sOQL) + { + $oFilter = \DBSearch::FromOQL($sOQL); + $aRes = $oFilter->ToDataArray(array('id')); + foreach($aRes as $aRow) + { + $this->debug($aRow); + $iKey = $aRow['id']; + if (!empty($iKey)) + { + $oObject = MetaModel::GetObject($sClass, $iKey); + $oObject->DBDelete(); + } + } + } + + /** * Create a UserRequest in database * * @param int $iNum * @param int $iTimeSpent * @param int $iOrgId * @param int $iCallerId - * @return UserRequest + * @return \UserRequest * @throws Exception */ protected function CreateUserRequest($iNum, $iTimeSpent = 0, $iOrgId = 0, $iCallerId = 0) { - /** @var UserRequest $oTicket */ - $oTicket = self::createObject('UserRequest', array( + /** @var \UserRequest $oTicket */ + $oTicket = $this->createObject('UserRequest', array( 'ref' => 'Ticket_'.$iNum, 'title' => 'BUG 1161_'.$iNum, //'request_type' => 'incident', @@ -193,7 +291,7 @@ class ItopDataTestCase extends ItopTestCase 'caller_id' => $iCallerId, 'org_id' => ($iOrgId == 0 ? $this->getTestOrgId() : $iOrgId), )); - $this->debug("\nCreated {$oTicket->Get('title')} ({$oTicket->Get('ref')})"); + $this->debug("Created {$oTicket->Get('title')} ({$oTicket->Get('ref')})"); return $oTicket; } @@ -209,7 +307,7 @@ class ItopDataTestCase extends ItopTestCase protected function CreateServer($iNum, $iRackUnit = null) { /** @var Server $oServer */ - $oServer = self::createObject('Server', array( + $oServer = $this->createObject('Server', array( 'name' => 'Server_'.$iNum, 'org_id' => $this->getTestOrgId(), 'nb_u' => $iRackUnit, @@ -228,7 +326,7 @@ class ItopDataTestCase extends ItopTestCase */ protected function CreatePhysicalInterface($iNum, $iSpeed, $iConnectableCiId) { - $oObj = self::createObject('PhysicalInterface', array( + $oObj = $this->createObject('PhysicalInterface', array( 'name' => "$iNum", 'speed' => $iSpeed, 'connectableci_id' => $iConnectableCiId, @@ -247,7 +345,7 @@ class ItopDataTestCase extends ItopTestCase */ protected function CreateFiberChannelInterface($iNum, $iSpeed, $iConnectableCiId) { - $oObj = self::createObject('FiberChannelInterface', array( + $oObj = $this->createObject('FiberChannelInterface', array( 'name' => "$iNum", 'speed' => $iSpeed, 'datacenterdevice_id' => $iConnectableCiId, @@ -266,7 +364,7 @@ class ItopDataTestCase extends ItopTestCase protected function CreatePerson($iNum, $iOrgId = 0) { /** @var Person $oPerson */ - $oPerson = self::createObject('Person', array( + $oPerson = $this->createObject('Person', array( 'name' => 'Person_'.$iNum, 'first_name' => 'Test', 'org_id' => ($iOrgId == 0 ? $this->getTestOrgId() : $iOrgId), @@ -287,7 +385,7 @@ class ItopDataTestCase extends ItopTestCase $oUserProfile->Set('profileid', $iProfileId); $oUserProfile->Set('reason', 'UNIT Tests'); $oSet = DBObjectSet::FromObject($oUserProfile); - $oUser = self::createObject('UserLocal', array( + $oUser = $this->createObject('UserLocal', array( 'contactid' => 2, 'login' => $sLogin, 'password' => $sLogin, @@ -310,7 +408,7 @@ class ItopDataTestCase extends ItopTestCase protected function CreateHypervisor($iNum, $oServer, $oFarm = null) { /** @var Hypervisor $oHypervisor */ - $oHypervisor = self::createObject('Hypervisor', array( + $oHypervisor = $this->createObject('Hypervisor', array( 'name' => 'Hypervisor_'.$iNum, 'org_id' => $this->getTestOrgId(), 'server_id' => $oServer->GetKey(), @@ -337,7 +435,7 @@ class ItopDataTestCase extends ItopTestCase protected function CreateFarm($iNum, $sRedundancy = '1') { /** @var Farm $oFarm */ - $oFarm = self::createObject('Farm', array( + $oFarm = $this->createObject('Farm', array( 'name' => 'Farm_'.$iNum, 'org_id' => $this->getTestOrgId(), 'redundancy' => $sRedundancy, @@ -356,7 +454,7 @@ class ItopDataTestCase extends ItopTestCase protected function CreateVirtualMachine($iNum, $oVirtualHost) { /** @var VirtualMachine $oVirtualMachine */ - $oVirtualMachine = self::createObject('VirtualMachine', array( + $oVirtualMachine = $this->createObject('VirtualMachine', array( 'name' => 'VirtualMachine_'.$iNum, 'org_id' => $this->getTestOrgId(), 'virtualhost_id' => $oVirtualHost->GetKey(), @@ -509,13 +607,15 @@ class ItopDataTestCase extends ItopTestCase } } - /** - * Reload a Ticket from the database. - * - * @param DBObject $oObject - * @throws ArchivedObjectException - * @throws Exception - */ + /** + * Reload a Ticket from the database. + * + * @param DBObject $oObject + * + * @return \DBObject|null + * @throws ArchivedObjectException + * @throws Exception + */ protected function ReloadObject(&$oObject) { $oObject = MetaModel::GetObject(get_class($oObject), $oObject->GetKey()); @@ -573,5 +673,12 @@ class ItopDataTestCase extends ItopTestCase } } + protected function CreateTestOrganization() + { + // Create a specific organization for the tests + $oOrg = $this->CreateOrganization('UnitTestOrganization'); + $this->iTestOrgId = $oOrg->GetKey(); + } + } \ No newline at end of file diff --git a/test/application/search/CriterionConversionTest.php b/test/application/search/CriterionConversionTest.php index 0805c1b9a1..c041901f9f 100644 --- a/test/application/search/CriterionConversionTest.php +++ b/test/application/search/CriterionConversionTest.php @@ -173,6 +173,7 @@ class CriterionConversionTest extends ItopDataTestCase * @param $aCriterion * @param $sExpectedOperator * + * @throws \CoreException * @throws \OQLException */ function testToSearchForm($aCriterion, $sExpectedOperator) @@ -311,22 +312,27 @@ class CriterionConversionTest extends ItopDataTestCase ); } - /** - * @dataProvider OqlProvider - * - * @param $sOQL - * - * @param $sExpectedOQL - * - * @param $aExpectedCriterion - * - * @throws \DictExceptionUnknownLanguage - * @throws \MissingQueryArgument - * @throws \OQLException - */ - function testOqlToForSearchToOql($sOQL, $sExpectedOQL, $aExpectedCriterion) + /** + * @dataProvider OqlProvider + * + * @param $sOQL + * + * @param $sExpectedOQL + * + * @param $aExpectedCriterion + * + * @throws \DictExceptionUnknownLanguage + * @throws \MissingQueryArgument + * @throws \OQLException + * @throws \CoreException + */ + function testOqlToSearchToOql($sOQL, $sExpectedOQL, $aExpectedCriterion) { - $this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US"); + // For tests on tags + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second'); + + $this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US"); } function OqlProvider() @@ -452,46 +458,67 @@ class CriterionConversionTest extends ItopDataTestCase 'ExpectedOQL' => "SELECT `dev` FROM DatacenterDevice AS `dev` WHERE ((INET_ATON(`dev`.`managementip`) < INET_ATON('10.22.32.255')) AND (INET_ATON(`dev`.`managementip`) > INET_ATON('10.22.32.224')))", 'ExpectedCriterion' => array(array('widget' => 'raw')), ), - + 'TagSet Matches' => array( + 'OQL' => "SELECT UserRequest WHERE tagfield MATCHES 'tag1'", + 'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.`tagfield` MATCHES 'tag1'", + 'ExpectedCriterion' => array(array('widget' => 'tag_set')), + ), + 'TagSet Matches2' => array( + 'OQL' => "SELECT UserRequest WHERE tagfield MATCHES 'tag1 tag2'", + 'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.`tagfield` MATCHES 'tag1 tag2'", + 'ExpectedCriterion' => array(array('widget' => 'tag_set')), + ), + 'TagSet Undefined' => array( + 'OQL' => "SELECT UserRequest WHERE tagfield = ''", + 'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`tagfield` = '')", + 'ExpectedCriterion' => array(array('widget' => 'tag_set')), + ), + 'TagSet Undefined and tag' => array( + 'OQL' => "SELECT UserRequest WHERE (((tagfield MATCHES 'tag1 tag2') OR (tagfield = '')) AND 1)", + 'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`tagfield` MATCHES 'tag1 tag2' OR (`UserRequest`.`tagfield` = '')) AND 1)", + 'ExpectedCriterion' => array(array('widget' => 'tag_set')), + ), ); } - /** - * @dataProvider OqlProviderDates - * - * @param $sOQL - * - * @param $sExpectedOQL - * - * @param $aExpectedCriterion - * - * @throws \DictExceptionUnknownLanguage - * @throws \MissingQueryArgument - * @throws \OQLException - */ + /** + * @dataProvider OqlProviderDates + * + * @param $sOQL + * + * @param $sExpectedOQL + * + * @param $aExpectedCriterion + * + * @throws \DictExceptionUnknownLanguage + * @throws \MissingQueryArgument + * @throws \OQLException + * @throws \CoreException + */ function testOqlToForSearchToOqlAltLanguageFR($sOQL, $sExpectedOQL, $aExpectedCriterion) { - $this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "FR FR"); + $this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "FR FR"); } - /** - * @dataProvider OqlProviderDates - * - * @param $sOQL - * - * @param $sExpectedOQL - * - * @param $aExpectedCriterion - * - * @throws \DictExceptionUnknownLanguage - * @throws \MissingQueryArgument - * @throws \OQLException - */ + /** + * @dataProvider OqlProviderDates + * + * @param $sOQL + * + * @param $sExpectedOQL + * + * @param $aExpectedCriterion + * + * @throws \DictExceptionUnknownLanguage + * @throws \MissingQueryArgument + * @throws \OQLException + * @throws \CoreException + */ function testOqlToForSearchToOqlAltLanguageEN($sOQL, $sExpectedOQL, $aExpectedCriterion) { - $this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US"); + $this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US"); } @@ -572,21 +599,22 @@ class CriterionConversionTest extends ItopDataTestCase ); } - /** - * - * @param $sOQL - * - * @param $sExpectedOQL - * - * @param $aExpectedCriterion - * - * @param $sLanguageCode - * - * @throws \DictExceptionUnknownLanguage - * @throws \MissingQueryArgument - * @throws \OQLException - */ - function OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, $sLanguageCode ) + /** + * + * @param $sOQL + * + * @param $sExpectedOQL + * + * @param $aExpectedCriterion + * + * @param $sLanguageCode + * + * @throws \CoreException + * @throws \DictExceptionUnknownLanguage + * @throws \MissingQueryArgument + * @throws \OQLException + */ + function OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, $sLanguageCode ) { $this->debug($sOQL); diff --git a/test/application/search/SearchFormTest.php b/test/application/search/SearchFormTest.php index f2806a3465..850233fd7b 100644 --- a/test/application/search/SearchFormTest.php +++ b/test/application/search/SearchFormTest.php @@ -43,6 +43,7 @@ class SearchFormTest extends ItopDataTestCase /** * @dataProvider GetFieldsProvider * @throws \OQLException + * @throws \CoreException */ public function testGetFields($sOQL) { @@ -74,6 +75,8 @@ class SearchFormTest extends ItopDataTestCase * @param $sOQL * @param $iOrCount * + * @throws \CoreException + * @throws \MissingQueryArgument */ public function testGetCriterion($sOQL, $iOrCount) { diff --git a/test/attributeset_widget_poc.html b/test/attributeset_widget_poc.html new file mode 100644 index 0000000000..396efa58fb --- /dev/null +++ b/test/attributeset_widget_poc.html @@ -0,0 +1,109 @@ + + + + + + +AttributeSet fields widget test + + + + + + + + + + + + + + + +

    POC Set widget (itop.set_widget) et CSS

    + +

    Edition : widget

    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + +

    Visualisation

    + +

    + + Ville 🏢 + Mer 🌊 + Soleil 🌞 + +

    + + + + \ No newline at end of file diff --git a/test/core/DBSearchCommitTest.php b/test/core/DBSearchCommitTest.php new file mode 100644 index 0000000000..267b723a67 --- /dev/null +++ b/test/core/DBSearchCommitTest.php @@ -0,0 +1,93 @@ + + *
  • MakeGroupByQuery
  • + * + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + * @backupGlobals disabled + */ +class DBSearchCommitTest extends ItopDataTestCase +{ + // Need database COMMIT in order to create the FULLTEXT INDEX of MySQL + const USE_TRANSACTION = false; + + /** + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + * @throws \Exception + */ + public function testAttributeTagSet() + { + // Create a tag + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'UNIT First'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'UNIT Second'); + //Use it + $oTicket = $this->CreateTicket(1); + $oTicket->Set(TAG_ATTCODE, 'tag1'); + $oTicket->DBWrite(); + + $oSearch = DBSearch::FromOQL("SELECT UserRequest"); + $oSearch->AddCondition(TAG_ATTCODE, 'tag1', 'MATCHES'); + $oSet = new \DBObjectSet($oSearch); + static::assertEquals(1, $oSet->Count()); + + + $oTicket->Set(TAG_ATTCODE, 'tag1 tag2'); + $oTicket->DBWrite(); + + $oSet = new \DBObjectSet($oSearch); + static::assertEquals(1, $oSet->Count()); + } + + /** + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + * @throws \Exception + */ + public function testAttributeTagSet2() + { + // Create a tag + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'UNIT First'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'UNIT Second'); + //Use it + $oTicket = $this->CreateTicket(1); + $oTicket->Set(TAG_ATTCODE, 'tag1'); + $oTicket->DBWrite(); + + $oSearch = DBSearch::FromOQL("SELECT UserRequest"); + $oSearch->AddCondition(TAG_ATTCODE, 'tag1'); + $oSet = new \DBObjectSet($oSearch); + static::assertEquals(1, $oSet->Count()); + + + $oTicket->Set(TAG_ATTCODE, 'tag1 tag2'); + $oTicket->DBWrite(); + + $oSet = new \DBObjectSet($oSearch); + static::assertEquals(0, $oSet->Count()); + } + +} diff --git a/test/core/OQLTest.php b/test/core/OQLTest.php new file mode 100644 index 0000000000..6d1861967e --- /dev/null +++ b/test/core/OQLTest.php @@ -0,0 +1,269 @@ +debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oQuery = $oOql->ParseQuery(); + static::assertInstanceOf('OqlQuery', $oQuery); + } + + public function GoodQueryProvider() + { + return array( + array('SELECT toto'), + array('SELECT toto WHERE toto.a = 1'), + array('SELECT toto WHERE toto.a = -1'), + array('SELECT toto WHERE toto.a = (1-1)'), + array('SELECT toto WHERE toto.a = (-1+3)'), + array('SELECT toto WHERE toto.a = (3+-1)'), + array('SELECT toto WHERE toto.a = (3--1)'), + array('SELECT toto WHERE toto.a = 0xC'), + array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''), + array('SELECT toto WHERE toto.a = :myparameter'), + array('SELECT toto WHERE toto.a IN (:param1)'), + array('SELECT toto WHERE toto.a IN (:param1, :param2)'), + array('SELECT toto WHERE toto.a=1'), + array('SELECT toto WHERE toto.a = "1"'), + array('SELECT toto WHERE toto.a & 1'), + array('SELECT toto WHERE toto.a | 1'), + array('SELECT toto WHERE toto.a ^ 1'), + array('SELECT toto WHERE toto.a << 1'), + array('SELECT toto WHERE toto.a >> 1'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'), + array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''), + array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\""'), + array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'), + array('SELECT toto WHERE toto.a NOT LIKE ""'), + array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'), + array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'), + array('SELECT Device JOIN Site ON Device.site = Site.id'), + array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''), + array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'), + array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'), + array('SELECT A FROM A'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'), + array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A UNION SELECT B'), + array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'), + array('SELECT A UNION SELECT B UNION SELECT C'), + array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT Person AS B WHERE B.name LIKE \'%A%\''), + array('SELECT Server WHERE name REGEXP \'dbserver[0-9]+\''), + array('SELECT Server WHERE name REGEXP \'^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$\''), + array('SELECT Change AS ch WHERE ch.start_date >= \'2009-12-31\' AND ch.end_date <= \'2010-01-01\''), + array('SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON(\'10.22.32.224\') AND INET_ATON(dev.managementip) < INET_ATON(\'10.22.32.255\')'), + array('SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1'), + array('SELECT PhysicalInterface AS if JOIN DatacenterDevice AS dev ON if.connectableci_id = dev.id WHERE dev.status = \'production\' AND dev.organization_name = \'Demo\''), + array('SELECT Ticket AS t WHERE t.agent_id = :current_contact_id'), + array('SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != \'closed\''), + array('SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)'), + array('SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = \'new\''), + array('SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)'), + array('SELECT Ticket WHERE tagfield MATCHES \'salad\''), + ); + } + + /** + * @dataProvider BadQueryProvider + * + * @param $sQuery + * @param $sExpectedExceptionClass + * + */ + public function testBadQueryParser($sQuery, $sExpectedExceptionClass) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $sExceptionClass = ''; + try + { + $oOql->ParseQuery(); + } + catch (\Exception $e) + { + $sExceptionClass = get_class($e); + } + + static::assertEquals($sExpectedExceptionClass, $sExceptionClass); + } + + public function BadQueryProvider() + { + return array( + array('SELECT toto WHERE toto.a = (3++1)', 'OQLParserException'), + array('SELECT toto WHHHERE toto.a = "1"', 'OQLParserException'), + array('SELECT toto WHERE toto.a == "1"', 'OQLParserException'), + array('SELECT toto WHERE toto.a % 1', 'Exception'), + array('SELECT toto WHERE toto.a like \'arg\'', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"', 'Exception'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'', 'Exception'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3', 'OQLParserException'), + ); + } + + /** + * @dataProvider TypeErrorQueryProvider + * + * @param $sQuery + * + * @expectedException \TypeError + * + * @throws \OQLException + */ + public function testTypeErrorQueryParser($sQuery) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oOql->ParseQuery(); + } + + public function TypeErrorQueryProvider() + { + return array( + array('SELECT A WHERE A.a MATCHES toto'), + ); + } + + + /** + * Needs actual datamodel + * + * @dataProvider QueryNormalizationProvider + * + * @param $sQuery + * @param $sExpectedExceptionClass + * + */ + public function testQueryNormalization($sQuery, $sExpectedExceptionClass) + { + $this->debug($sQuery); + $sExceptionClass = ''; + try + { + $oSearch = \DBObjectSearch::FromOQL($sQuery); + static::assertInstanceOf('DBObjectSearch', $oSearch); + } + catch (\Exception $e) + { + $sExceptionClass = get_class($e); + } + + static::assertEquals($sExpectedExceptionClass, $sExceptionClass); + } + + + public function QueryNormalizationProvider() + { + return array( + array('SELECT Contact', ''), + array('SELECT Contact WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE name = "foo"', ''), + array('SELECT Contact AS c WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE c.name = "foo"', ''), + array('SELECT Contact AS c WHERE Contact.name = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE x.name = "foo"', 'OqlNormalizeException'), + + array('SELECT Organization AS child JOIN Organization AS root ON child.parent_id BELOW root.id', ''), + array('SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id', ''), + + array('SELECT RelationProfessionnelle', 'UnknownClassOqlException'), + array('SELECT RelationProfessionnelle AS c WHERE name = "foo"', 'UnknownClassOqlException'), + + // The first query is the base query altered only in one place in the subsequent queries + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', ''), + array('SELECT Person AS p JOIN lnkXXXXXXXXXXXX AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', 'UnknownClassOqlException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.role = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.team_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id BELOW p.id WHERE p.name LIKE "bar"', ''), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.org_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), // inverted the JOIN spec + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE name LIKE "foo"', ''), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE x.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.eman LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE eman LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE id = 1', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', ''), + array('SELECT Person AS p JOIN Organization AS o ON p.location_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN Organization AS o ON p.name = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id JOIN Person AS p ON p.org_id = o.id', 'OqlNormalizeException'), + array('SELECT Person JOIN Organization AS o ON Person.org_id = o.id JOIN Person ON Person.org_id = o.id', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT Person AS p JOIN Location AS l ON p.location_id BELOW l.id', 'OqlNormalizeException'), + + array('SELECT Person FROM Person JOIN Location ON Person.location_id = Location.id', ''), + array('SELECT p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT p, l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), + array('SELECT p, foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), + + // Joins based on AttributeObjectKey + // + array('SELECT Attachment AS a JOIN UserRequest AS r ON a.item_id = r.id', ''), + array('SELECT UserRequest AS r JOIN Attachment AS a ON a.item_id = r.id', ''), + ); + } +} diff --git a/test/core/TagSetFieldDataTest.php b/test/core/TagSetFieldDataTest.php new file mode 100644 index 0000000000..14286a764c --- /dev/null +++ b/test/core/TagSetFieldDataTest.php @@ -0,0 +1,285 @@ +CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(1, $iCurrCount - $iInitialCount); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second'); + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(2, $iCurrCount - $iInitialCount); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag3', 'Third'); + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(3, $iCurrCount - $iInitialCount); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth'); + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(4, $iCurrCount - $iInitialCount); + } + + /** + * @throws \CoreException + */ + public function testDoCheckToWrite() + { + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iInitialCount = count($aAllowedValues); + $this->debug("Currently $iInitialCount tags defined"); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag3', 'Third'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth'); + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(4, $iCurrCount - $iInitialCount); + + try + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth'); + } catch (\CoreException $e) + { + $this->debug($e->getMessage()); + } + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(4, $iCurrCount - $iInitialCount); + + try + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'zembrek'); + } catch (\CoreException $e) + { + $this->debug($e->getMessage()); + } + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(4, $iCurrCount - $iInitialCount); + + try + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'zembrek', 'Fourth'); + } catch (\CoreException $e) + { + $this->debug($e->getMessage()); + } + $aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE); + $iCurrCount = count($aAllowedValues); + static::assertEquals(4, $iCurrCount - $iInitialCount); + } + + /** + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function testDoCheckToDelete() + { + $oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $oTagData->DBDelete(); + + // Create a tag + $oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + //Use it + $oTicket = $this->CreateTicket(1); + $oTicket->Set(TAG_ATTCODE, 'tag1'); + $oTicket->DBWrite(); + + // Try to delete the tag, must complain ! + try + { + $oTagData->DBDelete(); + } catch (\DeleteException $e) + { + static::assertTrue(true); + + return; + } + // Should not pass here + static::assertFalse(true); + } + + /** + * @throws \CoreException + * @throws \Exception + */ + public function testComputeValues() + { + $sTagClass = TagSetFieldData::GetTagDataClassName(TAG_CLASS, TAG_ATTCODE); + $oTagData = $this->createObject($sTagClass, array( + 'code' => 'tag1', + 'label' => 'First', + )); + $this->debug("Created {$oTagData->Get('obj_class')}::{$oTagData->Get('obj_attcode')}"); + + static::assertEquals(TAG_CLASS, $oTagData->Get('obj_class')); + static::assertEquals(TAG_ATTCODE, $oTagData->Get('obj_attcode')); + } + + /** + * Test invalid tag codes + * @dataProvider InvalidTagCodeProvider + * + * @expectedException \CoreException + * + * @param string $sTagCode + * + * @throws \CoreException + */ + public function testInvalidTagCode($sTagCode) + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, 'First'); + // Should not pass here + static::assertFalse(true); + } + + public function InvalidTagCodeProvider() + { + return array( + 'No space' => array('tag1 1'), + 'No _' => array('tag_1'), + 'No -' => array('tag-1'), + 'No %' => array('tag%1'), + 'At least 3 chars' => array(''), + 'At least 3 chars 1' => array('a'), + 'At least 3 chars 2' => array('ab'), + 'No #' => array('#tag'), + 'No !' => array('tag!'), + ); + } + + /** + * Test invalid tag labels + * @expectedException \CoreException + * @throws \CoreException + */ + public function testInvalidTagLabel() + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First|Second'); + // Should not pass here + static::assertFalse(true); + } + + /** + * Test that tag code cannot be modified if used + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function testUpdateCode() + { + $oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $oTagData->Set('code', 'tag2'); + $oTagData->DBWrite(); + + //Use it + $oTicket = $this->CreateTicket(1); + $oTicket->Set(TAG_ATTCODE, 'tag2'); + $oTicket->DBWrite(); + + // Try to change the code of the tag, must complain ! + try + { + $oTagData->Set('code', 'tag1'); + $oTagData->DBWrite(); + + } catch (\CoreException $e) + { + static::assertTrue(true); + + return; + } + // Should not pass here + static::assertFalse(true); + } + + /** + * Check that the code length is correctly checked + * + * @throws \CoreException + */ + public function testMaxTagCodeLength() + { + /** @var \AttributeTagSet $oAttdef */ + $oAttdef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE); + + $iMaxLength = $oAttdef->GetTagCodeMaxLength(); + $sTagCode = str_repeat('a', $iMaxLength); + + // Should work + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode); + + // Too long + $sTagCode = str_repeat('a', $iMaxLength + 1); + try + { + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode); + } catch (\CoreException $e) + { + $this->debug('Awaited: '.$e->getMessage()); + static::assertTrue(true); + return; + } + // Failed + static::assertFalse(true); + } + + public function testMaxTagsAllowed() + { + /** @var \AttributeTagSet $oAttDef */ + $oAttDef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE); + $iMaxTags = $oAttDef->GetMaxItems(); + for ($i = 0; $i < $iMaxTags; $i++) + { + $sTagCode = 'MaxTag'.$i; + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode); + } + $oTicket = $this->CreateTicket(1); + $this->debug("Max number of tags is $iMaxTags"); + $sValue = ''; + for ($i = 0; $i < ($iMaxTags + 1); $i++) + { + try + { + $sTagCode = 'MaxTag'.$i; + $sValue .= "$sTagCode "; + $oTicket->Set(TAG_ATTCODE, $sValue); + $oTicket->DBWrite(); + } catch (\Exception $e) + { + // Should fail on the last iteration + static::assertEquals($iMaxTags, $i); + $this->debug("Setting (".($i+1).") tag(s) failed"); + return; + } + $this->debug("Setting (".($i+1).") tag(s) worked"); + } + + static::assertFalse(true); + } +} diff --git a/test/core/ormTagSetTest.php b/test/core/ormTagSetTest.php new file mode 100644 index 0000000000..1b8f54a896 --- /dev/null +++ b/test/core/ormTagSetTest.php @@ -0,0 +1,304 @@ + + * + */ + +/** + * Created by PhpStorm. + * User: Eric + * Date: 27/08/2018 + * Time: 17:26 + */ + +namespace Combodo\iTop\Test\UnitTest\Core; + +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; +use Exception; +use ormTagSet; + +define('MAX_TAGS', 12); + +/** + * Tests of the ormTagSet class + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + * @backupGlobals disabled + */ +class ormTagSetTest extends ItopDataTestCase +{ + + /** + * @throws Exception + */ + protected function setUp() + { + parent::setUp(); + + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag3', 'Third'); + $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth'); + } + + public function testGetTagDataClass() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + static::assertEquals($oTagSet->GetTagDataClass(), 'TagSetFieldDataFor_Ticket__tagfield'); + } + + public function testGetValue() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + static::assertEquals($oTagSet->GetValues(), array()); + + $oTagSet->Add('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag1')); + + $oTagSet->Add('tag2'); + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2')); + } + + public function testAddTag() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + + $oTagSet->Add('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag1')); + + $oTagSet->SetValues(array('tag1', 'tag2')); + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2')); + + $oTagSet->Remove('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag2')); + + $oTagSet->Add('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2')); + } + + + /** + * @expectedException \CoreException + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function testMaxTagLimit() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, 3); + + $oTagSet->SetValues(array('tag1', 'tag2', 'tag3')); + + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2', 'tag3')); + + try + { + $oTagSet->SetValues(array('tag1', 'tag2', 'tag3', 'tag4')); + } + catch (\CoreException $e) + { + $this->debug('Awaited: '.$e->getMessage()); + throw $e; + } + } + + public function testEquals() + { + $oTagSet1 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet1->Add('tag1'); + static::assertTrue($oTagSet1->Equals($oTagSet1)); + + $oTagSet2 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet2->SetValues(array('tag1')); + + static::assertTrue($oTagSet1->Equals($oTagSet2)); + + $oTagSet1->Add('tag2'); + static::assertFalse($oTagSet1->Equals($oTagSet2)); + } + + public function testSetValue() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + + $oTagSet->SetValues(array('tag1')); + static::assertEquals($oTagSet->GetValues(), array('tag1')); + + $oTagSet->SetValues(array('tag1', 'tag2')); + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2')); + + } + + public function testRemoveTag() + { + $oTagSet = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet->Remove('tag_unknown'); + static::assertEquals($oTagSet->GetValues(), array()); + + $oTagSet->SetValues(array('tag1')); + $oTagSet->Remove('tag_unknown'); + static::assertEquals($oTagSet->GetValues(), array('tag1')); + + $oTagSet->SetValues(array('tag1', 'tag2')); + $oTagSet->Remove('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag2')); + + $oTagSet->Add('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag1', 'tag2')); + + $oTagSet->Remove('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag2')); + + $oTagSet->Remove('tag1'); + static::assertEquals($oTagSet->GetValues(), array('tag2')); + + $oTagSet->Remove('tag2'); + static::assertEquals($oTagSet->GetValues(), array()); + } + + public function testGetDelta() + { + $oTagSet1 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet1->SetValues(array('tag1', 'tag2')); + + $oTagSet2 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet2->SetValues(array('tag1', 'tag3', 'tag4')); + + $aDelta = $oTagSet1->GetDelta($oTagSet2); + static::assertCount(2, $aDelta); + static::assertCount(2, $aDelta['added']); + static::assertCount(1, $aDelta['removed']); + } + + public function testApplyDelta() + { + $oTagSet1 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet1->SetValues(array('tag1', 'tag2')); + + $oTagSet2 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet2->SetValues(array('tag1', 'tag3', 'tag4')); + + $aDelta = $oTagSet1->GetDelta($oTagSet2); + + $oTagSet1->ApplyDelta($aDelta); + + static::assertTrue($oTagSet1->Equals($oTagSet2)); + } + + /** + * @param $aInitialTags + * @param $aDiffTags + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * + * @dataProvider GetModifiedProvider + */ + public function testGetModified($aInitialTags, $aDiffAndExpectedTags) + { + $oTagSet1 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet1->SetValues($aInitialTags); + + foreach($aDiffAndExpectedTags as $aTestItem) + { + $oTagSet1->GenerateDiffFromArray($aTestItem['diff']); + static::assertEquals($aTestItem['modified'], $oTagSet1->GetModified()); + } + } + + public function GetModifiedProvider() + { + return array( + array( + array('tag2'), + array( + array('diff' => array('tag1', 'tag2'), 'modified' => array('tag1')), + array('diff' => array('tag2'), 'modified' => array('tag1')), + array('diff' => array(), 'modified' => array('tag1', 'tag2')), + ) + ), + array( + array('tag1', 'tag2'), + array( + array('diff' => array('tag1', 'tag3'), 'modified' => array('tag2', 'tag3')), + array('diff' => array('tag1', 'tag2'), 'modified' => array('tag2', 'tag3')), + array('diff' => array('tag1', 'tag2', 'tag3', 'tag4'), 'modified' => array('tag2', 'tag3', 'tag4')), + ) + ), + array( + array(), + array( + array('diff' => array('tag2'), 'modified' => array('tag2')), + array('diff' => array('tag1', 'tag2'), 'modified' => array('tag1', 'tag2')), + array('diff' => array('tag2'), 'modified' => array('tag1', 'tag2')), + ) + ), + ); + } + + /** + * @param $aInitialTags + * @param $aDelta + * @param $aExpectedTags + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + * @dataProvider BulkModifyProvider + */ + public function testBulkModify($aInitialTags, $aDelta, $aExpectedTags) + { + $oTagSet1 = new ormTagSet(TAG_CLASS, TAG_ATTCODE, MAX_TAGS); + $oTagSet1->SetValues($aInitialTags); + + $oTagSet1->ApplyDelta($aDelta); + + static::assertEquals($aExpectedTags, $oTagSet1->GetValues()); + } + + public function BulkModifyProvider() + { + return array( + 'Add one tag' => array( + array('tag1', 'tag2'), + array('added' => array('tag3')), + array('tag1', 'tag2', 'tag3') + ), + 'Remove one tag' => array( + array('tag1', 'tag2'), + array('removed' => array('tag2')), + array('tag1') + ), + 'Remove unexisting tag' => array( + array('tag1', 'tag2'), + array('removed' => array('tag3')), + array('tag1', 'tag2') + ), + 'Add one and remove one tag' => array( + array('tag1', 'tag2'), + array('added' => array('tag3'), 'removed' => array('tag2')), + array('tag1', 'tag3') + ), + 'Remove first tag' => array( + array('tag1', 'tag2'), + array('removed' => array('tag1')), + array('tag2') + ), + ); + } +} diff --git a/test/testlist.inc.php b/test/testlist.inc.php index a3bb15d2fe..88519843a0 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -123,328 +123,6 @@ class TestSQLQuery extends TestScenarioOnDB } } -class TestOQLParser extends TestFunction -{ - static public function GetName() {return 'Check OQL parsing';} - static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - $oOql = new OqlInterpreter($sQuery); - try - { - $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery - self::DumpVariable($oTrash); - } - catch (OQLException $OqlException) - { - if ($bIsCorrectQuery) - { - echo "

    More info on this unexpected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; - throw $OqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

    More info on this expected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; - return true; - } - } - catch (Exception $e) - { - if ($bIsCorrectQuery) - { - echo "

    More info on this unexpected failure:
    ".htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8')."

    \n"; - throw $OqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

    More info on this expected failure:
    ".htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8')."

    \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if ($bIsCorrectQuery) - { - return true; - } - else - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - } - - protected function TestQuery($sQuery, $bIsCorrectQuery) - { - if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) - { - return false; - } - return true; - } - - public function DoExecute() - { - $aQueries = array( - 'SELECT toto' => true, - 'SELECT toto WHERE toto.a = 1' => true, - 'SELECT toto WHERE toto.a = -1' => true, - 'SELECT toto WHERE toto.a = (1-1)' => true, - 'SELECT toto WHERE toto.a = (-1+3)' => true, - 'SELECT toto WHERE toto.a = (3+-1)' => true, - 'SELECT toto WHERE toto.a = (3--1)' => true, - 'SELECT toto WHERE toto.a = (3++1)' => false, - 'SELECT toto WHERE toto.a = 0xC' => true, - 'SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\'' => true, - 'SELECT toto WHERE toto.a = :myparameter' => true, - 'SELECT toto WHERE toto.a IN (:param1)' => true, - 'SELECT toto WHERE toto.a IN (:param1, :param2)' => true, - 'SELECT toto WHERE toto.a=1' => true, - 'SELECT toto WHERE toto.a = "1"' => true, - 'SELECT toto WHHHERE toto.a = "1"' => false, - 'SELECT toto WHERE toto.a == "1"' => false, - 'SELECT toto WHERE toto.a % 1' => false, - 'SELECT toto WHERE toto.a & 1' => true, // bitwise and - 'SELECT toto WHERE toto.a | 1' => true, // bitwise or - 'SELECT toto WHERE toto.a ^ 1' => true, // bitwise xor - 'SELECT toto WHERE toto.a << 1' => true, // bitwise left shift - 'SELECT toto WHERE toto.a >> 1' => true, // bitwise right shift - //'SELECT toto WHERE toto.a LIKE 1' => false, - 'SELECT toto WHERE toto.a like \'arg\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s it"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s "it""' => false, - 'SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That"s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\'' => true, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"' => false, - 'SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"' => true, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'' => false, - 'SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\'' => true, - - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\\\""' => true, - 'SELECT toto WHERE toto.a NOT LIKE ""' => true, - 'SELECT toto WHERE toto.a NOT LIKE "\\\\"' => true, - "SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = 'lnkContactRealObject' AND UserRightsMatrixClassGrant.action = 'modify' AND UserRightsMatrixClassGrant.login = 'Denis'" => true, - "SELECT A WHERE A.col1 = 'lit1' AND A.col2 = 'lit2' AND A.col3 = 'lit3'" => true, - - 'SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"' => true, - - //'SELECT toto WHERE toto.a > \'asd\'' => false, - 'SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id' => true, - 'SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, - - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = 'aa') OR (A.col3 = 'zzz' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = '' AND B.col4 > 100)" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true, - "SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2" => true, - - 'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true, - - // Several objects in a row... - // - 'SELECT A FROM A' => true, - 'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => true, - 'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true, - 'SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2' => true, - 'SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3' => false, - - // Unions - // - 'SELECT A UNION SELECT B' => true, - 'SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"' => true, - 'SELECT A UNION SELECT B UNION SELECT C' => true, - 'SELECT A UNION SELECT B UNION SELECT C UNION SELECT D' => true, - 'SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id' => true, - ); - - $iErrors = 0; - - foreach($aQueries as $sQuery => $bIsCorrectQuery) - { - $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; - echo "

    Testing query: $sQuery ($sIsOk)

    \n"; - try - { - $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); - } - catch(Exception $e) - { - $this->m_aErrors[] = $e->getMessage(); - $bRet = false; - } - if (!$bRet) $iErrors++; - } - - return ($iErrors == 0); - } -} - -class TestOQLNormalization extends TestBizModel -{ - static public function GetName() {return 'Check OQL normalization';} - static public function GetDescription() {return 'Attempts a series of queries, and in particular those with unknown or inconsistent class/attributes. Assumes a very standard installation!';} - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - try - { - $oSearch = DBObjectSearch::FromOQL($sQuery); - self::DumpVariable($sQuery); - } - catch (OQLNormalizeException $OqlException) - { - if ($bIsCorrectQuery) - { - echo "

    More info on this unexpected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; - throw $OqlException; - return false; - } - else - { - // Everything is fine :-) - echo "

    More info on this expected failure:
    ".$OqlException->getHtmlDesc()."

    \n"; - return true; - } - } - catch (Exception $e) - { - if ($bIsCorrectQuery) - { - echo "

    More info on this unexpected failure:
    ".htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8')."

    \n"; - throw $e; - return false; - } - else - { - // Everything is fine :-) - echo "

    More info on this expected failure:
    ".htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8')."

    \n"; - return true; - } - } - // The query was correctly parsed, was it expected to be correct ? - if ($bIsCorrectQuery) - { - return true; - } - else - { - throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)"); - return false; - } - } - - protected function TestQuery($sQuery, $bIsCorrectQuery) - { - if (!$this->CheckQuery($sQuery, $bIsCorrectQuery)) - { - return false; - } - return true; - } - - public function DoExecute() - { - $aQueries = array( - 'SELECT Contact' => true, - 'SELECT Contact WHERE nom_de_famille = "foo"' => false, - 'SELECT Contact AS c WHERE name = "foo"' => true, - 'SELECT Contact AS c WHERE nom_de_famille = "foo"' => false, - 'SELECT Contact AS c WHERE c.name = "foo"' => true, - 'SELECT Contact AS c WHERE Contact.name = "foo"' => false, - 'SELECT Contact AS c WHERE x.name = "foo"' => false, - - 'SELECT Organization AS child JOIN Organization AS root ON child.parent_id BELOW root.id' => true, - 'SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id' => true, - - 'SELECT RelationProfessionnelle' => false, - 'SELECT RelationProfessionnelle AS c WHERE name = "foo"' => false, - - // The first query is the base query altered only in one place in the subsequent queries - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"' => true, - 'SELECT Person AS p JOIN lnkXXXXXXXXXXXX AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.person_id = p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON person_id = p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.role = p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.team_id = p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id BELOW p.id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.org_id WHERE p.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"' => false, // inverted the JOIN spec - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE name LIKE "foo"' => true, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE x.name LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.eman LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE eman LIKE "foo"' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE id = 1' => false, - 'SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"' => false, - - 'SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"' => true, - 'SELECT Person AS p JOIN Organization AS o ON p.location_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"' => false, - 'SELECT Person AS p JOIN Organization AS o ON p.name = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"' => false, - - 'SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id JOIN Person AS p ON p.org_id = o.id' => false, - 'SELECT Person JOIN Organization AS o ON Person.org_id = o.id JOIN Person ON Person.org_id = o.id' => false, - - 'SELECT Person AS p JOIN Location AS l ON p.location_id = l.id' => true, - 'SELECT Person AS p JOIN Location AS l ON p.location_id BELOW l.id' => false, - - 'SELECT Person FROM Person JOIN Location ON Person.location_id = Location.id' => true, - 'SELECT p FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => true, - 'SELECT l FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => true, - 'SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => true, - 'SELECT p, l FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => true, - 'SELECT foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => false, - 'SELECT p, foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id' => false, - - // Joins based on AttributeObjectKey - // - 'SELECT Attachment AS a JOIN UserRequest AS r ON a.item_id = r.id' => true, - 'SELECT UserRequest AS r JOIN Attachment AS a ON a.item_id = r.id' => true, - ); - - $iErrors = 0; - - foreach($aQueries as $sQuery => $bIsCorrectQuery) - { - $sIsOk = $bIsCorrectQuery ? 'good' : 'bad'; - echo "

    Testing query: $sQuery ($sIsOk)

    \n"; - try - { - $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); - } - catch(Exception $e) - { - $this->m_aErrors[] = $e->getMessage(); - $bRet = false; - } - if (!$bRet) $iErrors++; - } - - return ($iErrors == 0); - } -} - class TestCSVParser extends TestFunction { static public function GetName() {return 'Check CSV parsing';}