value)
*
* @param array $aRow
* @param string $sClassAlias
* @param array $aAttToLoad
* @param array $aExtendedDataSpec
*
* @throws \CoreException
*/
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
{
parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
$this->sDisplayMode = static::DEFAULT_DISPLAY_MODE;
$this->bAllowWrite = false;
$this->bAllowDelete = false;
}
/**
* Return the allowed display modes
*
* @see static::ENUM_DISPLAY_MODE_XXX
*
* @return string[]
* @since 3.0.0
*/
public static function EnumDisplayModes(): array
{
return [
static::ENUM_DISPLAY_MODE_VIEW,
static::ENUM_DISPLAY_MODE_EDIT,
static::ENUM_DISPLAY_MODE_CREATE,
static::ENUM_DISPLAY_MODE_STIMULUS,
static::ENUM_DISPLAY_MODE_PRINT,
static::ENUM_DISPLAY_MODE_BULK_EDIT,
];
}
/**
* @see static::$sDisplayMode
* @return string
* @since 3.0.0
*/
public function GetDisplayMode(): string
{
return $this->sDisplayMode;
}
/**
* @param string $sMode
*
* @see static::$sDisplayMode
* @return $this
* @since 3.0.0
*/
public function SetDisplayMode(string $sMode)
{
$this->sDisplayMode = $sMode;
return $this;
}
/**
* returns what will be the next ID for the forms
*/
public static function GetNextFormId()
{
return 1 + self::$iGlobalFormId;
}
public static function GetUIPage()
{
return 'UI.php';
}
/**
* @param \WebPage $oPage
* @param \cmdbAbstractObject $oObj
* @param array $aParams
*
* @throws \Exception
*/
public static function ReloadAndDisplay($oPage, $oObj, $aParams)
{
$oAppContext = new ApplicationContext();
// Reload the page to let the "calling" page execute its 'onunload' method.
// Note 1: The redirection MUST NOT be made via an HTTP "header" since onunload is only called when the actual content of the DOM
// is replaced by some other content. So the "bouncing" page must provide some content (in our case a script making the redirection).
// Note 2: make sure that the URL below is different from the one of the "Modify" button, otherwise the button will have no effect. This is why we add "&a=1" at the end !!!
// Note 3: we use the toggle of a flag in the sessionStorage object to prevent an infinite loop of reloads in case the object is actually locked by another window
$sSessionStorageKey = get_class($oObj).'_'.$oObj->GetKey();
$sParams = '';
foreach($aParams as $sName => $value)
{
$sParams .= $sName.'='.urlencode($value).'&'; // Always add a trailing &
}
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1';
$oPage->add_early_script(<<Reload();
$oObj->SetDisplayMode(static::ENUM_DISPLAY_MODE_VIEW);
$oObj->DisplayDetails($oPage, false);
}
/**
* @param $sMessageId
* @param $sMessage
* @param $sSeverity
* @param $fRank
* @param bool $bMustNotExist
*
* @see SetSessionMessage()
* @since 2.6.0
*/
protected function SetSessionMessageFromInstance($sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
$sObjectClass = get_class($this);
$iObjectId = $this->GetKey();
self::SetSessionMessage($sObjectClass, $iObjectId, $sMessageId, $sMessage, $sSeverity, $fRank);
}
/**
* Set a message displayed to the end-user next time this object will be displayed
* Messages are uniquely identified so that plugins can override standard messages (the final work is given to the
* last plugin to set the message for a given message id) In practice, standard messages are recorded at the end
* but they will not overwrite existing messages
*
* @see SetSessionMessageFromInstance() to call from within an instance
*
* @param string $sClass The class of the object (must be the final class)
* @param int $iKey The identifier of the object
* @param string $sMessageId Your id or one of the well-known ids: 'create', 'update' and 'apply_stimulus'
* @param string $sMessage The HTML message (must be correctly escaped)
* @param string $sSeverity Any of the \WebPage::ENUM_SESSION_MESSAGE_SEVERITY_XXX constants
* @param float $fRank Ordering of the message: smallest displayed first (can be negative)
* @param bool $bMustNotExist Do not alter any existing message (considering the id)
*
* @return void
*/
public static function SetSessionMessage($sClass, $iKey, $sMessageId, $sMessage, $sSeverity, $fRank, $bMustNotExist = false)
{
$sMessageKey = $sClass.'::'.$iKey;
if (!Session::IsSet(['obj_messages', $sMessageKey])) {
Session::Set(['obj_messages', $sMessageKey], []);
}
if (!$bMustNotExist || !Session::IsSet(['obj_messages', $sMessageKey, $sMessageId])) {
Session::Set(['obj_messages', $sMessageKey, $sMessageId], [
'rank' => $fRank,
'severity' => $sSeverity,
'message' => $sMessage,
]);
}
}
/**
* @param \WebPage $oPage Warning, since 3.0.0 this parameter was kept for compatibility reason. You shouldn't write directly on the page!
* When writing to the page, markup will be put above the real header of the panel.
* To insert something IN the panel, we now need to add UIBlocks in either the "subtitle" or "toolbar" sections of the array that will be returned.
* @param bool $bEditMode Deprecated parameter in iTop 3.0.0, use {@see GetDisplayMode()} and ENUM_DISPLAY_MODE_* constants instead
*
* @return array{
* subtitle: \Combodo\iTop\Application\UI\Base\UIBlock[],
* toolbar: \Combodo\iTop\Application\UI\Base\UIBlock[]
* }
* blocks to be inserted in the "subtitle" and the "toolbar" sections of the ObjectDetails block.
* eg. ['subtitle' => [, ], 'toolbar' => []]
*
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*
* @since 3.0.0 $bEditMode is deprecated, see param documentation above
* @since 3.0.0 Changed signature: Method must return header content in an array (no more writing directly to the $oPage)
*
* @noinspection PhpUnusedParameterInspection
*/
public function DisplayBareHeader(WebPage $oPage, $bEditMode = false)
{
$aHeaderBlocks = [
'subtitle' => [],
'toolbar' => [],
];
// Standard Header with name, actions menu and history block
if (!$oPage->IsPrintableVersion()) {
// Is there a message for this object ??
$aMessages = [];
$aRanks = [];
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($this), $this->GetKey());
if ($aLockInfo['locked']) {
$aRanks[] = 0;
$sName = $aLockInfo['owner']->GetName();
if ($aLockInfo['owner']->Get('contactid') != 0) {
$sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')';
}
$aMessages[] = AlertUIBlockFactory::MakeForDanger('', Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName));
}
}
$sMessageKey = get_class($this).'::'.$this->GetKey();
$oPage->AddSessionMessages($sMessageKey, $aRanks, $aMessages);
}
if (!$oPage->IsPrintableVersion() && ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_VIEW)) {
// action menu
$oSingletonFilter = new DBObjectSearch(get_class($this));
$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
$oBlock = new MenuBlock($oSingletonFilter, 'details', false);
$sActionMenuId = utils::Sanitize(uniqid('', true), '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
$oActionMenuBlock = $oBlock->GetRenderContent($oPage, [], $sActionMenuId);
$aHeaderBlocks['toolbar'][$oActionMenuBlock->GetId()] = $oActionMenuBlock;
}
$aTags = array();
// Master data sources
if (!$oPage->IsPrintableVersion()) {
$oCreatorTask = null;
$bCanBeDeletedByTask = false;
$bCanBeDeletedByUser = true;
$aMasterSources = array();
$aSyncData = $this->GetSynchroData();
if (count($aSyncData) > 0) {
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))
{
$sLink = "".$oDataSource->GetName()."";
}
if ($oReplica->Get('status_dest_creator') == 1)
{
$oCreatorTask = $oDataSource;
$bCreatedByTask = true;
}
else
{
$bCreatedByTask = false;
}
if ($bCreatedByTask)
{
$sDeletePolicy = $oDataSource->Get('delete_policy');
if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
{
$bCanBeDeletedByTask = true;
}
$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
if ($sUserDeletePolicy == 'nobody')
{
$bCanBeDeletedByUser = false;
}
elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator())
{
$bCanBeDeletedByUser = false;
}
}
$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 = "
';
$sComments = $sSynchroIcon;
}
// Attribute is read-only
$sHTMLValue = "".$this->GetAsHTML($sAttCode).'';
} else {
$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,
true, $sInputType
).'';
}
$aFieldsMap[$sAttCode] = $sInputId;
// Attribute description
$sDescription = $oAttDef->GetDescription();
$sDescriptionForHTMLTag = utils::HtmlEntities($sDescription);
$sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag) || $sDescription === $oAttDef->GetLabel()) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.'" data-tooltip-max-width="600px"';
$val = array(
'label' => ''.$oAttDef->GetLabel().'',
'value' => $sHTMLValue,
'input_id' => $sInputId,
'input_type' => $sInputType,
'comments' => $sComments,
'infos' => $sInfos,
);
}
}
else
{
// Attribute description
$sDescription = $oAttDef->GetDescription();
$sDescriptionForHTMLTag = utils::HtmlEntities($sDescription);
$sDescriptionHTMLTag = (empty($sDescriptionForHTMLTag) || $sDescription === $oAttDef->GetLabel()) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLTag.' "data-tooltip-max-width="600px"';
$val = array(
'label' => ''.$oAttDef->GetLabel().'',
'value' => "".$this->GetAsHTML($sAttCode)."",
'comments' => $sComments,
'infos' => $sInfos,
);
$aFieldsMap[$sAttCode] = $sInputId;
}
}
else
{
$val = null; // Skip this field
}
}
else
{
// !bEditMode
$val = $this->GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode);
}
if ($val != null)
{
// Add extra data for markup generation
// - Attribute code and AttributeDef. class
$val['attcode'] = $sAttCode;
$val['atttype'] = $sAttDefClass;
$val['attlabel'] = $sAttLabel;
$val['attflags'] = ($bEditMode) ? $this->GetFormAttributeFlags($sAttCode) : OPT_ATT_READONLY;
// - How the field should be rendered
$val['layout'] =
(in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField()))
? Field::ENUM_FIELD_LAYOUT_LARGE
: Field::ENUM_FIELD_LAYOUT_SMALL;
// - For simple fields, we get the raw (stored) value as well
$bExcludeRawValue = false;
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude) {
if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) {
$bExcludeRawValue = true;
break;
}
}
$val['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
// The field is visible, add it to the current column
$oField = FieldUIBlockFactory::MakeFromParams($val);
if ($sFieldsetName[0] != '_') {
$oFieldSet->AddSubBlock($oField);
} else {
$oColumn->AddSubBlock($oField);
}
}
}
}
}
}
// Fields with CKEditor need to have the highlight.js lib loaded even if they are in read-only, as it is needed to format code snippets
if ($bHasFieldsWithRichTextEditor) {
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
}
return $aFieldsMap;
}
/**
* @param \WebPage $oPage
* @param bool $bEditMode Note that this parameter is no longer used in this method, {@see static::$sDisplayMode} is used instead, but we cannot remove it as it part of the base interface (iDisplay)...
*
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*
* @since 3.0.0 $bEditMode is deprecated and no longer used
*/
public function DisplayDetails(WebPage $oPage, $bEditMode = false)
{
// N°3786: As this can now be call recursively from the self::ReloadAndDisplay(), we need to make sure we don't fall into an infinite loop
static $bBlockReentrance = false;
$sClass = get_class($this);
$iKey = $this->GetKey();
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_VIEW) {
// The concurrent access lock makes sense only for already existing objects
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($LockEnabled) {
$aLockInfo = iTopOwnershipLock::IsLocked($sClass, $iKey);
if ($aLockInfo['locked'] === true && $aLockInfo['owner']->GetKey() == UserRights::GetUserId() && $bBlockReentrance === false) {
// If the object is locked by the current user, it's worth trying again, since
// the lock may be released by 'onunload' which is called AFTER loading the current page.
//$bTryAgain = $oOwner->GetKey() == UserRights::GetUserId();
$bBlockReentrance = true;
self::ReloadAndDisplay($oPage, $this, array('operation' => 'details'));
return;
}
}
}
// Object's details
$oObjectDetails = ObjectFactory::MakeDetails($this, $this->GetDisplayMode());
if ($oPage->IsPrintableVersion()) {
$oObjectDetails->SetIsHeaderVisibleOnScroll(false);
}
// Note: DisplayBareHeader is called before adding $oObjectDetails to the page, so it can inject HTML before it through $oPage.
/** @var \iTopWebPage $oPage */
$aHeadersBlocks = $this->DisplayBareHeader($oPage, $bEditMode);
if (false === empty($aHeadersBlocks['subtitle'])) {
$oObjectDetails->AddSubTitleBlocks($aHeadersBlocks['subtitle']);
}
if (false === empty($aHeadersBlocks['toolbar'])) {
$oObjectDetails->AddToolbarBlocks($aHeadersBlocks['toolbar']);
}
$oPage->AddUiBlock($oObjectDetails);
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, '', $oObjectDetails);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab('UI:PropertiesTab');
$this->DisplayBareProperties($oPage, $bEditMode);
$this->DisplayBareRelations($oPage, $bEditMode);
// Note: Adding the JS snippet which enables the image upload should have been done directly by the ActivityPanel which would have kept the independance principle
// of the UIBlock. For now we keep it this way in order to move on and trace this known limitation in N°3736.
/** @var ActivityPanel $oActivityPanel */
$oActivityPanel = $oPage->GetContentLayout()->GetSubBlock(ActivityPanel::BLOCK_CODE);
// Note: Testing if block exists is necessary as during the 'release_lock_and_details' operation we don't have an activity panel
if (!is_null($oActivityPanel) && $oActivityPanel->HasTransactionId()) {
$iTransactionId = $oActivityPanel->GetTransactionId();
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
}
}
/**
* @param \WebPage $oPage
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function DisplayPreview(WebPage $oPage)
{
$aDetails = array();
$sClass = get_class($this);
$aList = MetaModel::GetZListItems($sClass, 'preview');
foreach($aList as $sAttCode)
{
$aDetails[] = array(
'label' => MetaModel::GetLabel($sClass, $sAttCode),
'value' => $this->GetAsHTML($sAttCode),
);
}
$oPage->details($aDetails);
}
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aExtraParams
*
* @throws \ApplicationException
* @throws \CoreException
*/
public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
$oPage->AddUiBlock(self::GetDisplaySetBlock($oPage, $oSet, $aExtraParams));
}
/**
* Simplified 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 array $aExtraParams
*
* @return string The HTML representation of the table
* @throws \CoreException
*/
public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : utils::GetUniqueId();;
$aExtraParams['view_link'] = true;
$aExtraParams['select_mode'] = 'none';
return DataTableUIBlockFactory::MakeForObject($oPage, $sTableId, $oSet, $aExtraParams);
}
/**
* Get the HTML fragment corresponding to the display of a table representing a set of objects
*
* @param WebPage $oPage The page object is used for out-of-band information (mostly scripts) output
* @param \DBObjectSet $oSet The set of objects to display
* @param array $aExtraParams key used :
*
*
view_link : if true then for extkey will display links with friendly name and make column sortable, default true
*
menu : if true prints DisplayBlock menu, default true
*
display_aliases : list of query aliases that will be printed, defaults to [] (displays all)
*
zlist : name of the zlist to use, false to disable zlist lookup, default to 'list'
*
extra_fields : list of . to add to the result, separator ',', defaults to empty string
*
*
* @return String The HTML fragment representing the table of objects. Warning : no JS added to handled
* pagination or table sorting !
*
* @see DisplayBlock to get a similar table but with the JS for pagination & sorting
*
* @deprecated 3.0.0 use GetDisplaySetBlock
*/
public static function GetDisplaySet(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('use GetDisplaySetBlock');
$oPage->AddUiBlock(static::GetDisplaySetBlock($oPage, $oSet, $aExtraParams));
return "";
}
/**
* @param \WebPage $oPage
* @param \DBObjectSet $oSet
* @param array $aExtraParams
*
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock|string
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \ReflectionException
*
* @since 3.0.0
*/
public static function GetDisplaySetBlock(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
{
if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) {
return self::GetDisplaySetForPrinting($oPage, $oSet, $aExtraParams);
}
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else {
$iListId = $aExtraParams['currentId'];
}
return DataTableUIBlockFactory::MakeForResult($oPage, $iListId, $oSet, $aExtraParams);
}
public static function GetDataTableFromDBObjectSet(DBObjectSet $oSet, $aParams = array())
{
$aFields = null;
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0)) {
$aFields = explode(',', $aParams['fields']);
}
$bFieldsAdvanced = false;
if (isset($aParams['fields_advanced'])) {
$bFieldsAdvanced = (bool)$aParams['fields_advanced'];
}
$bLocalize = true;
if (isset($aParams['localize_values'])) {
$bLocalize = (bool)$aParams['localize_values'];
}
$aList = array();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = array();
foreach ($aClasses as $sAlias => $sClassName) {
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) {
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aHeader = array();
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
$aList[$sAlias] = array();
foreach (MetaModel::GetZListItems($sClassName, 'list') as $sAttCode) {
$oAttDef = Metamodel::GetAttributeDef($sClassName, $sAttCode);
if (is_null($aFields) || (count($aFields) == 0)) {
// Standard list of attributes (no link sets)
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();
foreach (MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) {
$aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass,
$sRemoteAttCode);
}
}
}
} else {
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields)) {
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
// Replace external key by the corresponding friendly name (if not already in the list)
foreach ($aList[$sAlias] as $sAttCode => $oAttDef) {
if ($oAttDef->IsExternalKey()) {
unset($aList[$sAlias][$sAttCode]);
$sFriendlyNameAttCode = $sAttCode.'_friendlyname';
if (!array_key_exists($sFriendlyNameAttCode,
$aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) {
$oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
$aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
}
}
}
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
$sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx;
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
$aHeader[$oAttDef->GetCode().'/D'] = ['label' => $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')'];
$aHeader[$oAttDef->GetCode().'/T'] = ['label' => $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')'];
} else {
$aHeader[$oAttDef->GetCode()] = ['label' => $sColLabel];
}
}
}
$oSet->Seek(0);
$aRows = [];
while ($aObjects = $oSet->FetchAssoc()) {
$aRow = [];
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
$oObj = $aObjects[$sAlias];
foreach ($aList[$sAlias] as $sAttCodeEx => $oAttDef) {
if (is_null($oObj)) {
$aRow[$oAttDef->GetCode()] = '';
} else {
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime') {
$sDate = $oObj->Get($sAttCodeEx);
if ($sDate === null) {
$aRow[$oAttDef->GetCode().'/D'] = '';
$aRow[$oAttDef->GetCode().'/T'] = '';
} else {
$iDate = AttributeDateTime::GetAsUnixSeconds($sDate);
$aRow[$oAttDef->GetCode().'/D'] = date('Y-m-d', $iDate); // Format kept as-is for 100% backward compatibility of the exports
$aRow[$oAttDef->GetCode().'/T'] = date('H:i:s', $iDate); // Format kept as-is for 100% backward compatibility of the exports
}
} else {
if ($oAttDef instanceof AttributeCaseLog) {
$rawValue = $oObj->Get($sAttCodeEx);
$outputValue = str_replace("\n", " ", utils::EscapeHtml($rawValue->__toString()));
// Trick for Excel: treat the content as text even if it begins with an equal sign
$aRow[$oAttDef->GetCode()] = $outputValue;
} 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()) {
$sKeyAttCode = $oAttDef->GetKeyAttCode();
if ($oObj->Get($sKeyAttCode) == 0) {
$rawValue = '';
}
}
if ($bLocalize) {
$outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue));
} else {
$outputValue = utils::EscapeHtml($rawValue);
}
$aRow[$oAttDef->GetCode()] = $outputValue;
}
}
}
}
}
$aRows[] = $aRow;
}
$oTable = new StaticTable();
$oTable->SetColumns($aHeader);
$oTable->SetData($aRows);
return $oTable;
//DataTableUIBlockFactory::MakeForStaticData('', $aHeader, $aRows);
}
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aExtraParams key used :
*
*
view_link : if true then for extkey will display links with friendly name and make column sortable, default true
*
menu : if true prints DisplayBlock menu, default true
*
display_aliases : list of query aliases that will be printed, defaults to [] (displays all)
*
zlist : name of the zlist to use, false to disable zlist lookup, default to 'list'
*
extra_fields : list of . to add to the result, separator ',', defaults to empty string
*
*
* @return string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @deprecated 3.0.0
*/
public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else {
$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();
$sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list';
$aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',',
trim($aExtraParams['extra_fields'])) : array();
$aExtraFields = array();
$sAttCode = '';
foreach($aExtraFieldsRaw as $sFieldName)
{
// Ignore attributes not of the main queried class
if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches))
{
$sClassAlias = $aMatches[1];
$sAttCode = $aMatches[2];
if (array_key_exists($sClassAlias, $oSet->GetSelectedClasses()))
{
$aExtraFields[$sClassAlias][] = $sAttCode;
}
}
else
{
$aExtraFields['*'] = $sAttCode;
}
}
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$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))))
{
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
if (array_key_exists($sAlias, $aExtraFields))
{
$aList[$sAlias] = $aExtraFields[$sAlias];
}
else
{
$aList[$sAlias] = array();
}
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)
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
if ($oAttDef instanceof AttributeLinkedSet)
{
// Removed from the display list
unset($aList[$sAlias][$index]);
}
}
if (empty($aList[$sAlias]))
{
unset($aList[$sAlias], $aAuthorizedClasses[$sAlias]);
}
}
$sSelectMode = 'none';
$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());
$oSettings->iDefaultPageSize = $iDefaultPageSize;
}
$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams);
}
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aParams
* @param string $sCharset
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8')
{
$oPage->add(self::GetSetAsCSV($oSet, $aParams, $sCharset));
}
/**
* @param \DBObjectSet $oSet
* @param array $aParams
* @param string $sCharset
*
* @return string
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
public static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8')
{
$sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma
$sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote
$aFields = null;
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0))
{
$aFields = explode(',', $aParams['fields']);
}
$bFieldsAdvanced = false;
if (isset($aParams['fields_advanced']))
{
$bFieldsAdvanced = (bool)$aParams['fields_advanced'];
}
$bLocalize = true;
if (isset($aParams['localize_values']))
{
$bLocalize = (bool)$aParams['localize_values'];
}
$aList = array();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = array();
foreach($aClasses as $sAlias => $sClassName)
{
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
{
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aHeader = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets)
if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField()))
{
$sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode;
if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
{
if ($bFieldsAdvanced)
{
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE))
{
$sRemoteClass = $oAttDef->GetTargetClass();
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
{
$aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass,
$sRemoteAttCode);
}
}
}
}
else
{
// Any other attribute
$aList[$sAlias][$sAttCodeEx] = $oAttDef;
}
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
if ($bFieldsAdvanced)
{
$aHeader[] = 'id';
}
foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx,
isset($aParams['showMandatoryFields'])) : $sAttCodeEx;
}
}
$sHtml = implode($sSeparator, $aHeader)."\n";
$oSet->Seek(0);
while ($aObjects = $oSet->FetchAssoc())
{
$aRow = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias];
if ($bFieldsAdvanced)
{
if (is_null($oObj))
{
$aRow[] = '';
}
else
{
$aRow[] = $oObj->GetKey();
}
}
foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef)
{
if (is_null($oObj))
{
$aRow[] = '';
}
else
{
$value = $oObj->Get($sAttCodeEx);
$sCSVValue = $oAttDef->GetAsCSV($value, $sSeparator, $sTextQualifier, $oObj, $bLocalize);
$aRow[] = iconv('UTF-8', $sCharset.'//IGNORE//TRANSLIT', $sCSVValue);
}
}
}
$sHtml .= implode($sSeparator, $aRow)."\n";
}
return $sHtml;
}
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
* @param array $aParams
*
* @throws \Exception
* only used in old and deprecated export.php
*
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
*/
public 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
*
* @param \DBObjectSet $oSet
* @param array $aParams
*
* @return string
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*
* @internal Only to be used by `/webservices/export.php` : this is a legacy method that produces wrong HTML (no TR on table body rows)
*/
public static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array())
{
$aFields = null;
if (isset($aParams['fields']) && (strlen($aParams['fields']) > 0))
{
$aFields = explode(',', $aParams['fields']);
}
$bFieldsAdvanced = false;
if (isset($aParams['fields_advanced']))
{
$bFieldsAdvanced = (bool)$aParams['fields_advanced'];
}
$bLocalize = true;
if (isset($aParams['localize_values']))
{
$bLocalize = (bool)$aParams['localize_values'];
}
$aList = array();
$aClasses = $oSet->GetFilter()->GetSelectedClasses();
$aAuthorizedClasses = array();
foreach($aClasses as $sAlias => $sClassName)
{
if (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO)
{
$aAuthorizedClasses[$sAlias] = $sClassName;
}
}
$aHeader = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$aList[$sAlias] = array();
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
if (is_null($aFields) || (count($aFields) == 0))
{
// Standard list of attributes (no link sets)
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();
foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode)
{
$aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass,
$sRemoteAttCode);
}
}
}
}
else
{
// User defined list of attributes
if (in_array($sAttCode, $aFields) || in_array($sAlias.'.'.$sAttCode, $aFields))
{
$aList[$sAlias][$sAttCode] = $oAttDef;
}
}
}
// Replace external key by the corresponding friendly name (if not already in the list)
foreach($aList[$sAlias] as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
unset($aList[$sAlias][$sAttCode]);
$sFriendlyNameAttCode = $sAttCode.'_friendlyname';
if (!array_key_exists($sFriendlyNameAttCode,
$aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode))
{
$oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode);
$aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt;
}
}
}
foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef)
{
$sColLabel = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx) : $sAttCodeEx;
$oFinalAttDef = $oAttDef->GetFinalAttDef();
if (get_class($oFinalAttDef) == 'AttributeDateTime')
{
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Date').')';
$aHeader[] = $sColLabel.' ('.Dict::S('UI:SplitDateTime-Time').')';
}
else
{
$aHeader[] = $sColLabel;
}
}
}
$sHtml = "
\n";
$sHtml .= "
\n";
$sHtml .= "
".implode("
", $aHeader)."
\n";
$sHtml .= "
\n";
$oSet->Seek(0);
while ($aObjects = $oSet->FetchAssoc())
{
$aRow = array();
foreach($aAuthorizedClasses as $sAlias => $sClassName)
{
$oObj = $aObjects[$sAlias];
foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef)
{
if (is_null($oObj))
{
$aRow[] = '
'; // Format kept as-is for 100% backward compatibility of the exports
$aRow[] = '
'.date('H:i:s',
$iDate).'
'; // Format kept as-is for 100% backward compatibility of the exports
}
}
else
{
if ($oAttDef instanceof AttributeCaseLog)
{
$rawValue = $oObj->Get($sAttCodeEx);
$outputValue = str_replace("\n", " ",
utils::EscapeHtml($rawValue->__toString()));
// Trick for Excel: treat the content as text even if it begins with an equal sign
$aRow[] = '
'.$outputValue.'
';
}
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())
{
$sKeyAttCode = $oAttDef->GetKeyAttCode();
if ($oObj->Get($sKeyAttCode) == 0)
{
$rawValue = '';
}
}
if ($bLocalize) {
$outputValue = utils::EscapeHtml($oFinalAttDef->GetEditValue($rawValue));
}
else {
$outputValue = utils::EscapeHtml($rawValue);
}
$aRow[] = '
{$sValidationSpan}{$sReloadSpan}$sHidden";
// Note: This should be refactored for all types of attribute (see at the end of this function) but as we are doing this for a maintenance release, we are scheduling it for the next main release in to order to avoid regressions as much as possible.
$sNullValue = $oAttDef->GetNullValue();
if (!is_numeric($sNullValue)) {
$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->GetModifiedEntry('html')) : 'undefined';
$oPage->add_ready_script("$('#$iId').on('keyup change validate', function(evt, sFormId) { return ValidateCaseLogField('$iId', $bMandatory, sFormId, $sNullValue, $sOriginalValue) } );"); // Custom validation function
// Replace the text area with CKEditor
// To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor()
$aConfig = utils::GetCkeditorPref();
$aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere');
// - Final config
$sConfigJS = json_encode($aConfig);
WebResourcesHelper::EnableCKEditorToWebPage($oPage);
$oPage->add_ready_script("$('#$iId').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit
$oPage->add_ready_script(
<<GetEditValue($value);
$oWidget = new UIHTMLEditorWidget($iId, $oAttDef, $sNameSuffix, $sFieldPrefix, $sHelpText,
$sValidationSpan.$sReloadSpan, $sEditValue, $bMandatory);
$sHTMLValue = $oWidget->Display($oPage, $aArgs);
break;
case 'LinkedSet':
$sInputType = self::ENUM_INPUT_TYPE_LINKEDSET;
if ($oAttDef->IsIndirect()) {
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix,
$oAttDef->DuplicatesAllowed());
} else {
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix);
}
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
$oObj = isset($aArgs['this']) ? $aArgs['this'] : null;
$sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj);
break;
case 'Document':
$sInputType = self::ENUM_INPUT_TYPE_DOCUMENT;
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
$oDocument = $value; // Value is an ormDocument object
$sFileName = '';
if (is_object($oDocument)) {
$sFileName = $oDocument->GetFileName();
}
$sFileNameForHtml = utils::EscapeHtml($sFileName);
$bHasFile = !empty($sFileName);
$iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
$sRemoveBtnLabelForHtml = utils::EscapeHtml(Dict::S('UI:Button:RemoveDocument'));
$sExtraCSSClassesForRemoveButton = $bHasFile ? '' : 'ibo-is-hidden';
$sHTMLValue = <<
{$sFileNameForHtml}
{$sValidationSpan}{$sReloadSpan}
HTML;
if ($sFileName == '') {
$oPage->add_ready_script("$('#remove_attr_{$iId}').addClass('ibo-is-hidden');");
}
break;
case 'Image':
$sInputType = self::ENUM_INPUT_TYPE_IMAGE;
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js');
$oDocument = $value; // Value is an ormDocument objectm
$sDefaultUrl = $oAttDef->Get('default_image');
if (is_object($oDocument) && !$oDocument->IsEmpty()) {
$sUrl = 'data:'.$oDocument->GetMimeType().';base64,'.base64_encode($oDocument->GetData());
} else {
$sUrl = null;
}
$sHTMLValue = "
\n";
$sHTMLValue .= "{$sValidationSpan}{$sReloadSpan}\n";
$aEditImage = array(
'input_name' => 'attr_'.$sFieldPrefix.$sAttCode.$sNameSuffix,
'max_file_size' => utils::ConvertToBytes(ini_get('upload_max_filesize')),
'max_width_px' => $oAttDef->Get('display_max_width'),
'max_height_px' => $oAttDef->Get('display_max_height'),
'current_image_url' => $sUrl,
'default_image_url' => $sDefaultUrl,
'labels' => array(
'reset_button' => utils::EscapeHtml(Dict::S('UI:Button:ResetImage')),
'remove_button' => utils::EscapeHtml(Dict::S('UI:Button:RemoveImage')),
'upload_button' => !empty($sHelpText) ? $sHelpText : utils::EscapeHtml(Dict::S('UI:Button:UploadImage')),
),
);
$sEditImageOptions = json_encode($aEditImage);
$oPage->add_ready_script("$('#edit_$iInputId').edit_image($sEditImageOptions);");
break;
case 'StopWatch':
$sHTMLValue = "The edition of a stopwatch is not allowed!!!";
break;
case 'List':
// Not editable for now...
$sHTMLValue = '';
break;
case 'One Way Password':
$sInputType = self::ENUM_INPUT_TYPE_PASSWORD;
$aEventsList[] = 'validate';
$oWidget = new UIPasswordWidget($sAttCode, $iId, $sNameSuffix);
$sHTMLValue = $oWidget->Display($oPage, $aArgs);
// Event list & validation is handled directly by the widget
break;
case 'ExtKey':
/** @var \AttributeExternalKey $oAttDef */
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
if ($bPreserveCurrentValue) {
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '', $value);
} else {
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs);
}
$sFieldName = $sFieldPrefix.$sAttCode.$sNameSuffix;
$aExtKeyParams = $aArgs;
$aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize();
$aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars();
$sHTMLValue = UIExtKeyWidget::DisplayFromAttCode($oPage, $sAttCode, $sClass, $oAttDef->GetLabel(),
$oAllowedValues, $value, $iId, $bMandatory, $sFieldName, $sFormPrefix, $aExtKeyParams, false, $sInputType);
$sHTMLValue .= "\n";
$bHasExtKeyUpdatingRemoteClassFields = (
array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs)
&& ($aArgs['replaceDependenciesByRemoteClassFields'])
);
if ($bHasExtKeyUpdatingRemoteClassFields) {
// On this field update we need to update all the corresponding remote class fields
// Used when extkey widget is in a linkedset indirect
$sWizardHelperJsVarName = $aArgs['wizHelperRemote'];
$aDependencies = $aArgs['remoteCodes'];
}
break;
case 'RedundancySetting':
$sHTMLValue .= '
{$sValidationSpan}{$sReloadSpan}\n";
break;
case 'select':
default:
$sInputType = self::ENUM_INPUT_TYPE_DROPDOWN_RAW;
$aEventsList[] = 'change';
$sHTMLValue = "{$sValidationSpan}{$sReloadSpan}\n";
break;
}
}
else
{
$sInputType = self::ENUM_INPUT_TYPE_SINGLE_INPUT;
$sDisplayValueForHtml = utils::EscapeHtml($sDisplayValue);
// Adding tooltip so we can read the whole value when its very long (eg. URL)
$sTip = '';
if (!empty($sDisplayValue)) {
$sTip = 'data-tooltip-content="'.$sDisplayValueForHtml.'"';
$oPage->add_ready_script(<<
{$sValidationSpan}{$sReloadSpan}
HTML;
$aEventsList[] = 'keyup';
$aEventsList[] = 'change';
}
break;
}
$sPattern = addslashes($oAttDef->GetValidationPattern()); //'^([0-9]+)$';
if (!empty($aEventsList))
{
if (!is_numeric($sNullValue))
{
$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';
$sEventList = implode(' ', $aEventsList);
$oPage->add_ready_script(<< 0)
{
//--- Add an event handler to launch a custom event: validate
// * Unbind first to avoid duplicate event handlers in case of reload of the whole (or part of the) form
// * We were using off/on directly on the node before, but that was causing an issue when adding dynamically new nodes
// indeed the events weren't attached on the of the new nodes !
// So we're adding the handler on a node above, and we're using a selector to catch only the event we're interested in !
$sDependencies = implode("','", $aDependencies);
$oPage->add_ready_script(<<add_dict_entry('UI:ValueMustBeSet');
$oPage->add_dict_entry('UI:ValueMustBeChanged');
$oPage->add_dict_entry('UI:ValueInvalidFormat');
// N°3750 refresh container data-input-type attribute if in an Ajax context
// indeed in such a case we're only returning the field value content and not the parent container, so we need to update it !
if (utils::IsXmlHttpRequest()) {
// We are refreshing the data attribute only with the .attr() method
// So any consumer that want to get this attribute value MUST use `.attr()` and not `.data()`
// Actually the later uses a dedicated memory (that is initialized by the DOM values on page loading)
// Whereas `.attr()` uses the DOM directly
$oPage->add_init_script('$("[data-input-id=\''.$iId.'\']").attr("data-input-type", "'.$sInputType.'");');
}
//TODO 3.0 remove the data-attcode attribute (either because it's has been moved to .field_container in 2.7 or even better because the admin. console has been reworked)
return "
{$sHTMLValue}
";
}
/**
* @param \WebPage $oPage
* @param array $aExtraParams
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \Exception
*/
public function DisplayModifyForm(WebPage $oPage, $aExtraParams = array())
{
$sOwnershipToken = null;
$iKey = $this->GetKey();
$sClass = get_class($this);
$this->SetDisplayMode(($iKey > 0) ? static::ENUM_DISPLAY_MODE_EDIT : static::ENUM_DISPLAY_MODE_CREATE);
$sDisplayMode = $this->GetDisplayMode();
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT)
{
// The concurrent access lock makes sense only for already existing objects
$LockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled');
if ($LockEnabled)
{
$sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data');
if ($sOwnershipToken !== null)
{
// We're probably inside something like "apply_modify" where the validation failed and we must prompt the user again to edit the object
// let's extend our lock
}
else
{
$aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey);
if ($aLockInfo['success'])
{
$sOwnershipToken = $aLockInfo['token'];
}
else
{
// If the object is locked by the current user, it's worth trying again, since
// 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;
}
}
}
}
self::$iGlobalFormId++;
$this->aFieldsMap = array();
$sPrefix = '';
if (isset($aExtraParams['formPrefix'])) {
$sPrefix = $aExtraParams['formPrefix'];
}
$this->m_iFormId = $sPrefix.self::$iGlobalFormId;
$oAppContext = new ApplicationContext();
if (!isset($aExtraParams['action'])) {
$sFormAction = utils::GetAbsoluteUrlAppRoot().'pages/'.$this->GetUIPage(); // No parameter in the URL, the only parameter will be the ones passed through the form
} else {
$sFormAction = $aExtraParams['action'];
}
// Custom label for the apply button ?
if (isset($aExtraParams['custom_button'])) {
$sApplyButton = $aExtraParams['custom_button'];
} else {
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$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 ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$sOperation = 'apply_modify';
} else {
$sOperation = 'apply_new';
}
}
$oContentBlock = new UIContentBlock();
$oPage->AddUiBlock($oContentBlock);
$oForm = new Form("form_{$this->m_iFormId}");
$oForm->SetAction($sFormAction)
->SetOnSubmitJsCode("return OnSubmit('form_{$this->m_iFormId}');");
$oContentBlock->AddSubBlock($oForm);
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
// The object already exists in the database, it's a modification
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
}
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', $sOperation));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
// Add transaction ID
$iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId();
$oPage->SetTransactionId($iTransactionId);
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
// TODO 3.0.0: Is this (the if condition, not the code inside) still necessary?
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) {
$sClassLabel = MetaModel::GetName($sClass);
if ($this->GetDisplayMode() == static::ENUM_DISPLAY_MODE_CREATE) {
$oPage->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); // Set title will take care of the encoding
} else {
$oPage->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $this->GetRawName(), $sClassLabel)); // Set title will take care of the encoding
}
}
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel();
$oCancelButton->AddCSSClasses(['action', 'cancel']);
$oToolbarButtons->AddSubBlock($oCancelButton);
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
$oApplyButton->AddCSSClass('action');
$oToolbarButtons->AddSubBlock($oApplyButton);
$aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && count($aTransitions)) {
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this);
$oTransitionPopoverMenu = new PopoverMenu();
$sTPMSectionId = 'transitions';
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
$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) {
case UR_ALLOWED_YES:
// Button to be displayed on its own on large screens
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
// Button to be displayed in a grouped button on smaller screens
$oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);");
$oTransitionPopoverMenu->AddItem($sTPMSectionId, new JsPopoverMenuItem($oTPMPopupMenuItem));
break;
default:
// Do nothing
}
}
// If there are some allowed transitions, build the grouped button
if ($oTransitionPopoverMenu->HasItems()) {
$oApplyForButtonGroup = ButtonUIBlockFactory::MakeForPrimaryAction($oApplyButton->GetLabel(), null, null, true);
$oApplyAndTransitionsButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oApplyForButtonGroup, $oTransitionPopoverMenu)
->SetIsHidden(true);
$oToolbarButtons->AddSubBlock($oApplyAndTransitionsButtonGroup);
}
}
$sStatesSelection = '';
if (!isset($aExtraParams['custom_operation']) && $this->IsNew())
{
$aInitialStates = MetaModel::EnumInitialStates($sClass);
//$aInitialStates = array('new' => 'foo', 'closed' => 'bar');
if (count($aInitialStates) > 1)
{
$sStatesSelection = Dict::Format('UI:Create_Class_InState',
MetaModel::GetName($sClass)).'';
$sStatesSelection .= '';
$oPage->add_ready_script(<<m_iFormId}').change( function() {
if ($('#obj_state_orig').val() != $(this).val()) {
$('.state_select_{$this->m_iFormId}').val($(this).val());
$('#form_{$this->m_iFormId}').data('force_submit', true);
$('#form_{$this->m_iFormId}').submit();
}
});
JS
);
}
}
$sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<SetIcon($sClassIcon);
$oToolbarButtons->AddCSSClass('ibo-toolbar--button');
} else {
$oObjectDetails = ObjectFactory::MakeDetails($this, $this->GetDisplayMode());
$oToolbarButtons->AddCSSClass('ibo-toolbar-top');
$oObjectDetails->AddToolbarBlock($oToolbarButtons);
}
$oForm->AddSubBlock($oObjectDetails);
if (isset($aExtraParams['nbBulkObj'])) {
// if bulk modify buttons must be after object display
$oForm->AddSubBlock($oToolbarButtons);
}
$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix, $oObjectDetails);
$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
$oPage->SetCurrentTab('UI:PropertiesTab');
$oPage->p($sStatesSelection);
$aFieldsMap = $this->DisplayBareProperties($oPage, true, $sPrefix, $aExtraParams);
//if we are in bulk modify : Special case to display the case log, if any...
// WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayModifyForm
if (isset($aExtraParams['nbBulkObj'])) {
foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef) {
if ($oAttDef instanceof AttributeCaseLog) {
$sComment = (isset($aExtraParams['fieldsComments'][$sAttCode])) ? $aExtraParams['fieldsComments'][$sAttCode] : '';
$this->DisplayCaseLogForBulkModify($oPage, $sAttCode, $sComment, $sPrefix);
$aFieldsMap[$sAttCode] = $this->m_iFormId.'_'.$sAttCode;
}
}
}
if (!is_array($aFieldsMap)) {
$aFieldsMap = array();
}
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$aFieldsMap['id'] = $sPrefix.'_id';
}
// Now display the relations, one tab per relation
if (!isset($aExtraParams['noRelations'])) {
$this->DisplayBareRelations($oPage, true); // Edit mode, will fill $this->aFieldsMap
$aFieldsMap = array_merge($aFieldsMap, $this->aFieldsMap);
}
$oPage->SetCurrentTab('');
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
foreach ($aExtraParams as $sName => $value) {
if (is_scalar($value)) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sName, $value));
}
}
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
$oPage->add($oAppContext->GetForForm());
// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=search_form&class='.$sClass.'&'.$oAppContext->GetForLink();
$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').on('click', function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl', $sJSToken)} );");
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$sState = $this->GetState();
$sSessionStorageKey = $sClass.'_'.$iKey;
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
$oPage->add_script(
<<add_ready_script(
<<m_iFormId}', false);
EOF
);
if ($sOwnershipToken !== null)
{
$this->GetOwnershipJSHandler($oPage, $sOwnershipToken);
}
else
{
// Probably a new object (or no concurrent lock), let's add a watchdog so that the session is kept open while editing
$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
$oPage->add_ready_script(
<<set_title($sTitle);
$sClassIconUrl = MetaModel::GetClassIcon($sClass, false);
$oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle)
->SetIcon($sClassIconUrl);
$oClassForm = FormUIBlockFactory::MakeStandard();
$oPanel->AddMainBlock($oClassForm);
$oClassForm->AddHtml($oAppContext->GetForForm())
->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'new'));
foreach ($aHiddenFields as $sKey => $sValue) {
if (is_scalar($sValue)) {
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sKey, $sValue));
}
}
$aDefaults = utils::ReadParam('default', array(), false, 'raw_data');
foreach ($aDefaults as $key => $value) {
if (is_array($value)) {
foreach ($value as $key2 => $value2) {
if (is_array($value2)) {
foreach ($value2 as $key3 => $value3) {
$sValue = utils::EscapeHtml($value3);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2][$key3]", $sValue));
}
} else {
$sValue = utils::EscapeHtml($value2);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key][$key2]", $sValue));
}
}
} else {
$sValue = utils::EscapeHtml($value);
$oClassForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("default[$key]", $sValue));
}
}
$oClassForm->AddSubBlock(self::DisplayBlockSelectClassToCreate($sClass, $sClassLabel, $aPossibleClasses));
$oP->AddSubBlock($oPanel);
}
/**
* @param string $sClassLabel
* @param array $aPossibleClasses
* @param string $sClass
*
* @return UIContentBlock
* @throws \CoreException
*/
public static function DisplayBlockSelectClassToCreate( string $sClass, string $sClassLabel,array $aPossibleClasses): UIContentBlock
{
$oBlock= UIContentBlockUIBlockFactory::MakeStandard();
$oBlock->AddSubBlock(HtmlFactory::MakeRaw(Dict::Format('UI:SelectTheTypeOf_Class_ToCreate', $sClassLabel)));
$oSelect = SelectUIBlockFactory::MakeForSelect('class');
$oBlock->AddSubBlock($oSelect);
asort($aPossibleClasses);
foreach ($aPossibleClasses as $sClassName => $sClassLabel) {
$oSelect->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, $sClassLabel, ($sClassName == $sClass)));
}
$oToolbar = ToolbarUIBlockFactory::MakeForAction();
$oBlock->AddSubBlock($oToolbar);
$oToolbar->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true));
return $oBlock;
}
/**
* @param \WebPage $oPage
* @param string $sClass
* @param \DBObject|null $oSourceObject Object to use for the creation form, can be either the class to instantiate, an object to clone or an object to use (eg. already prefilled / modeled object)
* @param array $aArgs
* @param array $aExtraParams Extra parameters depending on the context, below is a WIP attempt at documenting them:
* [
* ...
* 'keep_source_object' => true|false, // Whether the $oSourceObject should be kept or cloned. Default is true.
* ...
* ]
*
* @return mixed
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
public static function DisplayCreationForm(WebPage $oPage, $sClass, $oSourceObject = null, $aArgs = array(), $aExtraParams = array())
{
$sClass = ($oSourceObject == null) ? $sClass : get_class($oSourceObject);
if ($oSourceObject == null) {
$oObj = DBObject::MakeDefaultInstance($sClass);
} elseif (isset($aExtraParams['keep_source_object']) && (true === isset($aExtraParams['keep_source_object']))) {
$oObj = $oSourceObject;
} else {
$oObj = clone $oSourceObject;
}
$oObj->SetDisplayMode(static::ENUM_DISPLAY_MODE_CREATE);
// Pre-fill the object with default values, when there is only on possible choice
// AND the field is mandatory (otherwise there is always the possiblity to let it empty)
$aArgs['this'] = $oObj;
$aDetailsList = self::FLattenZList(MetaModel::GetZListItems($sClass, 'details'));
// Order the fields based on their dependencies
$aDeps = array();
foreach($aDetailsList as $sAttCode)
{
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
}
$aList = self::OrderDependentFields($aDeps);
// Now fill-in the fields with default/supplied values
foreach($aList as $sAttCode)
{
if (isset($aArgs['default'][$sAttCode]))
{
$oObj->Set($sAttCode, $aArgs['default'][$sAttCode]);
}
else
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
// If the field is mandatory, set it to the only possible value
$iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode);
if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY))
{
if ($oAttDef->IsExternalKey())
{
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs);
if ($oAllowedValues->CountWithLimit(2) == 1)
{
$oRemoteObj = $oAllowedValues->Fetch();
$oObj->Set($sAttCode, $oRemoteObj->GetKey());
}
}
else
{
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (is_array($aAllowedValues) && (count($aAllowedValues) == 1))
{
$aValues = array_keys($aAllowedValues);
$oObj->Set($sAttCode, $aValues[0]);
}
}
}
}
}
return $oObj->DisplayModifyForm($oPage, $aExtraParams);
}
/**
* @param \WebPage $oPage
* @param string $sStimulus
* @param array|null $aPrefillFormParam
* @param bool $bDisplayBareProperties Whether to display the object details or not
*
* @throws \ApplicationException
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function DisplayStimulusForm(WebPage $oPage, $sStimulus, $aPrefillFormParam = null, $bDisplayBareProperties = true)
{
$this->SetDisplayMode(static::ENUM_DISPLAY_MODE_STIMULUS);
$sClass = get_class($this);
$iKey = $this->GetKey();
$sDisplayMode = $this->GetDisplayMode();
$iTransactionId = utils::GetNewTransactionId();
$aTransitions = $this->EnumTransitions();
$aStimuli = MetaModel::EnumStimuli($sClass);
if (!isset($aTransitions[$sStimulus]))
{
// Invalid stimulus
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)
{
$aLockInfo = iTopOwnershipLock::AcquireLock($sClass, $iKey);
if ($aLockInfo['success'])
{
$sOwnershipToken = $aLockInfo['token'];
}
else
{
// If the object is locked by the current user, it's worth trying again, since
// 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();
// Get info on current state
$sCurrentState = $this->GetState();
$sTargetState = $aTransitions[$sStimulus]['target_state'];
$aExpectedAttributes = $this->GetTransitionAttributes($sStimulus /*, current state*/);
if ($aPrefillFormParam != null)
{
$aPrefillFormParam['expected_attributes'] = $aExpectedAttributes;
$this->PrefillForm('state_change', $aPrefillFormParam);
$aExpectedAttributes = $aPrefillFormParam['expected_attributes'];
}
$aDetails = array();
$iFieldIndex = 0;
$aFieldsMap = [
'id' => 'id',
];
// 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) {
$aAttributes[$sAttCode] = true;
}
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
if (!array_key_exists($sAttCode, $aAttributes)) {
$aAttributes[$sAttCode] = true;
}
}
// Order the fields based on their dependencies, set the fields for which there is only one possible value
// and perform this in the order of dependencies to avoid dead-ends
$aDeps = array();
foreach ($aAttributes as $sAttCode => $trash) {
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
}
$aList = $this->OrderDependentFields($aDeps);
$bExistFieldToDisplay = false;
foreach ($aList as $sAttCode) {
// Consider only the "expected" fields for the target state
if (array_key_exists($sAttCode, $aExpectedAttributes)) {
$iExpectCode = $aExpectedAttributes[$sAttCode];
// 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) == ''))) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$aArgs = array('this' => $this);
// If the field is mandatory, set it to the only possible value
if ((!$oAttDef->IsNullAllowed()) || ($iExpectCode & OPT_ATT_MANDATORY)) {
if ($oAttDef->IsExternalKey()) {
/** @var DBObjectSet $oAllowedValues */
$oAllowedValues = MetaModel::GetAllowedValuesAsObjectSet($sClass, $sAttCode, $aArgs, '',
$this->Get($sAttCode));
if ($oAllowedValues->CountWithLimit(2) == 1) {
$oRemoteObj = $oAllowedValues->Fetch();
$this->Set($sAttCode, $oRemoteObj->GetKey());
}
} else
{
if ($oAttDef instanceof \AttributeCaseLog) {
// Add JS files for display caselog
// Dummy collapsible section created in order to get JS files
$oCollapsibleSection = new CollapsibleSection('');
foreach ($oCollapsibleSection->GetJsFilesUrlRecursively(true) as $sJSFile) {
$oPage->add_linked_script($sJSFile);
}
}
$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs);
if (is_array($aAllowedValues) && count($aAllowedValues) == 1)
{
$aValues = array_keys($aAllowedValues);
$this->Set($sAttCode, $aValues[0]);
}
}
}
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
$this->Get($sAttCode), $this->GetEditValue($sAttCode), 'att_'.$iFieldIndex, '', $iExpectCode,
$aArgs);
$aAttrib = array(
'label' => ''.$oAttDef->GetLabel().'',
'value' => "$sHTMLValue",
);
//add attrib for data-attribute
// Prepare metadata attributes
$sAttCode = $oAttDef->GetCode();
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sAttDefClass = get_class($oAttDef);
$sAttLabel = MetaModel::GetLabel($sClass, $sAttCode);
$aAttrib['attcode'] = $sAttCode;
$aAttrib['atttype'] = $sAttDefClass;
$aAttrib['attlabel'] = $sAttLabel;
// - Attribute flags
$aAttrib['attflags'] = $this->GetFormAttributeFlags($sAttCode) ;
// - How the field should be rendered
$aAttrib['layout'] = (in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? 'large' : 'small';
// - For simple fields, we get the raw (stored) value as well
$bExcludeRawValue = false;
foreach (static::GetAttDefClassesToExcludeFromMarkupMetadataRawValue() as $sAttDefClassToExclude)
{
if (is_a($sAttDefClass, $sAttDefClassToExclude, true)) {
$bExcludeRawValue = true;
break;
}
}
$aAttrib['value_raw'] = ($bExcludeRawValue === false) ? $this->Get($sAttCode) : '';
$aDetails[] = $aAttrib;
$aFieldsMap[$sAttCode] = 'att_'.$iFieldIndex;
$iFieldIndex++;
$bExistFieldToDisplay = true;
}
}
}
if ($bExistFieldToDisplay || MetaModel::GetConfig()->Get('force_transition_confirmation')) {
$oPage->set_title($sActionLabel);
$oPage->add(<<
HTML
);
// Page title and subtitles
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionLabel.' - '.$this->GetRawName()));
if (!empty($sActionDetails)) {
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails));
}
$oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']);
$oPage->AddUiBlock($oFormContainer);
$oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form('apply_stimulus');
$oFormContainer->AddSubBlock($oForm);
$oForm->SetOnSubmitJsCode("return OnSubmit('apply_stimulus');")
->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $this->GetKey(), 'id'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'apply_stimulus'))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus))
->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
if ($sOwnershipToken !== null) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('ownership_token', utils::HtmlEntities($sOwnershipToken)));
}
// Note: Remove the table if we want fields to occupy the whole width of the container, BUT with today's layout, fields' label will occupy way too much space. This should be part of the field layout rework.
// Note 2: The hardcoded width allows the fields to be a bit wider (useful for long values) while still working on different screen sizes
// Note 3: The inline style is not ideal but we are still wondring how transition form should be displayed
$sHtml = '
';
$oAppContext = new ApplicationContext();
$sHtml .= $oAppContext->GetForForm();
$oForm->AddHtml($sHtml);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel')
// Action type is changed on purpose so the button is more visible in the form.
->SetActionType(Button::ENUM_ACTION_TYPE_REGULAR)
->SetOnClickJsCode("BackToDetails('{$sClass}', '{$this->GetKey()}', '', '{$sOwnershipToken}');");
$oForm->AddSubBlock($oCancelButton);
$oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true);
$oForm->AddSubBlock($oSubmitButton);
$oPage->add(<<
HTML
);
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oPage->add_script(
<<GetState()}', '$sStimulus');
oWizardHelper.SetFieldsMap($sJsonFieldsMap);
oWizardHelper.SetFieldsCount($iFieldsCount);
EOF
);
$sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(
<<GetOwnershipJSHandler($oPage, $sOwnershipToken);
}
// Note: This part (inline images activation) is duplicated in self::DisplayModifyForm and several other places. Maybe it should be refactored so it automatically activates when an HTML field is present, or be an option of the attribute. See bug N°1240.
$sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
} else {
//we can directly apply the stimuli
$bApplyStimulus = $this->ApplyStimulus($sStimulus); // will write the object in the DB
if (!$bApplyStimulus) {
throw new ApplicationException(Dict::S('UI:FailedToApplyStimuli'));
} else {
if ($sOwnershipToken !== null) {
// Release the concurrent lock, if any
iTopOwnershipLock::ReleaseLock($sClass, $iKey, $sOwnershipToken);
}
return true;
}
}
return false;
}
public static function ProcessZlist($aList, $aDetails, $sCurrentTab, $sCurrentCol, $sCurrentSet)
{
$index = 0;
foreach($aList as $sKey => $value)
{
if (is_array($value))
{
if (preg_match('/^(.*):(.*)$/U', $sKey, $aMatches))
{
$sCode = $aMatches[1];
$sName = $aMatches[2];
switch ($sCode)
{
case 'tab':
if (!isset($aDetails[$sName]))
{
$aDetails[$sName] = array('col1' => array());
}
$aDetails = self::ProcessZlist($value, $aDetails, $sName, 'col1', '');
break;
case 'fieldset':
if (!isset($aDetailsStruct[$sCurrentTab][$sCurrentCol][$sName]))
{
$aDetails[$sCurrentTab][$sCurrentCol][$sName] = array();
}
$aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sCurrentCol, $sName);
break;
default:
case 'col':
if (!isset($aDetails[$sCurrentTab][$sName]))
{
$aDetails[$sCurrentTab][$sName] = array();
}
$aDetails = self::ProcessZlist($value, $aDetails, $sCurrentTab, $sName, '');
break;
}
}
}
else
{
if (empty($sCurrentSet))
{
$aDetails[$sCurrentTab][$sCurrentCol]['_'.$index][] = $value;
}
else
{
$aDetails[$sCurrentTab][$sCurrentCol][$sCurrentSet][] = $value;
}
}
$index++;
}
return $aDetails;
}
public static function FlattenZList($aList)
{
$aResult = array();
foreach($aList as $value)
{
if (!is_array($value))
{
$aResult[] = $value;
}
else
{
$aResult = array_merge($aResult, self::FlattenZList($value));
}
}
return $aResult;
}
protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode)
{
$retVal = null;
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard))
{
// First prepare the label
// - Attribute description
$sDescription = $oAttDef->GetDescription();
$sDescriptionForHTMLAttributes = utils::HtmlEntities($sDescription);
$sDescriptionHTMLAttributes = (empty($sDescriptionForHTMLAttributes) || $sDescription === $oAttDef->GetLabel()) ? '' : 'class="ibo-has-description" data-tooltip-content="'.$sDescriptionForHTMLAttributes.'" data-tooltip-max-width="600px"';
// - Fullscreen toggler for large fields
$sFullscreenTogglerTooltip = Dict::S('UI:ToggleFullScreen');
$sFullscreenTogglerHTML = (false === in_array($oAttDef->GetEditClass(), static::GetAttEditClassesToRenderAsLargeField())) ? '' : <<
HTML;
$sLabelAsHtml = ''.MetaModel::GetLabel($sClass, $sAttCode).''.$sFullscreenTogglerHTML;
// Then prepare the value
// - The field is visible in the current state of the object
if ($oAttDef->GetEditClass() == 'Document') {
/** @var \ormDocument $oDocument */
$oDocument = $this->Get($sAttCode);
if (!$oDocument->IsEmpty()) {
$sFieldAsHtml = $this->GetAsHTML($sAttCode);
$sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_');
$sDisplayUrl = $oDocument->GetDisplayURL(get_class($this), $this->GetKey(), $sAttCode);
$sDownloadLabel = Dict::Format('UI:DownloadDocument_');
$sDownloadUrl = $oDocument->GetDownloadURL(get_class($this), $this->GetKey(), $sAttCode);
$sDisplayValue = <<{$sDisplayLabel} / {$sDownloadLabel}
HTML;
} else {
$sDisplayValue = '';
}
} elseif ($oAttDef instanceof AttributeDashboard) {
$sDisplayValue = '';
} else {
$sDisplayValue = $this->GetAsHTML($sAttCode);
}
$sValueAsHtml = $sDisplayValue;
$retVal = [
'label' => $sLabelAsHtml,
'value' => $sValueAsHtml,
];
}
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)
{
/** @var \ormDocument $oDoc */
$oDoc = $this->Get($sAttCode);
$sClass = get_class($this);
$sId = $this->GetKey();
$sDisplayUrl = $oDoc->GetDisplayURL($sClass, $sId, $sAttCode);
switch ($oDoc->GetMainMimeType())
{
case 'text':
case 'html':
$data = $oDoc->GetData();
switch ($oDoc->GetMimeType()) {
case 'text/xml':
$oPage->add("\n");
break;
default:
$oPage->add("
\n");
}
break;
case 'application':
switch ($oDoc->GetMimeType())
{
case 'application/pdf':
$oPage->add("\n");
break;
default:
$oPage->add(Dict::S('UI:Document:NoPreview'));
}
break;
case 'image':
$oPage->add("\n");
break;
default:
$oPage->add(Dict::S('UI:Document:NoPreview'));
}
return '';
}
// $m_highlightComparison[previous][new] => next value
protected static $m_highlightComparison = array(
HILIGHT_CLASS_CRITICAL => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_OK => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_CRITICAL,
),
HILIGHT_CLASS_WARNING => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_WARNING,
),
HILIGHT_CLASS_OK => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_OK,
HILIGHT_CLASS_NONE => HILIGHT_CLASS_OK,
),
HILIGHT_CLASS_NONE => array(
HILIGHT_CLASS_CRITICAL => HILIGHT_CLASS_CRITICAL,
HILIGHT_CLASS_WARNING => HILIGHT_CLASS_WARNING,
HILIGHT_CLASS_OK => HILIGHT_CLASS_OK,
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()
{
// Possible return values are:
// HILIGHT_CLASS_CRITICAL, HILIGHT_CLASS_WARNING, HILIGHT_CLASS_OK, HILIGHT_CLASS_NONE
$current = parent::GetHilightClass(); // Default computation
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
/** @var \iApplicationUIExtension $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)
{
$aResult = array();
$iCount = 0;
do
{
$bSet = false;
$iCount++;
foreach($aFields as $sFieldCode => $aDeps)
{
foreach($aDeps as $key => $sDependency)
{
if (in_array($sDependency, $aResult))
{
// Dependency is resolved, remove it
unset($aFields[$sFieldCode][$key]);
}
else
{
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)
{
// No more pending depencies for this field, add it to the list
$aResult[] = $sFieldCode;
unset($aFields[$sFieldCode]);
$bSet = true;
}
}
} while ($bSet && (count($aFields) > 0));
if (count($aFields) > 0)
{
$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
*
* @api
* @overwritable-hook
*
* @param string $sFinalClass 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
*/
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)
{
if ($sContextParam == 'menu')
{
return null;
}
else
{
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())
{
if (!is_array($aAttList))
{
$aAttList = $this->FlattenZList(MetaModel::GetZListItems(get_class($this), 'details'));
// Special case to process the case log, if any...
// WARNING: if you change this also check the functions DisplayModifyForm and DisplayCaseLog
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
{
if (array_key_exists($sAttCode, $aAttFlags))
{
$iFlags = $aAttFlags[$sAttCode];
}
elseif ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$aVoid = array();
$iFlags = $this->GetAttributeFlags($sAttCode, $aVoid);
}
if ($oAttDef instanceof AttributeCaseLog)
{
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;
}
}
}
}
$aWriteableAttList = array();
foreach($aAttList as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
if (array_key_exists($sAttCode, $aAttFlags))
{
$iFlags = $aAttFlags[$sAttCode];
}
elseif ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$aVoid = array();
$iFlags = $this->GetAttributeFlags($sAttCode, $aVoid);
}
if ($oAttDef->IsWritable())
{
if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY))
{
// Non-visible, or read-only attribute, do nothing
}
elseif ($iFlags & OPT_ATT_SLAVE)
{
$aErrors[$sAttCode] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode);
}
else
{
$aWriteableAttList[$sAttCode] = $oAttDef;
}
}
}
return $aWriteableAttList;
}
/**
* Compute the attribute flags depending on the object state
*/
public function GetFormAttributeFlags($sAttCode)
{
if ($this->IsNew())
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFlags = $this->GetAttributeFlags($sAttCode);
}
if (($iFlags & OPT_ATT_MANDATORY) && $this->IsNew())
{
$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 $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);
switch ($oAttDef->GetEditClass())
{
case 'Document':
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))
{
break;
}
$iValue = (((24 * $aDurationData['d']) + $aDurationData['h']) * 60 + $aDurationData['m']) * 60 + $aDurationData['s'];
$this->Set($sAttCode, $iValue);
$previousValue = $this->Get($sAttCode);
if ($previousValue !== $iValue)
{
$this->Set($sAttCode, $iValue);
}
break;
case 'CustomFields':
$this->Set($sAttCode, $value);
break;
case 'LinkedSet':
if ($this->IsValueModified($value))
{
$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, $oAttDef->GetMaxItems());
}
$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, $oAttDef->GetMaxItems());
}
$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]);
}
}
}
}
}
private function IsValueModified($value)
{
$aModifiedKeys = ['to_be_created', 'to_be_added', 'to_be_modified', 'to_be_removed', 'to_be_deleted'];
foreach ($aModifiedKeys as $sModifiedKey) {
if (array_key_exists( $sModifiedKey, $value) && (count($value[$sModifiedKey]) > 0))
{
return true;
}
}
return false;
}
/**
* Updates the object from the POSTed parameters (form)
*/
public function UpdateObjectFromPostedForm($sFormPrefix = '', $aAttList = null, $aAttFlags = array())
{
if (is_null($aAttList))
{
$aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this)));
}
$aValues = array();
foreach($aAttList as $sAttCode)
{
$value = $this->PrepareValueFromPostedForm($sFormPrefix, $sAttCode);
if (!is_null($value))
{
$aValues[$sAttCode] = $value;
}
}
$aErrors = array();
$aFinalValues = array();
foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $aAttFlags) as $sAttCode => $oAttDef)
{
$aFinalValues[$sAttCode] = $aValues[$sAttCode];
}
try
{
$this->UpdateObjectFromArray($aFinalValues);
}
catch (CoreException $e)
{
$aErrors[] = $e->getMessage();
}
if (!$this->IsNew()) // for new objects this is performed in DBInsertNoReload()
{
InlineImage::FinalizeInlineImages($this);
}
// Invoke extensions after the update of the object from the form
/** @var \iApplicationUIExtension $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
* @throws \FileUploadException
*/
protected function PrepareValueFromPostedForm($sFormPrefix, $sAttCode, $sClass = null, $aPostedData = null)
{
if ($sClass === null)
{
$sClass = get_class($this);
}
$value = null;
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
switch ($oAttDef->GetEditClass())
{
case 'Document':
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
$value = array('fcontents' => utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents'), 'remove' => $aOtherData['remove']);
break;
case 'Image':
$value = null;
$oImage = utils::ReadPostedDocument("attr_{$sFormPrefix}{$sAttCode}", 'fcontents');
if (!is_null($oImage->GetData()))
{
$aSize = utils::GetImageSize($oImage->GetData());
if (is_array($aSize) && $aSize[0] > 0 && $aSize[1] > 0)
{
$oImage = utils::ResizeImageToFit(
$oImage,
$aSize[0],
$aSize[1],
$oAttDef->Get('storage_max_width'),
$oAttDef->Get('storage_max_height')
);
}
else
{
IssueLog::Warning($sClass . ':' . $this->GetKey() . '/' . $sAttCode . ': Image could not be resized. Mimetype: ' . $oImage->GetMimeType() . ', filename: ' . $oImage->GetFileName());
}
}
$aOtherData = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
if (is_array($aOtherData))
{
$value = array('fcontents' => $oImage, 'remove' => $aOtherData['remove']);
}
break;
case 'RedundancySetting':
$value = $oAttDef->ReadValueFromPostedForm($sFormPrefix);
break;
case 'CustomFields':
$value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix);
break;
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))
{
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $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], $sObjClass, $aData);
}
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'];
$sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass();
$aObjData = array();
foreach($aData as $sKey => $value)
{
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
{
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $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], $sObjClass, $aData);
}
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
*
* The values are read from parameters (GET or POST, using {@see utils::ReadParam()}).
*
* To pass the arg, either add in HTML :
*
* ```html
*
*
* ```
*
* Or directly in the URL :
*
* ```php
* $aObjectArgs = ['attCode1' => ..., 'attCode2' => ...];
* $sQueryString = http_build_query(['sArgName' => $aObjectArgs]);
* ```
*
* @uses utils::ReadParam()
* @uses self::UpdateObjectFromArray
*/
public function UpdateObjectFromArg($sArgName, $aAttList = null, $aAttFlags = array())
{
if (is_null($aAttList))
{
$aAttList = array_keys(MetaModel::ListAttributeDefs(get_class($this)));
}
$aRawValues = utils::ReadParam($sArgName, array(), '', 'raw_data');
$aValues = array();
foreach($aAttList as $sAttCode)
{
if (isset($aRawValues[$sAttCode]))
{
$aValues[$sAttCode] = $aRawValues[$sAttCode];
}
}
$aErrors = array();
$aFinalValues = array();
foreach($this->GetWriteableAttList(array_keys($aValues), $aErrors, $aAttFlags) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsLinkSet())
{
$aFinalValues[$sAttCode] = json_decode($aValues[$sAttCode], true);
}
else
{
$aFinalValues[$sAttCode] = $aValues[$sAttCode];
}
}
try
{
$this->UpdateObjectFromArray($aFinalValues);
}
catch (CoreException $e)
{
$aErrors[] = $e->getMessage();
}
return $aErrors;
}
/**
* @inheritdoc
*/
public function DBInsertNoReload()
{
$res = parent::DBInsertNoReload();
$this->SetWarningsAsSessionMessages('create');
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
}
return $res;
}
/**
* @inheritdoc
* Attaches InlineImages to the current object
*/
protected function OnObjectKeyReady()
{
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.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
}
return $oNewObj;
}
public function DBUpdate()
{
$res = parent::DBUpdate();
$this->SetWarningsAsSessionMessages('update');
// 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)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
}
}
catch (Exception $e)
{
throw $e;
}
finally
{
unset($aUpdateReentrance[$sKey]);
}
return $res;
}
/**
* @param string $sMessageIdPrefix
*
* @since 2.6.0
*/
protected function SetWarningsAsSessionMessages($sMessageIdPrefix)
{
if (!empty($this->m_aCheckWarnings) && is_array($this->m_aCheckWarnings)) {
$iMsgNb = 0;
foreach ($this->m_aCheckWarnings as $sWarningMessage) {
$iMsgNb++;
$sMessageId = "$sMessageIdPrefix-$iMsgNb"; // each message must have its own messageId !
$this->SetSessionMessageFromInstance($sMessageId, $sWarningMessage, 'warning', 0);
}
}
}
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
}
return parent::DBDeleteTracked_Internal($oDeletionPlan);
}
public function IsModified()
{
if (parent::IsModified())
{
return true;
}
// Plugins
//
/** @var \iApplicationObjectExtension $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;
}
/**
* Whether to bypass the checks of user rights when writing this object, could be used in {@link \iApplicationObjectExtension::OnCheckToWrite()}
*
* @return bool
*/
public function GetAllowWrite()
{
return $this->bAllowWrite;
}
/**
* Bypass the check of the user rights when deleting this object
*
* @param bool $bAllow True to bypass the checks, false to restore the default behavior
*/
public function AllowDelete($bAllow = true)
{
$this->bAllowDelete = $bAllow;
}
/**
* @inheritdoc
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
// Plugins
//
/** @var \iApplicationObjectExtension $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
{
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
}
}
// User rights
//
if (!$this->bAllowWrite)
{
$aChanges = $this->ListChanges();
if (count($aChanges) > 0)
{
$aForbiddenFields = array();
foreach($this->ListChanges() as $sAttCode => $value)
{
$bUpdateAllowed = UserRights::IsActionAllowedOnAttribute(get_class($this), $sAttCode,
UR_ACTION_MODIFY, DBObjectSet::FromObject($this));
if (!$bUpdateAllowed)
{
$oAttCode = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$aForbiddenFields[] = $oAttCode->GetLabel();
}
}
if (count($aForbiddenFields) > 0)
{
// Security issue
$this->m_bSecurityIssue = true;
$this->m_aCheckIssues[] = Dict::Format('UI:Delete:NotAllowedToUpdate_Fields',
implode(', ', $aForbiddenFields));
}
}
}
}
/**
* @inheritDoc
*/
protected function DoCheckToDelete(&$oDeletionPlan)
{
parent::DoCheckToDelete($oDeletionPlan);
// Plugins
//
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
if (is_array($aNewIssues) && count($aNewIssues) > 0)
{
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
}
}
// User rights
//
if (! $this->bAllowDelete)
{
$bDeleteAllowed = UserRights::IsActionAllowed(get_class($this), UR_ACTION_DELETE, DBObjectSet::FromObject($this));
if (!$bDeleteAllowed)
{
// Security issue
$this->m_bSecurityIssue = true;
$this->m_aDeleteIssues[] = Dict::S('UI:Delete:NotAllowedToDelete');
}
}
}
/**
* Special display where the case log uses the whole "screen" at the bottom of the "Properties" tab
*
* @param \WebPage $oPage
* @param string $sAttCode
* @param string $sComment
* @param string $sPrefix
* @param bool $bEditMode
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \OQLException
* @throws \Exception
* @deprecated 3.0.0, will be removed in 3.1.0
*/
public function DisplayCaseLog(WebPage $oPage, $sAttCode, $sComment = '', $sPrefix = '', $bEditMode = false)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod();
$oPage->SetCurrentTab('UI:PropertiesTab');
$sClass = get_class($this);
if ($this->IsNew()) {
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
} else {
$iFlags = $this->GetAttributeFlags($sAttCode);
}
if ($iFlags & OPT_ATT_HIDDEN) {
// The case log is hidden do nothing
} else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$sAttDefClass = get_class($oAttDef);
$sAttLabel = $oAttDef->GetLabel();
$sAttMetaDataLabel = utils::HtmlEntities($sAttLabel);
$sAttMetaDataFlagHidden = (($iFlags & OPT_ATT_HIDDEN) === OPT_ATT_HIDDEN) ? 'true' : 'false';
$sAttMetaDataFlagReadOnly = (($iFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY) ? 'true' : 'false';
$sAttMetaDataFlagMandatory = (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY) ? 'true' : 'false';
$sAttMetaDataFlagMustChange = (($iFlags & OPT_ATT_MUSTCHANGE) === OPT_ATT_MUSTCHANGE) ? 'true' : 'false';
$sAttMetaDataFlagMustPrompt = (($iFlags & OPT_ATT_MUSTPROMPT) === OPT_ATT_MUSTPROMPT) ? 'true' : 'false';
$sAttMetaDataFlagSlave = (($iFlags & OPT_ATT_SLAVE) === OPT_ATT_SLAVE) ? 'true' : 'false';
$sInputId = $this->m_iFormId.'_'.$sAttCode;
if ((!$bEditMode) || ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)))
{
// Check if the attribute is not read-only because of a synchro...
if ($iFlags & OPT_ATT_SLAVE)
{
$aReasons = array();
$sTip = '';
foreach($aReasons as $aRow) {
$sDescription = utils::EscapeHtml($aRow['description']);
$sDescription = str_replace(array("\r\n", "\n"), " ", $sDescription);
$sTip .= "
HTML
);
}
}
/**
* Special display where the case log uses the whole "screen" at the bottom of the "Properties" tab
*
* @param \WebPage $oPage
* @param string $sAttCode
* @param string $sComment
* @param string $sPrefix
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DictExceptionMissingString
* @throws \MySQLException
* @throws \OQLException
* @throws \Exception
*/
public function DisplayCaseLogForBulkModify(WebPage $oPage, $sAttCode, $sComment = '', $sPrefix = '')
{
$sClass = get_class($this);
$iFlags = $this->GetAttributeFlags($sAttCode);
if ($iFlags & (OPT_ATT_HIDDEN | OPT_ATT_READONLY | OPT_ATT_SLAVE)) {
// The case log can not be updated
} else {
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
$sAttDefClass = get_class($oAttDef);
$sAttLabel = $oAttDef->GetLabel();
$sAttMetaDataLabel = utils::HtmlEntities($sAttLabel);
$sAttMetaDataFlagMandatory = (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY) ? 'true' : 'false';
$sAttMetaDataFlagMustChange = (($iFlags & OPT_ATT_MUSTCHANGE) === OPT_ATT_MUSTCHANGE) ? 'true' : 'false';
$sAttMetaDataFlagMustPrompt = (($iFlags & OPT_ATT_MUSTPROMPT) === OPT_ATT_MUSTPROMPT) ? 'true' : 'false';
$sInputId = $this->m_iFormId.'_'.$sAttCode;
$sValue = $this->Get($sAttCode);
$sDisplayValue = $this->GetEditValue($sAttCode);
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
$aFieldsMap[$sAttCode] = $sInputId;
$sCommentAsHtml = ($sComment != '') ? '
'.$sComment.'
' : '';
$oFieldset = FieldSetUIBlockFactory::MakeStandard($sAttLabel.$sCommentAsHtml);
$oPage->AddSubBlock($oFieldset);
$oDivField = FieldUIBlockFactory::MakeLarge("");
// UIContentBlockUIBlockFactory::MakeStandard(null,["field_container field_large"]);
$oDivField->AddDataAttribute("attribute-type", $sAttDefClass);
$oDivField->AddDataAttribute("attribute-label", $sAttMetaDataLabel);
$oDivField->AddDataAttribute("attribute-flag-hidden", false);
$oDivField->AddDataAttribute("attribute-flag-read-only", false);
$oDivField->AddDataAttribute("attribute-flag-mandatory", $sAttMetaDataFlagMandatory);
$oDivField->AddDataAttribute("attribute-flag-must-change", $sAttMetaDataFlagMustChange);
$oDivField->AddDataAttribute("attribute-flag-must-prompt", $sAttMetaDataFlagMustPrompt);
$oDivField->AddDataAttribute("attribute-flag-slave", false);
$oFieldset->AddSubBlock($oDivField);
//$oDivField->SetComments($sComment);
$sFieldAsHtml = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs);
$sHTMLValue = $sFieldAsHtml;
$oDivField->AddSubBlock(new Html($sHTMLValue));
}
}
/**
* @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)
{
DeprecatedCallsLog::NotifyDeprecatedPhpMethod('Since iTop 2.4, use DBObject::GetTransitionAttributes() instead');
$aTransitions = $this->EnumTransitions();
if (!isset($aTransitions[$sStimulus])) {
// Invalid stimulus
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'];
$aTargetStates = MetaModel::EnumStates(get_class($this));
$aTargetState = $aTargetStates[$sTargetState];
$aCurrentState = $aTargetStates[$this->GetState()];
$aExpectedAttributes = $aTargetState['attribute_list'];
$aCurrentAttributes = $aCurrentState['attribute_list'];
$aComputedAttributes = array();
foreach($aExpectedAttributes as $sAttCode => $iExpectCode)
{
if (!array_key_exists($sAttCode, $aCurrentAttributes))
{
$aComputedAttributes[$sAttCode] = $iExpectCode;
}
else
{
if (!($aCurrentAttributes[$sAttCode] & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)))
{
$iExpectCode = $iExpectCode & ~(OPT_ATT_MUSTPROMPT | OPT_ATT_MUSTCHANGE); // Already prompted/changed, reset the flags
}
// Later: better check if the attribute is not *null*
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] = $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
*
* @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);
$sSelectedObj = implode(',', $aSelectedObj);
$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 homogeneous
$aList = MetaModel::ListAttributeDefs($sClass);
$aValues = array();
foreach($aList as $sAttCode => $oAttDef)
{
if ($oAttDef->IsScalar())
{
$aValues[$sAttCode] = array();
}
}
while ($oObj = $oSet->Fetch())
{
foreach($aList as $sAttCode => $oAttDef)
{
if ($oAttDef->IsScalar() && $oAttDef->IsWritable())
{
$currValue = $oObj->Get($sAttCode);
if ($oAttDef instanceof AttributeCaseLog)
{
$currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059.
}
elseif ($currValue instanceof ormSet)
{
$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']++;
}
}
}
}
// Now create an object that has values for the homogeneous values only
/** @var \cmdbAbstractObject $oDummyObj */
$oDummyObj = new $sClass(); // @@ What if the class is abstract ?
$oDummyObj->SetDisplayMode(static::ENUM_DISPLAY_MODE_BULK_EDIT);
$aComments = array();
function MyComparison($a, $b) // Sort descending
{
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 = '';
$sFormPrefix = '2_';
foreach ($aList as $sAttCode => $oAttDef) {
$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}').on('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
if (count($aDependents) > 0) {
// When 'disabling' a field, all its dependent fields must be disabled too
$sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']";
$oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n");
}
if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) {
if ($oAttDef->GetEditClass() == 'One Way Password') {
$sTip = Dict::S('UI:Component:Field:BulkModify:UnknownValues:Tooltip');
$oDummyObj->Set($sAttCode, null);
$aComments[$sAttCode] = '
?';
$aComments[$sAttCode] .= '
';
$sReadyScript .= 'ToggleField(false, \''.$iFormId.'_'.$sAttCode.'\');'."\n";
} else {
$iCount = count($aValues[$sAttCode]);
if ($iCount == 1) {
// Homogeneous value
reset($aValues[$sAttCode]);
$aKeys = array_keys($aValues[$sAttCode]);
$currValue = $aKeys[0]; // The only value is the first key
$oDummyObj->Set($sAttCode, $currValue);
$aComments[$sAttCode] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$sValueCheckbox .= '';
}
$aComments[$sAttCode] .= '
";
$sTip = utils::HtmlEntities($sTip);
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);
$oTagSet->SetDisplayPartial(true);
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] = '';
$sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
$sValueCheckbox = '';
}
$aComments[$sAttCode] .= '
'.$iCount.$sValueCheckbox.'
';
}
$sReadyScript .= 'ToggleField('.(($iCount == 1) ? 'true' : 'false').', \''.$iFormId.'_'.$sAttCode.'\');'."\n";
}
}
}
if (MetaModel::HasLifecycle($sClass) && ($oDummyObj->GetState() == '')) {
// Hmmm, it's not gonna work like this ! Set a default value for the "state"
// Maybe we should use the "state" that is the most common among the objects...
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
$aMultiValues = $aValues[$sStateAttCode];
uasort($aMultiValues, 'MyComparison');
foreach ($aMultiValues as $sCurrValue => $aVal) {
$oDummyObj->Set($sStateAttCode, $sCurrValue);
break;
}
//$oStateAtt = MetaModel::GetAttributeDef($sClass, $sStateAttCode);
//$oDummyObj->Set($sStateAttCode, $oStateAtt->GetDefaultValue());
}
$oP->add("
');
oOwnershipLockModal.text(data.popup_message);
oOwnershipLockModal.dialog('open');
}
$('.ibo-object-details .ibo-toolbar .ibo-button:not([name="cancel"])').prop('disabled', true);
clearInterval(hOwnershipLockHandlerInterval);
}
}, 'json');
}, $iInterval);
JS
);
}
/**
* Return an array of AttributeDefinition EditClass that should be rendered as large field in the UI
*
* @return array
* @since 2.7.0
*/
protected static function GetAttEditClassesToRenderAsLargeField(){
return array(
'CaseLog',
'CustomFields',
'HTML',
'OQLExpression',
'Text',
);
}
/**
* Return an array of AttributeDefinition classes that should be excluded from the markup metadata when priting raw value (typically large values)
* This markup is mostly aimed at CSS/JS hooks for extensions and Behat tests
*
* @return array
* @since 2.7.0
*
* @internal Do NOT use, this is experimental and most likely to be moved elsewhere when we find its rightful place.
*/
public static function GetAttDefClassesToExcludeFromMarkupMetadataRawValue(){
return array(
'AttributeBlob',
'AttributeCustomFields',
'AttributeDashboard',
'AttributeLinkedSet',
'AttributeStopWatch',
'AttributeSubItem',
'AttributeTable',
'AttributeText',
'AttributePassword',
'AttributeOneWayPassword',
);
}
}