N°3190 - Edit n:n LinkedSetIndirect in object details using a tagset-like widget

- Add generic set block ui component
- Add model link set (direct and indirect) attribute (display style)
- Add model link set direct allowed values
- Create link set viewer block UI (BlockLinksSetDisplayAsProperty)
- Add set block ui factory for linkset
- Add object factory and create new endpoint in object controller (with data binder)
- Add link set model, link set repository and link set data transformer services
This commit is contained in:
bdalsass
2023-01-24 10:03:10 +01:00
committed by GitHub
parent 9482139b5a
commit fb1ceebaa4
55 changed files with 3948 additions and 234 deletions

View File

@@ -41,8 +41,12 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksViewTable; use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksViewTable;
use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksViewTable; use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksViewTable;
use Combodo\iTop\Application\UI\Links\Set\LinksSetUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel;
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties'); define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
@@ -663,10 +667,11 @@ HTML
continue; continue;
} }
// Display mode // Process only link set attributes with tab display style
if (!$oAttDef->IsLinkset()) { $bIsLinkSetWithDisplayStyleTab = is_a($oAttDef, AttributeLinkedSet::class) && $oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB;
if (!$oAttDef->IsLinkset() || !$bIsLinkSetWithDisplayStyleTab) {
continue; continue;
} // Process only linkset attributes... }
$sLinkedClass = $oAttDef->GetLinkedClass(); $sLinkedClass = $oAttDef->GetLinkedClass();
@@ -897,7 +902,8 @@ HTML
// the caller may override some flags if needed // the caller may override some flags if needed
$iFlags = $iFlags | $aExtraFlags[$sAttCode]; $iFlags = $iFlags | $aExtraFlags[$sAttCode];
} }
if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) { $bIsLinkSetWithDisplayStyleTab = is_a($oAttDef, AttributeLinkedSet::class) && $oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB;
if ((($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard) && !$bIsLinkSetWithDisplayStyleTab) {
$sInputId = $this->m_iFormId.'_'.$sAttCode; $sInputId = $this->m_iFormId.'_'.$sAttCode;
if ($oAttDef->IsWritable()) { if ($oAttDef->IsWritable()) {
$sInputType = ''; $sInputType = '';
@@ -905,8 +911,8 @@ HTML
// State attribute is always read-only from the UI // State attribute is always read-only from the UI
$sHTMLValue = $this->GetAsHTML($sAttCode); $sHTMLValue = $this->GetAsHTML($sAttCode);
$val = array( $val = array(
'label' => '<label>'.$oAttDef->GetLabel().'</label>', 'label' => '<label>'.$oAttDef->GetLabel().'</label>',
'value' => $sHTMLValue, 'value' => $sHTMLValue,
'input_id' => $sInputId, 'input_id' => $sInputId,
'comments' => $sComments, 'comments' => $sComments,
'infos' => $sInfos, 'infos' => $sInfos,
@@ -935,7 +941,11 @@ HTML
} else { } else {
$sValue = $this->Get($sAttCode); $sValue = $this->Get($sAttCode);
$sDisplayValue = $this->GetEditValue($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode);
// transfer bulk context to components as it can be needed (linked set)
$aArgs = array('this' => $this, 'formPrefix' => $sPrefix); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
if (array_key_exists('bulk_context', $aExtraParams)) {
$aArgs['bulk_context'] = $aExtraParams['bulk_context'];
}
$sHTMLValue = "".self::GetFormElementForField( $sHTMLValue = "".self::GetFormElementForField(
$oPage, $sClass, $sAttCode, $oAttDef, $sValue, $oPage, $sClass, $sAttCode, $oAttDef, $sValue,
$sDisplayValue, $sInputId, '', $iFlags, $aArgs, $sDisplayValue, $sInputId, '', $iFlags, $aArgs,
@@ -2019,7 +2029,15 @@ HTML
} }
$sHTMLValue = ''; $sHTMLValue = '';
if (!$oAttDef->IsExternalField()) {
// attributes not compatible with bulk modify
$bAttNotCompatibleWithBulk = array_key_exists('bulk_context', $aArgs) && !$oAttDef->IsBulkModifyCompatible();
if ($bAttNotCompatibleWithBulk) {
$oTagSetBlock = new Html('<span class="ibo-bulk--bulk-modify--incompatible-attribute">'.Dict::S('UI:Bulk:modify:IncompatibleAttribute').'</span>');
$sHTMLValue = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oTagSetBlock);
}
if (!$oAttDef->IsExternalField() && !$bAttNotCompatibleWithBulk) {
$bMandatory = 'false'; $bMandatory = 'false';
if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) {
$bMandatory = 'true'; $bMandatory = 'true';
@@ -2313,17 +2331,29 @@ EOF
break; break;
case 'LinkedSet': case 'LinkedSet':
$sInputType = self::ENUM_INPUT_TYPE_LINKEDSET; if ($oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_PROPERTY) {
if ($oAttDef->IsIndirect()) { if (array_key_exists('bulk_context', $aArgs)) {
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oTagSetBlock = LinksSetUIBlockFactory::MakeForBulkLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName, $aArgs['bulk_context']);
$oAttDef->DuplicatesAllowed()); } else {
$oTagSetBlock = LinksSetUIBlockFactory::MakeForLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName);
}
$oTagSetBlock->SetName("attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}");
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
$sHTMLValue = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oTagSetBlock);
} else { } else {
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix); $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 = $aArgs['this'] ?? null;
$sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj);
} }
$aEventsList[] = 'validate';
$aEventsList[] = 'change';
$oObj = isset($aArgs['this']) ? $aArgs['this'] : null;
$sHTMLValue = $oWidget->Display($oPage, $value, array(), $sFormPrefix, $oObj);
break; break;
case 'Document': case 'Document':
@@ -3550,17 +3580,14 @@ EOF
protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode) protected function GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode)
{ {
$retVal = null; $retVal = null;
if ($this->IsNew()) if ($this->IsNew()) {
{
$iFlags = $this->GetInitialStateAttributeFlags($sAttCode); $iFlags = $this->GetInitialStateAttributeFlags($sAttCode);
} } else {
else
{
$iFlags = $this->GetAttributeFlags($sAttCode); $iFlags = $this->GetAttributeFlags($sAttCode);
} }
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ((!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard)) $bIsLinkSetWithDisplayStyleTab = is_a($oAttDef, AttributeLinkedSet::class) && $oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB;
{ if ((($iFlags & OPT_ATT_HIDDEN) == 0) && !($oAttDef instanceof AttributeDashboard) && !$bIsLinkSetWithDisplayStyleTab) {
// First prepare the label // First prepare the label
// - Attribute description // - Attribute description
$sDescription = $oAttDef->GetDescription(); $sDescription = $oAttDef->GetDescription();
@@ -3989,14 +4016,17 @@ HTML;
foreach ($value['to_be_created'] as $aData) foreach ($value['to_be_created'] as $aData)
{ {
$sSubClass = $aData['class']; $sSubClass = $aData['class'];
if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass))) if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass))) {
{
$aObjData = $aData['data']; $aObjData = $aData['data'];
$oLink = MetaModel::NewObject($sSubClass); // Avoid duplicates on bulk modify
$oLink->UpdateObjectFromArray($aObjData); if (!in_array($aObjData[$oAttDef->GetExtKeyToRemote()], $oLinkSet->GetColumnAsArray($oAttDef->GetExtKeyToRemote(), false))
$oLinkSet->AddItem($oLink); || $oAttDef->DuplicatesAllowed()) {
$oLink = MetaModel::NewObject($sSubClass);
$oLink->UpdateObjectFromArray($aObjData);
$oLinkSet->AddItem($oLink);
}
} }
} }
} }
if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0)) if (array_key_exists('to_be_added', $value) && (count($value['to_be_added']) > 0))
{ {
@@ -4202,28 +4232,29 @@ HTML;
case 'LinkedSet': case 'LinkedSet':
/** @var AttributeLinkedSet $oAttDef */ /** @var AttributeLinkedSet $oAttDef */
if ($oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_PROPERTY) {
$sLinkedClass = LinkSetModel::GetLinkedClass($oAttDef);
$sTargetField = LinkSetModel::GetTargetField($oAttDef);
$aOperations = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_operations", '{}', 'raw_data'), true);
$value = LinkSetDataTransformer::Encode($aOperations, $sLinkedClass, $sTargetField);
break;
}
$aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', $aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}',
'raw_data'), true); 'raw_data'), true);
$aToBeCreated = array(); $aToBeCreated = array();
foreach($aRawToBeCreated as $aData) foreach ($aRawToBeCreated as $aData) {
{
$sSubFormPrefix = $aData['formPrefix']; $sSubFormPrefix = $aData['formPrefix'];
$sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass();
$aObjData = array(); $aObjData = array();
foreach($aData as $sKey => $value) foreach ($aData as $sKey => $value) {
{ if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) {
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
{
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]); $oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]);
// Recursing over n:n link datetime attributes // Recursing over n:n link datetime attributes
// Note: We might need to do it with other attribute types, like Document or redundancy setting. // Note: We might need to do it with other attribute types, like Document or redundancy setting.
if ($oLinkAttDef instanceof AttributeDateTime) if ($oLinkAttDef instanceof AttributeDateTime) {
{
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix,
$aMatches[1], $sObjClass, $aData); $aMatches[1], $sObjClass, $aData);
} } else {
else
{
$aObjData[$aMatches[1]] = $value; $aObjData[$aMatches[1]] = $value;
} }
} }
@@ -4234,25 +4265,19 @@ HTML;
$aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}', $aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}',
'raw_data'), true); 'raw_data'), true);
$aToBeModified = array(); $aToBeModified = array();
foreach($aRawToBeModified as $iObjKey => $aData) foreach($aRawToBeModified as $iObjKey => $aData) {
{
$sSubFormPrefix = $aData['formPrefix']; $sSubFormPrefix = $aData['formPrefix'];
$sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass(); $sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass();
$aObjData = array(); $aObjData = array();
foreach($aData as $sKey => $value) foreach($aData as $sKey => $value) {
{ if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) {
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
{
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]); $oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]);
// Recursing over n:n link datetime attributes // Recursing over n:n link datetime attributes
// Note: We might need to do it with other attribute types, like Document or redundancy setting. // Note: We might need to do it with other attribute types, like Document or redundancy setting.
if ($oLinkAttDef instanceof AttributeDateTime) if ($oLinkAttDef instanceof AttributeDateTime) {
{
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix,
$aMatches[1], $sObjClass, $aData); $aMatches[1], $sObjClass, $aData);
} } else {
else
{
$aObjData[$aMatches[1]] = $value; $aObjData[$aMatches[1]] = $value;
} }
} }
@@ -4261,13 +4286,13 @@ HTML;
} }
$value = array( $value = array(
'to_be_created' => $aToBeCreated, 'to_be_created' => $aToBeCreated,
'to_be_modified' => $aToBeModified, 'to_be_modified' => $aToBeModified,
'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]',
'raw_data'), true), 'raw_data'), true),
'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]',
'raw_data'), true), 'raw_data'), true),
'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]',
'raw_data'), true), 'raw_data'), true),
); );
break; break;
@@ -4275,7 +4300,9 @@ HTML;
case 'Set': case 'Set':
case 'TagSet': case 'TagSet':
$sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data'); $sTagSetJson = utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}", null, 'raw_data');
if ($sTagSetJson !== null) { // bulk modify, direct linked set not handled
$value = json_decode($sTagSetJson, true); $value = json_decode($sTagSetJson, true);
}
break; break;
default: default:
@@ -4896,13 +4923,12 @@ HTML
$sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")"; $sOQL = "SELECT $sClass WHERE id IN (".$sSelectedObj.")";
$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL)); $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL));
// Compute the distribution of the values for each field to determine which of the "scalar" fields are homogeneous // Compute the distribution of the values for each field to determine which of the "scalar or linked set" fields are homogeneous
$aList = MetaModel::ListAttributeDefs($sClass); $aList = MetaModel::ListAttributeDefs($sClass);
$aValues = array(); $aValues = array();
foreach($aList as $sAttCode => $oAttDef) foreach($aList as $sAttCode => $oAttDef)
{ {
if ($oAttDef->IsScalar()) if ($oAttDef->IsBulkModifyCompatible()) {
{
$aValues[$sAttCode] = array(); $aValues[$sAttCode] = array();
} }
} }
@@ -4910,26 +4936,27 @@ HTML
{ {
foreach($aList as $sAttCode => $oAttDef) foreach($aList as $sAttCode => $oAttDef)
{ {
if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) if ($oAttDef->IsBulkModifyCompatible() && $oAttDef->IsWritable()) {
{
$currValue = $oObj->Get($sAttCode); $currValue = $oObj->Get($sAttCode);
if ($oAttDef instanceof AttributeCaseLog) $editValue = '';
{ if ($oAttDef instanceof AttributeCaseLog) {
$currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059. $currValue = ''; // Put a single scalar value to force caselog to mock a new entry. For more info see N°1059.
} } elseif ($currValue instanceof ormSet) {
elseif ($currValue instanceof ormSet)
{
$currValue = $oAttDef->GetEditValue($currValue, $oObj); $currValue = $oAttDef->GetEditValue($currValue, $oObj);
} else if ($currValue instanceof ormLinkSet) {
$sHtmlValue = $oAttDef->GetAsHTML($currValue);
$editValue = $oAttDef->GetEditValue($currValue, $oObj);
$currValue = $sHtmlValue;
} }
if (is_object($currValue)) if (is_object($currValue)) {
{
continue; continue;
} // Skip non scalar values... } // Skip non scalar values...
if (!array_key_exists($currValue, $aValues[$sAttCode])) if (!array_key_exists($currValue, $aValues[$sAttCode]))
{ {
$aValues[$sAttCode][$currValue] = array( $aValues[$sAttCode][$currValue] = array(
'count' => 1, 'count' => 1,
'display' => $oObj->GetAsHTML($sAttCode), 'display' => $oObj->GetAsHTML($sAttCode),
'edit_value' => $editValue,
); );
} }
else else
@@ -4971,7 +4998,7 @@ HTML
$sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']"; $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']";
$oP->add_ready_script("$('#enable_{$sFormPrefix}{$sAttCode}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); $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->IsBulkModifyCompatible() && $oAttDef->IsWritable()) {
if ($oAttDef->GetEditClass() == 'One Way Password') { if ($oAttDef->GetEditClass() == 'One Way Password') {
$sTip = Dict::S('UI:Component:Field:BulkModify:UnknownValues:Tooltip'); $sTip = Dict::S('UI:Component:Field:BulkModify:UnknownValues:Tooltip');
@@ -4987,7 +5014,13 @@ HTML
reset($aValues[$sAttCode]); reset($aValues[$sAttCode]);
$aKeys = array_keys($aValues[$sAttCode]); $aKeys = array_keys($aValues[$sAttCode]);
$currValue = $aKeys[0]; // The only value is the first key $currValue = $aKeys[0]; // The only value is the first key
$oDummyObj->Set($sAttCode, $currValue); if ($oAttDef->GetEditClass() == 'LinkedSet') {
$oOrmLinkSet = $oDummyObj->Get($sAttCode);
LinkSetDataTransformer::StringToOrmLinkSet($aValues[$sAttCode][$currValue]['edit_value'], $oOrmLinkSet);
} else {
$oDummyObj->Set($sAttCode, $currValue);
}
$aComments[$sAttCode] = ''; $aComments[$sAttCode] = '';
$sValueCheckbox = ''; $sValueCheckbox = '';
if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) { if ($sAttCode != MetaModel::GetStateAttributeCode($sClass) || !MetaModel::HasLifecycle($sClass)) {
@@ -5035,6 +5068,12 @@ HTML
$oTagSet->GenerateDiffFromArray($aTagCodes); $oTagSet->GenerateDiffFromArray($aTagCodes);
} }
$oDummyObj->Set($sAttCode, $oTagSet); $oDummyObj->Set($sAttCode, $oTagSet);
} else if ($oAttDef->GetEditClass() == 'LinkedSet') {
$oOrmLinkSet = $oDummyObj->Get($sAttCode);
foreach ($aMultiValues as $key => $sValue) {
LinkSetDataTransformer::StringToOrmLinkSet($sValue['edit_value'], $oOrmLinkSet);
}
} else { } else {
$oDummyObj->Set($sAttCode, null); $oDummyObj->Set($sAttCode, null);
} }
@@ -5070,15 +5109,18 @@ HTML
$aParams = array $aParams = array
( (
'fieldsComments' => $aComments, 'fieldsComments' => $aComments,
'noRelations' => true, 'noRelations' => true,
'custom_operation' => $sCustomOperation, 'custom_operation' => $sCustomOperation,
'custom_button' => Dict::S('UI:Button:PreviewModifications'), 'custom_button' => Dict::S('UI:Button:PreviewModifications'),
'selectObj' => $sSelectedObj, 'selectObj' => $sSelectedObj,
'nbBulkObj' => $iAllowedCount, 'nbBulkObj' => $iAllowedCount,
'preview_mode' => true, 'preview_mode' => true,
'disabled_fields' => $sDisableFields, 'disabled_fields' => $sDisableFields,
'disable_plugins' => true, 'disable_plugins' => true,
'bulk_context' => [
'oql' => $sOQL,
],
); );
$aParams = $aParams + $aContextData; // merge keeping associations $aParams = $aParams + $aContextData; // merge keeping associations

View File

@@ -60,16 +60,20 @@ class WizardHelper
// special handling for lists // special handling for lists
// assumes this is handled as an array of objects // assumes this is handled as an array of objects
// thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...] // thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...]
$aData = json_decode($value, true); // true means decode as a hash array (not an object) if (!is_array($value)) {
$aData = json_decode($value, true); // true means decode as a hash array (not an object)
} else {
$aData = $value;
}
// Check what are the meaningful attributes // Check what are the meaningful attributes
$aFields = $this->GetLinkedWizardStructure($oAttDef); $aFields = $this->GetLinkedWizardStructure($oAttDef);
$sLinkedClass = $oAttDef->GetLinkedClass(); $sLinkedClass = $oAttDef->GetLinkedClass();
$aLinkedObjectsArray = array(); $aLinkedObjectsArray = array();
if (!is_array($aData)) if (!is_array($aData)) {
{ echo("aData: '$aData' (value: '$value')\n");
echo ("aData: '$aData' (value: '$value')\n");
} }
foreach($aData as $aLinkedObject) foreach ($aData as $aLinkedObject)
{ {
$oLinkedObj = MetaModel::NewObject($sLinkedClass); $oLinkedObj = MetaModel::NewObject($sLinkedClass);
foreach($aFields as $sLinkedAttCode) foreach($aFields as $sLinkedAttCode)

View File

@@ -5,12 +5,15 @@
*/ */
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
use Combodo\iTop\Application\UI\Links\Indirect\BlockLinksSetDisplayAsProperty;
use Combodo\iTop\Form\Field\LabelField; use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Field\TextAreaField; use Combodo\iTop\Form\Field\TextAreaField;
use Combodo\iTop\Form\Form; use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator; use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
use Combodo\iTop\Form\Validator\Validator; use Combodo\iTop\Form\Validator\Validator;
use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Service\Links\LinkSetModel;
require_once('MyHelpers.class.inc.php'); require_once('MyHelpers.class.inc.php');
require_once('ormdocument.class.inc.php'); require_once('ormdocument.class.inc.php');
@@ -92,6 +95,9 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re
define('LINKSET_RELATIONTYPE_PROPERTY', 'property'); define('LINKSET_RELATIONTYPE_PROPERTY', 'property');
define('LINKSET_RELATIONTYPE_LINK', 'link'); define('LINKSET_RELATIONTYPE_LINK', 'link');
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
/** /**
* Attributes implementing this interface won't be accepted as `group by` field * Attributes implementing this interface won't be accepted as `group by` field
* *
@@ -140,6 +146,15 @@ abstract class AttributeDefinition
abstract public function GetEditClass(); abstract public function GetEditClass();
/**
* @return array Css classes
* @since 3.1.0 N°3190
*/
public function GetCssClasses(): array
{
return $this->aCSSClasses;
}
/** /**
* Return the search widget type corresponding to this attribute * Return the search widget type corresponding to this attribute
* *
@@ -361,6 +376,18 @@ abstract class AttributeDefinition
return false; return false;
} }
/**
* Returns true if the attribute can be used in bulk modify.
*
* @return bool
* @since 3.1.0 N°3190
*
*/
public static function IsBulkModifyCompatible(): bool
{
return static::IsScalar();
}
/** /**
* Returns true if the attribute value is a set of related objects (1-N or N-N) * Returns true if the attribute value is a set of related objects (1-N or N-N)
* *
@@ -1417,6 +1444,7 @@ class AttributeLinkedSet extends AttributeDefinition
public function __construct($sCode, $aParams) public function __construct($sCode, $aParams)
{ {
parent::__construct($sCode, $aParams); parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-set';
} }
public static function ListExpectedParams() public static function ListExpectedParams()
@@ -1430,6 +1458,12 @@ class AttributeLinkedSet extends AttributeDefinition
return "LinkedSet"; return "LinkedSet";
} }
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return false;
}
public function IsWritable() public function IsWritable()
{ {
return true; return true;
@@ -1447,7 +1481,26 @@ class AttributeLinkedSet extends AttributeDefinition
public function GetValuesDef() public function GetValuesDef()
{ {
return $this->Get("allowed_values"); $oValSetDef = $this->Get("allowed_values");
if (!$oValSetDef) {
// Let's propose every existing value
$oValSetDef = new ValueSetObjects('SELECT '.LinkSetModel::GetTargetClass($this));
}
return $oValSetDef;
}
public function GetEditValue($value, $oHostObj = null)
{
/** @var ormLinkSet $value * */
if ($value->Count() === 0) {
return '';
}
/** Return linked objects key as string **/
$aValues = $value->GetValues();
return implode(' ', $aValues);
} }
public function GetPrerequisiteAttributes($sClass = null) public function GetPrerequisiteAttributes($sClass = null)
@@ -1532,6 +1585,15 @@ class AttributeLinkedSet extends AttributeDefinition
return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK); return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK);
} }
/**
* @return string see LINKSET_DISPLAY_STYLE_* constants
* @since 3.1.0 N°3190
*/
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', LINKSET_DISPLAY_STYLE_TAB);
}
/** /**
* @return boolean * @return boolean
* @since 3.1.0 N°5563 * @since 3.1.0 N°5563
@@ -1566,49 +1628,30 @@ class AttributeLinkedSet extends AttributeDefinition
return ''; return '';
} }
/** /** @inheritDoc * */
* @param string $sValue public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true): string
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws \CoreException
*/
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{ {
if (is_object($sValue) && ($sValue instanceof ormLinkSet)) try {
{
$sValue->Rewind(); /** @var ormLinkSet $sValue */
$aItems = array(); if ($sValue->Count() === 0) {
while ($oObj = $sValue->Fetch()) return '';
{
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
{
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
$sAttValue = $oObj->GetAsHTML($sAttCode);
if (strlen($sAttValue) > 0)
{
$aAttributes[] = $sAttValue;
}
}
$sAttributes = implode(', ', $aAttributes);
$aItems[] = $sAttributes;
} }
return implode('<br/>', $aItems); $oLinkSetBlock = new BlockLinksSetDisplayAsProperty($this->GetCode(), $this, $sValue);
}
return null; return ConsoleBlockRenderer::RenderBlockTemplates($oLinkSetBlock);
}
catch (Exception $e) {
$sMessage = "Error while displaying attribute {$this->GetCode()}";
IssueLog::Error($sMessage, IssueLog::CHANNEL_DEFAULT, [
'host_object_class' => $this->GetHostClass(),
'host_object_key' => $oHostObject->GetKey(),
'attribute' => $this->GetCode(),
]);
return $sMessage;
}
} }
/** /**
@@ -2361,6 +2404,13 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
return $oRet; return $oRet;
} }
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return true;
}
} }
/** /**

View File

@@ -847,11 +847,30 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
} }
$oLinkSet = new DBObjectSet($oLinkSearch); $oLinkSet = new DBObjectSet($oLinkSearch);
$oLinkSet->SetShowObsoleteData($bShowObsolete); $oLinkSet->SetShowObsoleteData($bShowObsolete);
if ($this->HasDelta()) if ($this->HasDelta()) {
{
$oLinkSet->AddObjectArray($this->aAdded); $oLinkSet->AddObjectArray($this->aAdded);
} }
return $oLinkSet; return $oLinkSet;
} }
/**
* GetValues.
*
* @return array of tag codes
*/
public function GetValues()
{
$aValues = array();
foreach ($this->aPreserved as $sTagCode => $oTag) {
$aValues[] = $sTagCode;
}
foreach ($this->aAdded as $sTagCode => $oTag) {
$aValues[] = $sTagCode;
}
sort($aValues);
return $aValues;
}
} }

View File

@@ -3,5 +3,7 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
@import "bulk/all";
@import "display-block/all"; @import "display-block/all";
@import "linked-set/all";
@import "tabular-fields/all"; @import "tabular-fields/all";

View File

@@ -0,0 +1,6 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "bulk-modify";

View File

@@ -0,0 +1,14 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-bulk--bulk-modify--incompatible-attribute {
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
}
}

View File

@@ -0,0 +1,6 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "linked-set-selector";

View File

@@ -0,0 +1,14 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-linked-set--bulk-tooltip-info {
font-size: $ibo-font-size-100;
&:before{
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
@extend %fa-solid-base;
content: '\f05a';
color: $ibo-color-information-500;
}
}

View File

@@ -4,10 +4,95 @@
*/ */
$ibo-vendors-selectize-input--color: $ibo-color-grey-900 !default; $ibo-vendors-selectize-input--color: $ibo-color-grey-900 !default;
$ibo-vendors-selectize-control--plugin-add-button--add-option--right: $ibo-spacing-0 !default;
$ibo-vendors-selectize-control--plugin-add-button--add-option--height: 100% !default;
$ibo-vendors-selectize-control--plugin-add-button--add-option--width: $ibo-size-350 !default;
$ibo-vendors-selectize-control--plugin-add-button--add-option--color: $ibo-color-grey-900 !default;
$ibo-vendors-selectize--item--icon--margin-right: $ibo-spacing-200 !default;
$ibo-vendors-selectize--item--add--background-color: $ibo-color-green-100 !default;
$ibo-vendors-selectize--item--add--icon--color: $ibo-color-green-900 !default;
$ibo-vendors-selectize--item--remove--background-color: $ibo-color-red-100 !default;
$ibo-vendors-selectize--item--remove--icon--color: $ibo-color-red-800 !default;
$ibo-vendors-selectize--item--ignore-partial--background-color: $ibo-color-grey-200 !default;
$ibo-vendors-selectize--input-error--border: 1px solid $ibo-color-red-600 !default;
.selectize-dropdown-content { .selectize-dropdown-content {
max-height: unset; /* Overloaded as it will be handled by the _input-select.scss partial */ max-height: unset; /* Overloaded as it will be handled by the _input-select.scss partial */
} }
.selectize-input input{ .selectize-input input{
color: $ibo-vendors-selectize-input--color; color: $ibo-vendors-selectize-input--color;
}
.selectize-control.plugin-combodo_add_button{
display: flex;
.selectize-add-option {
position: absolute;
right: $ibo-vendors-selectize-control--plugin-add-button--add-option--right;
display: inline-flex;
justify-content: center;
align-items: center;
height: $ibo-vendors-selectize-control--plugin-add-button--add-option--height;
width: $ibo-vendors-selectize-control--plugin-add-button--add-option--width;
z-index: 1;
color: $ibo-vendors-selectize-control--plugin-add-button--add-option--color;
}
}
// Simple options renderer
.simple-option-renderer--container {
display: flex;
align-items: center;
}
.simple-option-renderer--container--icon {
width: 25px;
text-align: center;
}
.simple-option-renderer--container--label {
margin-left: 3px;
flex-grow: 1;
}
.selectize-input{
.attribute-set-item{
>* {
display: inline;
}
&.item-add::before,&.item-remove::before{
@extend %fa-solid-base;
margin-right: $ibo-vendors-selectize--item--icon--margin-right;
}
&.item-add{
background-color: $ibo-vendors-selectize--item--add--background-color !important;
&::before{
color: $ibo-vendors-selectize--item--add--icon--color;
content: '\f067';
}
}
&.item-remove{
background-color: $ibo-vendors-selectize--item--remove--background-color !important;
&::before{
color: $ibo-vendors-selectize--item--remove--icon--color;
content: '\f1f8';
}
}
&.item-ignore-partial{
background-color: $ibo-vendors-selectize--item--ignore-partial--background-color !important;
}
}
&.selectize-input-error{
border: $ibo-vendors-selectize--input-error--border;
}
} }

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Dict::Add('EN US', 'English', 'English', array(
// Bulk modify
'UI:Bulk:modify:IncompatibleAttribute' => 'This attribute can\'t be edited in bulk context',
));

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Dict::Add('FR FR', 'French', 'Français', array(
// Bulk modify
'UI:Bulk:modify:IncompatibleAttribute' => 'Cet attribut ne peut être édité dans une modification en masse',
));

View File

@@ -17,17 +17,31 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
*/ */
// Display DataTable
Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array(
'UI:Links:ActionRow:Detach' => 'Detach',
'UI:Links:ActionRow:Detach+' => 'Detach this object', // Action row
'UI:Links:ActionRow:Detach:Confirmation' => 'Do you really want to detach <b>{item}</b> from current object ?', 'UI:Links:ActionRow:Detach' => 'Detach',
'UI:Links:ActionRow:Delete' => 'Delete', 'UI:Links:ActionRow:Detach+' => 'Detach this object',
'UI:Links:ActionRow:Delete+' => 'Delete this object', 'UI:Links:ActionRow:Detach:Confirmation' => 'Do you really want to detach <b>{item}</b> from current object ?',
'UI:Links:ActionRow:Delete:Confirmation' => 'Do you really want to delete <b>{item}</b> from current object ?', 'UI:Links:ActionRow:Delete' => 'Delete',
'UI:Links:ActionRow:Modify' => 'Modify', 'UI:Links:ActionRow:Delete+' => 'Delete this object',
'UI:Links:ActionRow:Modify+' => 'Modify this object', 'UI:Links:ActionRow:Delete:Confirmation' => 'Do you really want to delete <b>{item}</b> from current object ?',
'UI:Links:ActionRow:Modify:Modal:Title' => 'Modify a link', 'UI:Links:ActionRow:Modify' => 'Modify',
'UI:Links:New:Modal:Title' => 'Creation of a link', 'UI:Links:ActionRow:Modify+' => 'Modify this object',
'UI:Links:New:Button:Tooltip' => 'Add a new link', 'UI:Links:ActionRow:Modify:Modal:Title' => 'Modify a link',
// New
'UI:Links:New:Modal:Title' => 'Creation of a link',
'UI:Links:New:Button:Tooltip' => 'Add a new link',
// Bulk
'UI:Links:Bulk:LinkWillBeCreatedForAllObjects' => 'Link all objects',
'UI:Links:Bulk:LinkWillBeDeletedFromAllObjects' => 'Unlink all objects',
'UI:Links:Bulk:LinkWillBeCreatedFor1Object' => 'Link one object',
'UI:Links:Bulk:LinkWillBeDeletedFrom1Object' => 'Unlink one object',
'UI:Links:Bulk:LinkWillBeCreatedForXObjects' => 'Link {count} objects',
'UI:Links:Bulk:LinkWillBeDeletedFromXObjects' => 'Unlink {count} objects',
'UI:Links:Bulk:LinkExistForAllObjects' => 'All objets are already linked',
'UI:Links:Bulk:LinkExistForOneObject' => 'One object is linked',
'UI:Links:Bulk:LinkExistForXObjects' => '{count} objects are linked',
)); ));

View File

@@ -17,12 +17,24 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
*/ */
// Display DataTable
Dict::Add('FR FR', 'French', 'Français', array( Dict::Add('FR FR', 'French', 'Français', array(
'UI:Links:ActionRow:Detach' => 'Détacher',
'UI:Links:ActionRow:Detach+' => 'Détacher cet objet', // Action row
'UI:Links:ActionRow:Detach:Confirmation' => 'Voulez-vous détacher <b>{item}</b> de l\'objet courant ?', 'UI:Links:ActionRow:Detach' => 'Détacher',
'UI:Links:ActionRow:Delete' => 'Supprimer', 'UI:Links:ActionRow:Detach+' => 'Détacher cet objet',
'UI:Links:ActionRow:Delete+' => 'Supprimer cet objet', 'UI:Links:ActionRow:Detach:Confirmation' => 'Voulez-vous détacher <b>{item}</b> de l\'objet courant ?',
'UI:Links:ActionRow:Delete:Confirmation' => 'Voulez-vous supprimer <b>{item}</b> de l\'objet courant ?', 'UI:Links:ActionRow:Delete' => 'Supprimer',
'UI:Links:ActionRow:Delete+' => 'Supprimer cet objet',
'UI:Links:ActionRow:Delete:Confirmation' => 'Voulez-vous supprimer <b>{item}</b> de l\'objet courant ?',
// Bulk
'UI:Links:Bulk:LinkWillBeCreatedForAllObjects' => 'Lier à tous les objets',
'UI:Links:Bulk:LinkWillBeDeletedFromAllObjects' => 'Détacher de tous les objets',
'UI:Links:Bulk:LinkWillBeCreatedFor1Object' => 'Lier à un objet',
'UI:Links:Bulk:LinkWillBeDeletedFrom1Object' => 'Détacher de un objet',
'UI:Links:Bulk:LinkWillBeCreatedForXObjects' => 'Lier à {count} objets',
'UI:Links:Bulk:LinkWillBeDeletedFromXObjects' => 'Détacher de {count} objets',
'UI:Links:Bulk:LinkExistForAllObjects' => 'Tous les objets sont déjà liés',
'UI:Links:Bulk:LinkExistForOneObject' => 'Un objet est lié',
'UI:Links:Bulk:LinkExistForXObjects' => '{count} objets sont liés',
)); ));

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Selectize.define("combodo_add_button", function (aOptions) {
// Selectize instance
let oSelf = this;
// Plugin options
aOptions = $.extend({
title: "Add Option",
className: "selectize-add-option",
label: "+",
html: function () {
return (
'<a class="' + this.className + ' fas fa-plus" title="' + this.title + '"></a>'
);
},
},
aOptions
);
// Override setup function
oSelf.setup = (function () {
let oOriginal = oSelf.setup;
return function () {
oOriginal.apply(oSelf, arguments);
oSelf.$buttonAdd = $(aOptions.html());
oSelf.$wrapper.append(oSelf.$buttonAdd);
if(oSelf.settings.hasOwnProperty('onAdd')) {
oSelf.on('add', oSelf.settings['onAdd']);
oSelf.$buttonAdd.on('click', function(){
oSelf.trigger( "add");
});
}
else{
oSelf.$buttonAdd.css({
opacity: .5,
cursor: 'default'
});
}
};
})();
});

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Selectize.define("combodo_auto_position", function (aOptions) {
// Selectize instance
let oSelf = this;
// Plugin options
aOptions = $.extend({
maxDropDownHeight: 200,
},
aOptions
);
// override settings
oSelf.settings.dropdownParent = 'body';
// Override position dropdown function
oSelf.positionDropdown = (function () {
return function () {
let iRefHeight = oSelf.$dropdown.outerHeight() < aOptions.maxDropDownHeight ?
oSelf.$dropdown.outerHeight() : aOptions.maxDropDownHeight;
if(oSelf.$control.offset().top + oSelf.$control.outerHeight() + iRefHeight > window.innerHeight){
oSelf.$dropdown.css({
top: oSelf.$control.offset().top - iRefHeight,
left: oSelf.$control.offset().left,
width: oSelf.$wrapper.outerWidth(),
'max-height': `${aOptions.maxDropDownHeight}px`,
'overflow-y': 'auto',
'border-top': '1px solid #d0d0d0',
});
}
else{
oSelf.$dropdown.css({
top: oSelf.$control.offset().top + oSelf.$control.outerHeight(),
left: oSelf.$control.offset().left,
width: oSelf.$wrapper.outerWidth(),
'max-height': `${aOptions.maxDropDownHeight}px`,
'overflow-y': 'auto'
});
}
};
}());
});

View File

@@ -0,0 +1,280 @@
/*
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Selectize.define("combodo_multi_values_synthesis", function (aOptions) {
// Selectize instance
let oSelf = this;
oSelf.require("combodo_update_operations");
// Plugin options
aOptions = $.extend({
tooltip_links_will_be_created_for_all_objects: 'Links will be created for all objects',
tooltip_links_will_be_deleted_from_all_objects: 'Links will be deleted from all objects',
tooltip_links_will_be_created_for_one_object: 'Links will be created for one object',
tooltip_links_will_be_deleted_from_one_object: 'Links will be deleted from one object',
tooltip_links_will_be_created_for_x_objects: 'Links will be created for {count} objects',
tooltip_links_will_be_deleted_from_x_objects: 'Links will be deleted from {count} objects',
tooltip_links_exist_for_all_objects: 'Links exist for all objects',
tooltip_links_exist_for_one_object: 'Links exist for one object',
tooltip_links_exist_for_x_objects: 'Links exist for some objects'
},
aOptions
);
// Items operations
const OPERATIONS = {
add: 'add',
remove: 'remove',
ignore: 'ignore',
};
// Items states css classes
const ITEMS_CLASSES = {
add: 'item-add',
remove: 'item-remove',
ignore_all: 'item-ignore-all',
ignore_partial: 'item-ignore-partial'
};
// Local operations
let aOperations = {};
// Override addItem function
oSelf.addItem = (function () {
let oOriginal = oSelf.addItem;
return function () {
oOriginal.apply(this, arguments);
// Retrieve item and item element
const sItemValue = arguments[0];
const $Item = oSelf.getItem(sItemValue);
// Restore operation if exist and return
if(typeof(aOperations[sItemValue]) !== 'undefined'){
if(aOperations[sItemValue] === OPERATIONS.add){
oSelf.Add($Item, sItemValue);
}
else if(aOperations[sItemValue] === OPERATIONS.remove){
oSelf.Remove($Item, sItemValue);
}
else if(aOperations[sItemValue] === OPERATIONS.ignore){
oSelf.Ignore($Item, sItemValue);
}
// Element exist in default selection,
// click allow user to switch between add or ignore states
if(oSelf.settings.initial.includes(sItemValue)) {
oSelf.listenClick($Item, sItemValue);
}
return;
}
// If no operation to restore
if(!oSelf.settings.initial.includes(sItemValue)) {
// Element doesn't exist in initial value, we mark it as added
oSelf.Add($Item, sItemValue);
}
else{
// Element exist, we restore it
oSelf.Ignore($Item, sItemValue);
// Element exist in default selection,
// click allow user to switch between add or ignore states
oSelf.listenClick($Item, sItemValue);
}
}
})();
// Override removeItem function
oSelf.removeItem = (function () {
let oOriginal = oSelf.removeItem;
return function () {
// Retrieve item and item element
const sItem = arguments[0];
const $Item = oSelf.getItem(sItem);
// Element doesn't exist in default selection,
if(!oSelf.settings.initial.includes(sItem)) {
// Remove operation
delete aOperations[sItem];
// Call original remove function (element will be removed of the input)
oOriginal.apply(this, arguments);
}
else{
// Store remove operation (element will NOT be removed)
oSelf.Remove($Item, sItem);
}
}
})();
// Override updateOperations function
oSelf.updateOperations = (function () {
let oOriginal = oSelf.updateOperations;
return function () {
// Call original updateOperations function
oOriginal.apply(this, arguments);
// Iterate throw local operations...
const aCurrentOptions = Object.values(oSelf.options);
for (const [key, value] of Object.entries(aOperations)) {
oSelf.operations[key] = {
operation: value,
data: CombodoGlobalToolbox.ExtractArrayItemsContainingThisKeyAndValue(aCurrentOptions, oSelf.settings.valueField, key)
}
}
}
})();
// Declare listenClick function
oSelf.listenClick = (function () {
return function ($item, sItem) {
// Listen item element click event
$item.on('click', function(){
// If element has operation
if(aOperations[sItem] === OPERATIONS.add || aOperations[sItem] === OPERATIONS.remove) {
// Restore state
oSelf.Ignore($item, sItem);
}
else{
// No need to add
if(oSelf.options[sItem]['full'])
return;
// Add element
oSelf.Add($item, sItem);
}
});
}
})();
// Declare Add function
oSelf.Add = (function () {
return function ($item, sItem) {
aOperations[sItem] = OPERATIONS.add;
oSelf.updateOperationsInput();
oSelf.ResetElementClass($item);
oSelf.UpdateAllTooltip($item, sItem);
$item.addClass(ITEMS_CLASSES.add);
}
})();
// Declare Remove function
oSelf.Remove = (function () {
return function ($item, sItem) {
aOperations[sItem] = OPERATIONS.remove;
oSelf.updateOperationsInput();
oSelf.ResetElementClass($item);
oSelf.UpdateRemoveTooltip($item, sItem);
$item.addClass(ITEMS_CLASSES.remove);
}
})();
// Declare Ignore function
oSelf.Ignore = (function () {
return function ($item, sItem) {
aOperations[sItem] = OPERATIONS.ignore;
oSelf.updateOperationsInput();
oSelf.ResetElementClass($item);
oSelf.UpdateIgnoreTooltip($item, sItem);
oSelf.options[sItem]['full'] ?
$item.addClass(ITEMS_CLASSES.ignore_all) :
$item.addClass(ITEMS_CLASSES.ignore_partial);
}
})();
// Declare ResetElementClass function
oSelf.ResetElementClass = (function () {
return function ($item) {
$item.removeClass(Object.values(ITEMS_CLASSES));
}
})();
// Update add tooltip
oSelf.UpdateAllTooltip = (function () {
return function ($item, sItem) {
const iOccurrence = oSelf.options[sItem]['occurrence'];
let sTooltip = '';
if(oSelf.options[sItem]['empty']){
sTooltip = aOptions.tooltip_links_will_be_created_for_all_objects;
}
else if(iOccurrence === '1'){
sTooltip = aOptions.tooltip_links_will_be_created_for_one_object;
}
else{
sTooltip = aOptions.tooltip_links_will_be_created_for_x_objects.replaceAll('{count}', iOccurrence);
}
oSelf.CreateTooltip($item, sItem, sTooltip);
}
})();
// Update remove tooltip
oSelf.UpdateRemoveTooltip = (function () {
return function ($item, sItem) {
const iOccurrence = oSelf.options[sItem]['occurrence'];
let sTooltip = '';
if(oSelf.options[sItem]['full']){
sTooltip = aOptions.tooltip_links_will_be_deleted_from_all_objects;
}
else if(oSelf.options[sItem]['occurrence'] === '1'){
sTooltip = aOptions.tooltip_links_will_be_deleted_from_one_object;
}
else{
sTooltip = aOptions.tooltip_links_will_be_deleted_from_x_objects.replaceAll('{count}', iOccurrence);
}
oSelf.CreateTooltip($item, sItem, sTooltip);
}
})();
// Update ignore tooltip
oSelf.UpdateIgnoreTooltip = (function () {
return function ($item, sItem) {
const iOccurrence = oSelf.options[sItem]['occurrence'];
let sTooltip = '';
if(oSelf.options[sItem]['full']){
sTooltip = aOptions.tooltip_links_exist_for_all_objects;
}
else if(iOccurrence === '1'){
sTooltip = aOptions.tooltip_links_exist_for_one_object;
}
else{
sTooltip = aOptions.tooltip_links_exist_for_x_objects.replaceAll('{count}', iOccurrence);
}
oSelf.CreateTooltip($item, sItem, sTooltip);
}
})();
// Update ignore tooltip
oSelf.CreateTooltip = (function () {
return function ($item, sItem, sTooltip) {
$item.attr('data-tooltip-content', oSelf.options[sItem][this.settings.tooltipField] + '<br><span class="ibo-linked-set--bulk-tooltip-info">' + sTooltip + '</span>');
$item.attr('data-tooltip-html-enabled', true);
CombodoTooltip.InitTooltipFromMarkup($item, true);
}
})();
});

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
Selectize.define("combodo_update_operations", function () {
// Selectize instance
let oSelf = this;
// Plugin variables
oSelf.bIsInitialized = false;
oSelf.operations = {};
// Override setup function
oSelf.setup = (function () {
let oOriginal = oSelf.setup;
return function () {
oOriginal.apply(oSelf, arguments);
oSelf.$operationsInput = $(`<input type="hidden" value="{}" name="${oSelf.$input.attr('name')}_operations">`)
oSelf.$wrapper.append(oSelf.$operationsInput);
oSelf.bIsInitialized = true;
oSelf.updateOperationsInput();
};
})();
// Override addItem function
oSelf.addItem = (function () {
let oOriginal = oSelf.addItem;
return function () {
oOriginal.apply(this, arguments);
if(oSelf.bIsInitialized && !arguments[1]){
this.updateOperationsInput();
}
}
})();
// Override removeItem function
oSelf.removeItem = (function () {
let oOriginal = oSelf.removeItem;
return function () {
oOriginal.apply(this, arguments);
if(oSelf.bIsInitialized){
this.updateOperationsInput();
}
}
})();
// Declare updateOperationsInput function
oSelf.updateOperationsInput = (function () {
return function () {
// update operations
oSelf.updateOperations();
// setup in progress
if(typeof(oSelf.$operationsInput) === 'undefined'){
return;
}
// update operations input
oSelf.$operationsInput.val(JSON.stringify(oSelf.operations));
};
})();
// Declare updateOperations function
oSelf.updateOperations = (function () {
return function () {
// Reset operations
oSelf.operations = {};
// Reference data
const aCurrentItems = Object.values(oSelf.items);
const aCurrentOptions = Object.values(oSelf.options);
// Scan items in current value and not in initial value
aCurrentItems.forEach(function(e){
if(!oSelf.settings.initial.includes(e)){
oSelf.operations[e] = {
operation: 'add',
data: CombodoGlobalToolbox.ExtractArrayItemsContainingThisKeyAndValue(aCurrentOptions, oSelf.settings.valueField, e)
}
}
});
// scan items in initial value and not in current value
oSelf.settings.initial.forEach(function(e){
if(!aCurrentItems.includes(e)){
oSelf.operations[e] = {
operation: 'remove',
data: CombodoGlobalToolbox.ExtractArrayItemsContainingThisKeyAndValue(aCurrentOptions, oSelf.settings.valueField, e)
}
}
});
};
})();
});

View File

@@ -759,6 +759,105 @@ const CombodoGlobalToolbox = {
oCurrentDate = new Date(); oCurrentDate = new Date();
} }
while ((oCurrentDate - oDate) < iDuration); while ((oCurrentDate - oDate) < iDuration);
},
/**
* Render a template and inject data into it.
*
* This rendering engine is aimed to produce client side template rendering.
*
* markups with attributes:
* data-template-attr-{title|name|for}: set dom element attribute with corresponding datavalue
* data-template-text: set dom element text with corresponding data value
* data-template-condition: set dom element visibility depending on data value
* data-template-css-{background-image}: set dom element css property with corresponding data value
* data-template-add-class: add class to dom element with corresponding data value
*
* @since 3.1.0
*
* @param sTemplateId
* @param aData
* @param sTemplateClass
* @returns {*|jQuery|HTMLElement|JQuery<HTMLElement>}
* @constructor
*/
RenderTemplate: function(sTemplateId, aData, sTemplateClass = null)
{
let sHtml = '<div>' + $(sTemplateId).html() + '</div>';
// Create element
let oElement = $(sHtml);
if(sTemplateClass !== null){
oElement.addClass(sTemplateClass);
}
// Attribute replacement
let aAttrElements = ['title', 'name', 'for'];
aAttrElements.forEach(function(e){
$(`[data-template-attr-${e}]`, oElement).each(function(){
$(this).attr(e, aData[$(this).attr(`data-template-attr-${e}`)]);
})
});
// CSS replacement
let aCssElements = ['background-image'];
aCssElements.forEach(function(e){
$(`[data-template-css-${e}]`, oElement).each(function(){
$(this).css(e, aData[$(this).attr(`data-template-css-${e}`)]);
})
});
// Text replacement
$('[data-template-text]', oElement).each(function(){
$(this).text(aData[$(this).attr('data-template-text')]);
})
// Condition
$('[data-template-condition]', oElement).each(function(){
$(this).toggle(aData[$(this).attr('data-template-condition')]);
})
// Add classes
$('[data-template-add-class]', oElement).each(function(){
$(this).addClass(aData[$(this).attr('data-template-add-class')]);
})
return oElement;
},
/**
* ExtractArrayItemsContainingThisKeyAndValue.
*
* This function extract item(s) of an array witch include the key value pair.
*
* @since 3.1.0
*
* @param aArrayToSearchIn Array to search in
* @param sKey Key to search
* @param sValue Value to search
* @returns {*|*[]|null}
* @constructor
*/
ExtractArrayItemsContainingThisKeyAndValue: function(aArrayToSearchIn, sKey, sValue)
{
let aResult = [];
// Iterate throw items...
for(let i = 0 ; i < aArrayToSearchIn.length ; i++){
if(aArrayToSearchIn[i][sKey] === sValue){
aResult.push(aArrayToSearchIn[i]);
}
}
// Return result
switch(aResult.length){
case 0:
return null;
case 1:
return aResult[0];
default:
return aResult;
}
} }
}; };

View File

@@ -149,7 +149,7 @@ class ClassLoader
/** /**
* @return string[] Array of classname => path * @return string[] Array of classname => path
* @psalm-return array<string, string> * @psalm-var array<string, string>
*/ */
public function getClassMap() public function getClassMap()
{ {

View File

@@ -24,8 +24,21 @@ use Composer\Semver\VersionParser;
*/ */
class InstalledVersions class InstalledVersions
{ {
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
*/
private static $installed; private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors; private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static $installedByVendor = array(); private static $installedByVendor = array();
/** /**

View File

@@ -254,6 +254,13 @@ return array(
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\Select' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/Select.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\Select' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/Select.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOption' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/SelectOption.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOption' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/SelectOption.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOptionUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/SelectOptionUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOptionUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Input/Select/SelectOptionUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AbstractDataProvider' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AbstractDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AjaxDataProvider' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AjaxDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AjaxDataProviderForOQL' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AjaxDataProviderForOql.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\SimpleDataProvider' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/SimpleDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\iDataProvider' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/iDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/Set.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => $baseDir . '/sources/Application/UI/Base/Component/Input/TextArea.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => $baseDir . '/sources/Application/UI/Base/Component/Input/TextArea.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
@@ -349,7 +356,9 @@ return array(
'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockLinksSetDisplayAsProperty' => $baseDir . '/sources/Application/UI/Links/Set/BlockLinksSetDisplayAsProperty.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => $baseDir . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php',
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinksSetUIBlockFactory' => $baseDir . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php', 'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php', 'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
@@ -425,12 +434,18 @@ return array(
'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Router\\Router' => $baseDir . '/sources/Router/Router.php', 'Combodo\\iTop\\Router\\Router' => $baseDir . '/sources/Router/Router.php',
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => $baseDir . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => $baseDir . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => $baseDir . '/sources/Application/Service/Events/Description/EventDataDescription.php', 'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => $baseDir . '/sources/Application/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => $baseDir . '/sources/Application/Service/Events/Description/EventDescription.php', 'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => $baseDir . '/sources/Application/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => $baseDir . '/sources/Application/Service/Events/EventData.php', 'Combodo\\iTop\\Service\\Events\\EventData' => $baseDir . '/sources/Application/Service/Events/EventData.php',
'Combodo\\iTop\\Service\\Events\\EventHelper' => $baseDir . '/sources/Application/Service/Events/EventHelper.php', 'Combodo\\iTop\\Service\\Events\\EventHelper' => $baseDir . '/sources/Application/Service/Events/EventHelper.php',
'Combodo\\iTop\\Service\\Events\\EventService' => $baseDir . '/sources/Application/Service/Events/EventService.php', 'Combodo\\iTop\\Service\\Events\\EventService' => $baseDir . '/sources/Application/Service/Events/EventService.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Application/Service/Events/iEventServiceSetup.php', 'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Application/Service/Events/iEventServiceSetup.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => $baseDir . '/sources/Service/Links/LinkSetDataTransformer.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => $baseDir . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php', 'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Config' => $baseDir . '/core/config.class.inc.php', 'Config' => $baseDir . '/core/config.class.inc.php',

View File

@@ -60,16 +60,11 @@ class ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f
} }
} }
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file) function composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file)
{ {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file; require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
} }
} }

View File

@@ -619,6 +619,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\Select' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/Select.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\Select' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/Select.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOption' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/SelectOption.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOption' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/SelectOption.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOptionUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/SelectOptionUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Select\\SelectOptionUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Select/SelectOptionUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AbstractDataProvider' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AbstractDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AjaxDataProvider' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AjaxDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\AjaxDataProviderForOQL' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/AjaxDataProviderForOql.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\SimpleDataProvider' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/SimpleDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\DataProvider\\iDataProvider' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/DataProvider/iDataProvider.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\Set' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/Set.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\Set\\SetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/Set/SetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/TextArea.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\TextArea' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/TextArea.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php',
'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php',
@@ -714,7 +721,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Direct\\BlockDirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Direct/BlockDirectLinksViewTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksEditTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksEditTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockIndirectLinksViewTable' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockIndirectLinksViewTable.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockLinksSetDisplayAsProperty' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Set/BlockLinksSetDisplayAsProperty.php',
'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php', 'Combodo\\iTop\\Application\\UI\\Links\\Indirect\\BlockObjectPickerDialog' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Indirect/BlockObjectPickerDialog.php',
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinksSetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php', 'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php', 'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php', 'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
@@ -790,12 +799,18 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php',
'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php',
'Combodo\\iTop\\Router\\Router' => __DIR__ . '/../..' . '/sources/Router/Router.php', 'Combodo\\iTop\\Router\\Router' => __DIR__ . '/../..' . '/sources/Router/Router.php',
'Combodo\\iTop\\Service\\Base\\ObjectRepository' => __DIR__ . '/../..' . '/sources/Service/Base/ObjectRepository.php',
'Combodo\\iTop\\Service\\Base\\iDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Base/iDataPostProcessor.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => __DIR__ . '/../..' . '/sources/Application/Service/Events/Description/EventDataDescription.php', 'Combodo\\iTop\\Service\\Events\\Description\\EventDataDescription' => __DIR__ . '/../..' . '/sources/Application/Service/Events/Description/EventDataDescription.php',
'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => __DIR__ . '/../..' . '/sources/Application/Service/Events/Description/EventDescription.php', 'Combodo\\iTop\\Service\\Events\\Description\\EventDescription' => __DIR__ . '/../..' . '/sources/Application/Service/Events/Description/EventDescription.php',
'Combodo\\iTop\\Service\\Events\\EventData' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventData.php', 'Combodo\\iTop\\Service\\Events\\EventData' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventData.php',
'Combodo\\iTop\\Service\\Events\\EventHelper' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventHelper.php', 'Combodo\\iTop\\Service\\Events\\EventHelper' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventHelper.php',
'Combodo\\iTop\\Service\\Events\\EventService' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventService.php', 'Combodo\\iTop\\Service\\Events\\EventService' => __DIR__ . '/../..' . '/sources/Application/Service/Events/EventService.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Application/Service/Events/iEventServiceSetup.php', 'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Application/Service/Events/iEventServiceSetup.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetDataTransformer.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
'Combodo\\iTop\\Service\\Links\\LinksBulkDataPostProcessor' => __DIR__ . '/../..' . '/sources/Service/Links/LinksBulkDataPostProcessor.php',
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php', 'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php', 'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',

View File

@@ -5,7 +5,7 @@
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '1cac1890774b4defa212a142f88348f2f8743f4c', 'reference' => '9482139b5aec9978f05b1cbb0542b24c18681819',
'name' => 'combodo/itop', 'name' => 'combodo/itop',
'dev' => true, 'dev' => true,
), ),
@@ -25,7 +25,7 @@
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'reference' => '1cac1890774b4defa212a142f88348f2f8743f4c', 'reference' => '9482139b5aec9978f05b1cbb0542b24c18681819',
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'combodo/tcpdf' => array( 'combodo/tcpdf' => array(

View File

@@ -1499,6 +1499,14 @@ EOF;
$aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0);
$aParameters['duplicates'] = $this->GetPropBoolean($oField, 'duplicates', false); $aParameters['duplicates'] = $this->GetPropBoolean($oField, 'duplicates', false);
$aParameters['depends_on'] = $sDependencies; $aParameters['depends_on'] = $sDependencies;
$aParameters['display_style'] = $this->GetPropString($oField, 'display_style');
if ($sOql = $oField->GetChildText('filter')) {
$sEscapedOql = self::QuoteForPHP($sOql);
$aParameters['allowed_values'] = "new ValueSetObjects($sEscapedOql)";
} else {
$aParameters['allowed_values'] = 'null';
}
} }
elseif ($sAttType == 'AttributeLinkedSet') elseif ($sAttType == 'AttributeLinkedSet')
{ {
@@ -1506,6 +1514,7 @@ EOF;
$aParameters['ext_key_to_me'] = $this->GetMandatoryPropString($oField, 'ext_key_to_me'); $aParameters['ext_key_to_me'] = $this->GetMandatoryPropString($oField, 'ext_key_to_me');
$aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0); $aParameters['count_min'] = $this->GetPropNumber($oField, 'count_min', 0);
$aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0); $aParameters['count_max'] = $this->GetPropNumber($oField, 'count_max', 0);
$aParameters['display_style'] = $this->GetPropString($oField, 'display_style');
$sEditMode = $oField->GetChildText('edit_mode'); $sEditMode = $oField->GetChildText('edit_mode');
if (!is_null($sEditMode)) if (!is_null($sEditMode))
{ {

View File

@@ -384,6 +384,7 @@
<xsd:element name="ext_key_to_me" type="xsd:string"/> <xsd:element name="ext_key_to_me" type="xsd:string"/>
<xsd:element name="count_min" type="xsd:string"/> <xsd:element name="count_min" type="xsd:string"/>
<xsd:element name="count_max" type="xsd:string"/> <xsd:element name="count_max" type="xsd:string"/>
<xsd:element name="display_style" type="xsd:string" minOccurs="0"/>
</xsd:sequence> </xsd:sequence>
</xsd:extension> </xsd:extension>
</xsd:complexContent> </xsd:complexContent>

View File

@@ -0,0 +1,160 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider;
/**
* Class AbstractDataProvider
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider
* @since 3.1.0
*/
abstract class AbstractDataProvider implements iDataProvider
{
/** @var string $sDataValueField Field used for input value */
private string $sDataValueField;
/** @var string $sDataLabelField Field used for label rendering */
private string $sDataLabelField;
/** @var array $aDataSearchFields Fields used for search engine */
private array $aDataSearchFields;
/** @var string|null $sGroupField Field used for grouping options */
private ?string $sGroupField;
/** @var string|null $sTooltipField Field used for item tooltip */
private ?string $sTooltipField;
/**
* Constructor.
*
*/
public function __construct()
{
// Initialization
$this->Init();
}
/**
* Initialization.
*
* @return void
*/
private function Init()
{
$this->sDataLabelField = 'label';
$this->sDataValueField = 'value';
$this->aDataSearchFields = ['search'];
$this->sGroupField = null;
$this->sTooltipField = 'label';
}
/** @inheritDoc */
public function GetDataValueField(): string
{
return $this->sDataValueField;
}
/** @inheritDoc */
public function SetDataValueField(string $sField): AbstractDataProvider
{
$this->sDataValueField = $sField;
return $this;
}
/** @inheritDoc */
public function GetDataLabelField(): string
{
return $this->sDataLabelField;
}
/** @inheritDoc */
public function SetDataLabelField(string $sField): AbstractDataProvider
{
$this->sDataLabelField = $sField;
return $this;
}
/** @inheritDoc */
public function GetDataSearchFields(): array
{
return $this->aDataSearchFields;
}
/** @inheritDoc */
public function SetDataSearchFields(array $aFields): AbstractDataProvider
{
$this->aDataSearchFields = $aFields;
return $this;
}
/** @inheritDoc */
public function GetGroupField(): ?string
{
return $this->sGroupField;
}
/** @inheritDoc */
public function SetGroupField(string $sField): iDataProvider
{
$this->sGroupField = $sField;
return $this;
}
/** @inheritDoc */
public function GetTooltipField(): ?string
{
return $this->sTooltipField;
}
/** @inheritDoc */
public function SetTooltipField(string $sField): iDataProvider
{
$this->sTooltipField = $sField;
return $this;
}
/**
* IsAjaxProviderType.
*
* @return bool
*/
public function IsAjaxProviderType(): bool
{
return $this->GetType() === iDataProvider::TYPE_AJAX_PROVIDER;
}
/**
* IsSimpleProviderType.
*
* @return bool
*/
public function IsSimpleProviderType(): bool
{
return $this->GetType() === iDataProvider::TYPE_SIMPLE_PROVIDER;
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider;
/**
* Class AjaxDataProvider
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider
* @since 3.1.0
*/
class AjaxDataProvider extends SimpleDataProvider
{
/** @var int DEFAULT_MAX_RESULTS maximum results fetched */
const DEFAULT_MAX_RESULTS = 25;
/**
* @see \Combodo\iTop\Router\Router
* @var string $sAjaxRoute Router route name
*/
private string $sRoute;
/** @var array $aParams Query string params */
private array $aParams = [];
/** @var array $aPostParams Post params */
private array $aPostParams = [];
/** @var int $iMaxResults Maximum entries */
private int $iMaxResults = AjaxDataProvider::DEFAULT_MAX_RESULTS;
/**
* Constructor.
*
* @param string $sRoute Router route name
* @param array $aParams Query string params
* @param array $aPostParams Post params
*/
public function __construct(string $sRoute, array $aParams = [], array $aPostParams = [])
{
parent::__construct();
// Retrieve parameters
$this->sRoute = $sRoute;
$this->aParams = $aParams;
$this->aPostParams = $aPostParams;
}
/** @inheritDoc */
public function GetType(): string
{
return iDataProvider::TYPE_AJAX_PROVIDER;
}
/**
* SetParam.
*
* @param string $sName
* @param string $sValue
*
* @return $this
*/
public function SetParam(string $sName, string $sValue): AjaxDataProvider
{
$this->aParams[$sName] = $sValue;
return $this;
}
/**
* GetParam.
*
* @param string $sName
*
* @return string
*/
public function GetParam(string $sName): string
{
return $this->aParams[$sName];
}
/**
* GetParams.
*
* @return array
*/
public function GetParams(): array
{
return $this->aParams;
}
/**
* GetParamsAsQueryString.
*
* @return string
*/
public function GetParamsAsQueryString(): string
{
$aFlattened = $this->aParams;
array_walk($aFlattened, function (&$sValue, $key) {
$sValue = "{$key}={$sValue}";
});
return '&'.implode('&', $aFlattened);
}
/**
* GetPostParamsAsJsonString.
*
* @return string
*/
public function GetPostParamsAsJsonString(): string
{
return json_encode($this->aPostParams);
}
/**
* SetPostParam.
*
* @param string $sName
* @param $oValue
*
* @return $this
*/
public function SetPostParam(string $sName, $oValue): AjaxDataProvider
{
$this->aPostParams[$sName] = $oValue;
return $this;
}
/**
* GetRoute.
*
* @return void
*/
public function GetRoute(): string
{
return $this->sRoute;
}
/**
* Return maximum results count.
*
* @return int
*/
public function GetMaxResults(): int
{
return $this->iMaxResults;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider;
/**
* Class AjaxDataProviderForOQL
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider
* @since 3.1.0
*/
class AjaxDataProviderForOQL extends AjaxDataProvider
{
/**
* Constructor.
*
* @param string $sObjectClass Db Object class
* @param string $sOql Oql
* @param string|null $sWizardHelperJsVarName Wizard helper name
* @param array $aFieldsToLoad Array of fields to load
*/
public function __construct(string $sObjectClass, string $sOql, string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [])
{
parent::__construct('object.search', [
'object_class' => $sObjectClass,
'oql' => $sOql,
'fields_to_load' => json_encode($aFieldsToLoad),
], [
'this_object_data' => $sWizardHelperJsVarName != null ? "EVAL_JAVASCRIPT{{$sWizardHelperJsVarName}.UpdateWizardToJSON();}" : "",
]);
// Initialization
$this->Init();
}
/**
* Initialization.
*
* @return void
*/
private function Init()
{
$this->SetDataLabelField('friendlyname')
->SetDataValueField('key')
->SetDataSearchFields(['friendlyname', 'additional_field']);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider;
/**
* Class SimpleDataProvider
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider
* @since 3.1.0
*/
class SimpleDataProvider extends AbstractDataProvider
{
/** @var array $aOptions */
private array $aOptions;
/**
* Constructor.
*
* @param array $aOptions
*/
public function __construct(array $aOptions = [])
{
parent::__construct();
// retrieve parameters
$this->SetOptions($aOptions);
}
/** @inheritDoc */
public function GetType(): string
{
return iDataProvider::TYPE_SIMPLE_PROVIDER;
}
/** @inheritDoc */
public function SetOptions(array $aOptions): SimpleDataProvider
{
$this->aOptions = $aOptions;
return $this;
}
/** @inheritDoc */
public function SetOption(string $sKey, string $sValue): SimpleDataProvider
{
$this->aOptions[$sKey] = $sValue;
return $this;
}
/** @inheritDoc */
public function GetOptions(): array
{
return $this->aOptions;
}
/**
* GetOptionsGroups.
*
* @return array
*/
public function GetOptionsGroups(): array
{
$aGroups = [];
if ($this->GetGroupField() != null) {
foreach ($this->GetOptions() as $aOption) {
if (array_key_exists($this->GetGroupField(), $aOption)) {
$aGroups[$aOption[$this->GetGroupField()]] = [
'label' => $aOption[$this->GetGroupField()],
'value' => $aOption[$this->GetGroupField()],
];
}
}
}
return array_values($aGroups);
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider;
/**
* Interface iDataProvider
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider
* @since 3.1.0
*/
interface iDataProvider
{
public const TYPE_AJAX_PROVIDER = "AJAX_PROVIDER";
public const TYPE_SIMPLE_PROVIDER = "SIMPLE_PROVIDER";
/**
* GetType.
*
* @return string
*/
public function GetType(): string;
/**
* SetOptions.
*
* @param array $aOptions
*
* @return $this
*/
public function SetOptions(array $aOptions): iDataProvider;
/**
* SetOption.
*
* @param string $sKey
* @param string $sValue
*
* @return $this
*/
public function SetOption(string $sKey, string $sValue): iDataProvider;
/**
* GetOptions.
*
* @return array
*/
public function GetOptions(): array;
/**
* GetDataValueField.
*
* @return string
*/
public function GetDataValueField(): string;
/**
* SetDataValueField.
*
* @param string $sField
*
* @return $this
*/
public function SetDataValueField(string $sField): iDataProvider;
/**
* GetDataLabelField.
*
* @return string
*/
public function GetDataLabelField(): string;
/**
* SetDataLabelField.
*
* @param string $sField
*
* @return $this
*/
public function SetDataLabelField(string $sField): iDataProvider;
/**
* GetDataSearchFields.
*
* @return array
*/
public function GetDataSearchFields(): array;
/**
* SetDataSearchFields.
*
* @param array $aFields
*
* @return $this
*/
public function SetDataSearchFields(array $aFields): iDataProvider;
/**
* GetGroupField.
*
* @return string|null
*/
public function GetGroupField(): ?string;
/**
* SetGroupField.
*
* @param string $sField
*
* @return $this
*/
public function SetGroupField(string $sField): iDataProvider;
/**
* GetTooltipField.
*
* @return string|null
*/
public function GetTooltipField(): ?string;
/**
* SetTooltipField.
*
* @param string $sField
*
* @return $this
*/
public function SetTooltipField(string $sField): iDataProvider;
}

View File

@@ -0,0 +1,397 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set;
use Combodo\iTop\Application\UI\Base\Component\Input\AbstractInput;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\iDataProvider;
use Dict;
/**
* Class Set
*
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set
* @since 3.1.0
*/
class Set extends AbstractInput
{
// Overloaded constants
public const BLOCK_CODE = 'ibo-set';
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/input/set/layout';
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/input/set/layout';
public const DEFAULT_JS_FILES_REL_PATH = [
'js/selectize/plugin_combodo_add_button.js',
'js/selectize/plugin_combodo_auto_position.js',
'js/selectize/plugin_combodo_update_operations.js',
'js/selectize/plugin_combodo_multi_values_synthesis.js',
];
/** @var int|null $iMaxItems Maximum number of items selectable */
private ?int $iMaxItems;
/** @var int|null $iMaxItem Maximum number of displayed options */
private ?int $iMaxOptions;
/** @var bool $bHasRemoveItemButton Enable remove item button */
private bool $bHasRemoveItemButton;
/** @var bool $bHasAddOptionButton Enable add option button */
private bool $bHasAddOptionButton;
/** @var string $sAddButtonTitle Add button title */
private string $sAddButtonTitle;
/** @var bool $bIsPreloadEnabled Load data at initialization (ajax data provider only) */
private bool $bIsPreloadEnabled;
/** @var string|null $sTemplateOptions Template for rendering options in dropdown (twig) */
private ?string $sTemplateOptions;
/** @var string|null $sTemplateItems Template for rendering items in input (twig) */
private ?string $sTemplateItems;
/** @var \Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\iDataProvider $oDataProvider Set data provider */
private iDataProvider $oDataProvider;
/** @var bool $bIsMultiValuesSynthesis Used for bulk modify for example */
private bool $bIsMultiValuesSynthesis;
/** @var bool $bHasError Error flag */
private bool $bHasError;
/**
* Constructor.
*
* @param string|null $sId Block identifier
*/
public function __construct(string $sId = null)
{
parent::__construct($sId);
// Initialization
$this->Init();
}
/**
* Initialization.
*
* @return void
*/
private function Init()
{
$this->SetValue('[]');
// @todo BDA placeholder depending on autocomplete activation (search...., click to add...)
$this->SetPlaceholder(Dict::S('Core:AttributeSet:placeholder'));
$this->iMaxItems = null;
$this->iMaxOptions = null;
$this->bHasRemoveItemButton = true;
$this->bHasAddOptionButton = false;
$this->sAddButtonTitle = Dict::S('UI:Button:Create');
$this->bIsPreloadEnabled = false;
$this->sTemplateOptions = null;
$this->sTemplateItems = null;
$this->bIsMultiValuesSynthesis = false;
$this->bHasError = false;
}
/**
* SetMaxItems.
*
* @param int|null $iMaxItems
*
* @return $this
*/
public function SetMaxItems(?int $iMaxItems): Set
{
$this->iMaxItems = $iMaxItems;
return $this;
}
/**
* GetMaxItems.
*
* @return int|null
*/
public function GetMaxItems(): ?int
{
return $this->iMaxItems;
}
/**
* SetMaxOptions.
*
* @param int|null $iMaxOptions
*
* @return $this
*/
public function SetMaxOptions(?int $iMaxOptions): Set
{
$this->iMaxOptions = $iMaxOptions;
return $this;
}
/**
* GetMaxOptions.
*
* @return int|null
*/
public function GetMaxOptions(): ?int
{
return $this->iMaxOptions;
}
/**
* SetHasRemoveItemButton.
*
* @param bool $bHasRemoveItemButton
*
* @return $this
*/
public function SetHasRemoveItemButton(bool $bHasRemoveItemButton): Set
{
$this->bHasRemoveItemButton = $bHasRemoveItemButton;
return $this;
}
/**
* HasRemoveItemButton.
*
* @return bool
*/
public function HasRemoveItemButton(): bool
{
return $this->bHasRemoveItemButton;
}
/**
* SetHasAddOptionButton.
*
* @param bool $bHasAddOptionButton
*
* @return $this
*/
public function SetHasAddOptionButton(bool $bHasAddOptionButton): Set
{
$this->bHasAddOptionButton = $bHasAddOptionButton;
return $this;
}
/**
* HasAddOptionButton.
*
* @return bool
*/
public function HasAddOptionButton(): bool
{
return $this->bHasAddOptionButton;
}
/**
* GetAddButtonTitle.
*
* @return string
*/
public function GetAddButtonTitle(): string
{
return $this->sAddButtonTitle;
}
/**
* SetAddButtonTitle.
*
* @param string $sTitle
*
* @return $this
*/
public function SetAddButtonTitle(string $sTitle): Set
{
$this->sAddButtonTitle = $sTitle;
return $this;
}
/**
* SetPreloadEnabled.
*
* @param bool $bEnabled
*
* @return $this
*/
public function SetPreloadEnabled(bool $bEnabled): Set
{
$this->bIsPreloadEnabled = $bEnabled;
return $this;
}
/**
* IsPreloadEnabled.
*
* @return bool
*/
public function IsPreloadEnabled(): bool
{
return $this->bIsPreloadEnabled;
}
/**
* SetOptionsTemplate.
*
* @param string $sTemplate
*
* @return $this
*/
public function SetOptionsTemplate(string $sTemplate): Set
{
$this->sTemplateOptions = $sTemplate;
return $this;
}
/**
* Return options template.
*
* @return string
*/
public function GetOptionsTemplate(): ?string
{
return $this->sTemplateOptions;
}
/**
* HasOptionsTemplate.
*
* @return bool
*/
public function HasOptionsTemplate(): bool
{
return $this->sTemplateOptions != null;
}
/**
* SetItemsTemplate.
*
* @param string $sTemplate
*
* @return $this
*/
public function SetItemsTemplate(string $sTemplate): Set
{
$this->sTemplateItems = $sTemplate;
return $this;
}
/**
* Return items template.
*
* @return string
*/
public function GetItemsTemplate(): ?string
{
return $this->sTemplateItems;
}
/**
* HasItemsTemplate.
*
* @return bool
*/
public function HasItemsTemplate(): bool
{
return $this->sTemplateItems != null;
}
/**
* SetDataProvider.
*
* @param \Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\iDataProvider $oDataProvider
*
* @return $this
*/
public function SetDataProvider(iDataProvider $oDataProvider): Set
{
$this->oDataProvider = $oDataProvider;
return $this;
}
/**
* Get data provider.
*
* @return iDataProvider
*/
public function GetDataProvider(): iDataProvider
{
return $this->oDataProvider;
}
/**
* SetIsMultiValuesSynthesis.
*
* @param bool $bIsMultiValuesSynthesis
*
* @return $this
*/
public function SetIsMultiValuesSynthesis(bool $bIsMultiValuesSynthesis): Set
{
$this->bIsMultiValuesSynthesis = $bIsMultiValuesSynthesis;
return $this;
}
/**
* IsMultiValuesSynthesis.
*
* @return bool
*/
public function IsMultiValuesSynthesis(): bool
{
return $this->bIsMultiValuesSynthesis;
}
/**
* SetHasError.
*
* @param $bHasError
*
* @return $this
*/
public function SetHasError($bHasError): Set
{
$this->bHasError = $bHasError;
return $this;
}
/**
* HasError.
*
* @return bool
*/
public function HasError(): bool
{
return $this->bHasError;
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Base\Component\Input\Set;
use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\AjaxDataProvider;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\AjaxDataProviderForOQL;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\SimpleDataProvider;
/**
* Class SetUIBlockFactory
*
* @api
*
* @since 3.1.0
* @package Combodo\iTop\Application\UI\Base\Component\Input\Set
*/
class SetUIBlockFactory extends AbstractUIBlockFactory
{
/** @inheritDoc */
public const TWIG_TAG_NAME = 'UISet';
/** @inheritDoc */
public const UI_BLOCK_CLASS_NAME = Set::class;
/**
* MakeForSimple.
*
* Create a simple set base on a static array of options.
* Options array must contain label, value and search string for each option.
* Keys for each entry must be provided but can be the same.
* If a group field is provided, options will be grouped according to this setting.
*
* @param string $sId Block identifier
* @param array $aOptions Array containing options
* @param string $sLabelFields Field used for label rendering
* @param string $sValueField Field used for option value
* @param array $aSearchFields Fields used for searching
* @param string|null $sGroupField Field used for grouping
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForSimple(string $sId, array $aOptions, string $sLabelFields, string $sValueField, array $aSearchFields, ?string $sGroupField = null): Set
{
// Create set ui block
$oSetUIBlock = new Set($sId);
// Simple data provider
$oDataProvider = new SimpleDataProvider($aOptions);
$oDataProvider
->SetDataLabelField($sLabelFields)
->SetDataValueField($sValueField)
->SetDataSearchFields($aSearchFields)
->SetTooltipField($sLabelFields);
if ($sGroupField != null) {
$oDataProvider->SetGroupField($sGroupField);
}
$oSetUIBlock->SetDataProvider($oDataProvider);
return $oSetUIBlock;
}
/**
* MakeForAjax.
*
* Create a dynamic set base on options provided by ajax call.
* Options array must contain label, value and search string for each option.
* Keys for each entry must be provided but can be the same.
* If a group field is provided, options will be grouped according to this setting.
*
* @param string $sId Block identifier
* @param string $sAjaxRoute Ajax route @see \Combodo\iTop\Router\Router
* @param array $aAjaxRouteParams Url query parameters
* @param string $sLabelFields Field used for label
* @param string $sValueField Field used for value
* @param array $aSearchFields Fields used for search
* @param string|null $sGroupField Field used for grouping
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForAjax(string $sId, string $sAjaxRoute, array $aAjaxRouteParams, string $sLabelFields, string $sValueField, array $aSearchFields, ?string $sGroupField = null): Set
{
// Create set ui block
$oSetUIBlock = new Set($sId);
// Ajax data provider
$oDataProvider = new AjaxDataProvider($sAjaxRoute, $aAjaxRouteParams);
$oDataProvider
->SetDataLabelField($sLabelFields)
->SetDataValueField($sValueField)
->SetDataSearchFields($aSearchFields)
->SetTooltipField($sLabelFields);
if ($sGroupField != null) {
$oDataProvider->SetGroupField($sGroupField);
}
$oSetUIBlock->SetDataProvider($oDataProvider);
return $oSetUIBlock;
}
/**
* MakeForOQL.
*
* Create a oql set base on options provided by OQL call.
* Options array must contain label, value and search string for each option.
* Keys for each entry must be provided but can be the same.
* If a group field is provided, options will be grouped according to this setting.
* Default fields are loaded but you can request more.
*
* @param string $sId Block identifier
* @param string $sObjectClass Object class
* @param string $sOql OQL to query objects
* @param string|null $sWizardHelperJsVarName Wizard helper name
* @param array $aFieldsToLoad Additional fields to load on objects
* @param string|null $sGroupField Field used for grouping
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForOQL(string $sId, string $sObjectClass, string $sOql, string $sWizardHelperJsVarName = null, array $aFieldsToLoad = [], ?string $sGroupField = null): Set
{
// Create set ui block
$oSetUIBlock = new Set($sId);
// Renderers
$oSetUIBlock->SetOptionsTemplate('application/object/set/option_renderer.html.twig');
$oSetUIBlock->SetItemsTemplate('application/object/set/item_renderer.html.twig');
// OQL data provider
$oDataProvider = new AjaxDataProviderForOQL($sObjectClass, $sOql, $sWizardHelperJsVarName, $aFieldsToLoad);
if ($sGroupField != null) {
$oDataProvider->SetGroupField($sGroupField);
}
$oDataProvider->SetTooltipField('full_description');
$oSetUIBlock->SetDataProvider($oDataProvider);
return $oSetUIBlock;
}
}

View File

@@ -6,9 +6,21 @@
namespace Combodo\iTop\Application\UI\Links; namespace Combodo\iTop\Application\UI\Links;
use ApplicationException;
use ArchivedObjectException;
use AttributeLinkedSet;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon; use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use CoreException;
use CoreWarning;
use DBObject;
use DictExceptionMissingString;
use DisplayBlock;
use Exception;
use MetaModel; use MetaModel;
use MySQLException;
use Utils;
use WebPage;
/** /**
* Class AbstractBlockLinksViewTable * Class AbstractBlockLinksViewTable
@@ -27,8 +39,8 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
'js/wizardhelper.js', 'js/wizardhelper.js',
]; ];
/** @var \DBObject $oDbObject db object witch link set belongs to */ /** @var DBObject $oDbObject db object witch link set belongs to */
protected \DBObject $oDbObject; protected DBObject $oDbObject;
/** @var string $sObjectClass db object class name */ /** @var string $sObjectClass db object class name */
protected string $sObjectClass; protected string $sObjectClass;
@@ -36,27 +48,27 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
/** @var string $sAttCode db object link set attribute code */ /** @var string $sAttCode db object link set attribute code */
protected string $sAttCode; protected string $sAttCode;
/** @var \AttributeLinkedSet $oAttDef attribute link set */ /** @var AttributeLinkedSet $oAttDef attribute link set */
protected \AttributeLinkedSet $oAttDef; protected AttributeLinkedSet $oAttDef;
/** @var string $sTargetClass links target classname */ /** @var string $sTargetClass links target classname */
protected string $sTargetClass; protected string $sTargetClass;
protected string $sTableId; protected string $sTableId;
/** /**
* Constructor. * Constructor.
* *
* @param \WebPage $oPage * @param WebPage $oPage
* @param \DBObject $oDbObject * @param DBObject $oDbObject
* @param string $sObjectClass * @param string $sObjectClass
* @param string $sAttCode * @param string $sAttCode
* @param \AttributeLinkedSet $oAttDef * @param AttributeLinkedSet $oAttDef
* *
* @throws \CoreException * @throws CoreException
* @throws \Exception * @throws Exception
*/ */
public function __construct(\WebPage $oPage, \DBObject $oDbObject, string $sObjectClass, string $sAttCode, \AttributeLinkedSet $oAttDef) public function __construct(WebPage $oPage, DBObject $oDbObject, string $sObjectClass, string $sAttCode, AttributeLinkedSet $oAttDef)
{ {
parent::__construct('', ["ibo-block-links-table"]); parent::__construct('', ["ibo-block-links-table"]);
@@ -78,7 +90,7 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* Init. * Init.
* *
* @return void * @return void
* @throws \Exception * @throws Exception
*/ */
private function Init() private function Init()
{ {
@@ -89,12 +101,12 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* Initialize UI. * Initialize UI.
* *
* @return void * @return void
* @throws \CoreException * @throws CoreException
*/ */
private function InitUI(\WebPage $oPage) private function InitUI(WebPage $oPage)
{ {
// header // header
$this->InitHeader();; $this->InitHeader();
// Table // Table
$this->InitTable($oPage); $this->InitTable($oPage);
@@ -104,7 +116,8 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* InitHeader. * InitHeader.
* *
* @return void * @return void
* @throws \CoreException * @throws CoreException
* @throws \Exception
*/ */
private function InitHeader() private function InitHeader()
{ {
@@ -117,21 +130,21 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
/** /**
* InitTable. * InitTable.
* *
* @param \WebPage $oPage * @param WebPage $oPage
* *
* @return void * @return void
* @throws \ApplicationException * @throws ApplicationException
* @throws \ArchivedObjectException * @throws ArchivedObjectException
* @throws \CoreException * @throws CoreException
* @throws \CoreWarning * @throws CoreWarning
* @throws \DictExceptionMissingString * @throws DictExceptionMissingString
* @throws \MySQLException * @throws MySQLException
*/ */
private function InitTable(\WebPage $oPage) private function InitTable(WebPage $oPage)
{ {
// retrieve db object set // retrieve db object set
$oOrmLinkSet = $this->oDbObject->Get($this->sAttCode); $oOrmLinkSet = $this->oDbObject->Get($this->sAttCode);
$oLinkSet = $oOrmLinkSet->ToDBObjectSet(\utils::ShowObsoleteData()); $oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData());
// add list block // add list block
$oBlock = new \DisplayBlock($oLinkSet->GetFilter(), 'list', false); $oBlock = new \DisplayBlock($oLinkSet->GetFilter(), 'list', false);
@@ -163,11 +176,11 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* *
* Provide parameters for display block as list. * Provide parameters for display block as list.
* *
* @see \DisplayBlock::RenderList * @see DisplayBlock::RenderList
* *
* @return array * @return array
* @throws \ArchivedObjectException * @throws ArchivedObjectException
* @throws \CoreException * @throws CoreException
*/ */
abstract function GetExtraParam(): array; abstract function GetExtraParam(): array;
@@ -178,7 +191,7 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* *
* @see \Combodo\iTop\Application\UI\Base\Component\DataTable\tTableRowActions * @see \Combodo\iTop\Application\UI\Base\Component\DataTable\tTableRowActions
* *
* @return \string[][] * @return string[][]
*/ */
abstract function GetRowActions(): array; abstract function GetRowActions(): array;
@@ -188,12 +201,11 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
* Return link set target class. * Return link set target class.
* *
* @return string * @return string
* @throws \Exception * @throws Exception
*/ */
abstract function GetTargetClass(): string; abstract function GetTargetClass(): string;
/** /**
* @return string * @return string
*/ */
@@ -201,5 +213,5 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock
{ {
return $this->sAttCode; return $this->sAttCode;
} }
} }

View File

@@ -127,7 +127,7 @@ class BlockDirectLinksEditTable extends UIContentBlock
* *
* @return void * @return void
*/ */
public function InitTable(\WebPage $oPage, \DBObjectSet $oValue, string $sFormPrefix) public function InitTable(\WebPage $oPage, $oValue, string $sFormPrefix)
{ {
/** @todo fields initialization */ /** @todo fields initialization */
$this->sInputName = $sFormPrefix.'attr_'.$this->oUILinksDirectWidget->GetAttCode(); $this->sInputName = $sFormPrefix.'attr_'.$this->oUILinksDirectWidget->GetAttCode();
@@ -193,26 +193,6 @@ class BlockDirectLinksEditTable extends UIContentBlock
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->oUILinksDirectWidget->GetInputId().'" onClick="oWidget'.$this->oUILinksDirectWidget->GetInputId().'.directlinks(\'instance\')._onSelectChange();" value="'.$oLinkObj->GetKey().'"/>'; $aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->oUILinksDirectWidget->GetInputId().'" onClick="oWidget'.$this->oUILinksDirectWidget->GetInputId().'.directlinks(\'instance\')._onSelectChange();" value="'.$oLinkObj->GetKey().'"/>';
foreach ($this->oUILinksDirectWidget->GetZList() as $sLinkedAttCode) { foreach ($this->oUILinksDirectWidget->GetZList() as $sLinkedAttCode) {
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode); $aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
// tentative d'ajout des attributs en édition
// $sValue = $oLinkObj->Get($sLinkedAttCode);
// $sDisplayValue = $oLinkObj->GetEditValue($sLinkedAttCode);
// $oAttDef = MetaModel::GetAttributeDef($this->oUILinksDirectWidget->GetLinkedClass(), $sLinkedAttCode);
//
// $aRow[$sLinkedAttCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'
// .\cmdbAbstractObject::GetFormElementForField(
// $oPage,
// $this->oUILinksDirectWidget->GetLinkedClass(),
// $sLinkedAttCode,
// $oAttDef,
// $sValue,
// $sDisplayValue,
// $this->GetFieldId($oValue, $sLinkedAttCode),
// ']',
// 0,
// []
// )
// .'</div></div></div>';
} }
$aRows[] = $aRow; $aRows[] = $aRow;
} }

View File

@@ -19,7 +19,8 @@ use PHPUnit\Exception;
*/ */
class BlockIndirectLinksViewTable extends AbstractBlockLinksViewTable class BlockIndirectLinksViewTable extends AbstractBlockLinksViewTable
{ {
public const BLOCK_CODE = 'ibo-block-indirect-links-view-table'; public const BLOCK_CODE = 'ibo-block-indirect-links-view-table';
public const REQUIRES_ANCESTORS_DEFAULT_JS_FILES = true;
/** @inheritdoc */ /** @inheritdoc */
public function GetTargetClass(): string public function GetTargetClass(): string

View File

@@ -0,0 +1,161 @@
<?php
/**
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Application\UI\Links\Indirect;
use ApplicationContext;
use AttributeLinkedSet;
use cmdbAbstractObject;
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\Links\LinkSetRepository;
use ormLinkSet;
use Twig\Environment;
use utils;
/**
* Class BlockLinksSetDisplayAsProperty
*
* @internal
* @since 3.1.0
* @package Combodo\iTop\Application\UI\Links\Indirect
*/
class BlockLinksSetDisplayAsProperty extends UIContentBlock
{
public const BLOCK_CODE = 'ibo-block-links-set-as-property';
/** @var AttributeLinkedSet $oAttribute Attribute link set */
private AttributeLinkedSet $oAttribute;
/** @var \ormLinkSet $oValue Link set value */
private ormLinkSet $oValue;
/** @var string $sTargetClass Link set target class */
private string $sTargetClass;
/** @var ?array $aObjectsData Links objects converted as array */
private ?array $aObjectsData;
/** @var \Twig\Environment Twig environment */
private Environment $oTwigEnv;
/** @var string $sAppContext Application context */
private string $sAppContext;
/** @var string $sUIPage UI page */
private string $sUIPage;
/**
* Constructor.
*
* @param string $sId block identifier
* @param AttributeLinkedSet $oAttribute
* @param ormLinkSet $oValue
*
* @throws \Exception
* @throws \Twig\Error\LoaderError
*/
public function __construct(string $sId, AttributeLinkedSet $oAttribute, ormLinkSet $oValue)
{
parent::__construct($sId);
// retrieve parameters
$this->oAttribute = $oAttribute;
$this->oValue = $oValue;
// Initialization
$this->Init();
// UI Initialization
$this->InitUI();
}
/**
* Initialization.
*
* @return void
* @throws \Twig\Error\LoaderError
*/
private function Init()
{
// Link set model properties
$this->sTargetClass = LinkSetModel::GetTargetClass($this->oAttribute);
$sTargetField = LinkSetModel::GetTargetField($this->oAttribute);
// Get objects from linked data
$this->aObjectsData = LinkSetRepository::LinksDbSetToTargetObjectArray($this->oValue, $this->sTargetClass, $sTargetField);
// Twig environment
$this->oTwigEnv = TwigHelper::GetTwigEnvironment(TwigHelper::ENUM_TEMPLATES_BASE_PATH_BACKOFFICE);
$oAppContext = new ApplicationContext();
$this->sAppContext = $oAppContext->GetForLink();
$this->sUIPage = cmdbAbstractObject::ComputeStandardUIPage($this->sTargetClass);
}
/**
* UI Initialization.
*
* @return void
* @throws \Exception
*/
private function InitUI()
{
// Error handling
if ($this->aObjectsData === null) {
$sMessage = "Error while displaying attribute {$this->oAttribute->GetCode()}";
$this->AddSubBlock(HtmlFactory::MakeHtmlContent($sMessage));
return;
}
// Container
$sHtml = '<span class="'.implode(' ', $this->oAttribute->GetCssClasses()).'">';
// Iterate throw data...
foreach ($this->aObjectsData as $aItem) {
// Ignore obsolete data
if (!utils::ShowObsoleteData() && $aItem['obsolescence_flag']) {
continue;
}
// Generate template
$sTemplate = TwigHelper::RenderTemplate($this->oTwigEnv, $aItem, 'application/object/set/set_renderer');
// Friendly name
$sFriendlyNameForHtml = utils::HtmlEntities($aItem['friendlyname']);
// Append value
$sHtml .= '<a'.$this->GenerateLinkUrl($aItem['key']).' class="attribute-set-item" data-label="'.$sFriendlyNameForHtml.'" data-tooltip-content="'.$sFriendlyNameForHtml.'">'.$sTemplate.'</a>';
}
// Close container
$sHtml .= '</span>';
// Make html block
$this->AddSubBlock(HtmlFactory::MakeHtmlContent($sHtml));
}
/**
* GenerateLinkUrl.
*
* @param $id
*
* @return string
* @throws \Exception
*/
private function GenerateLinkUrl($id): string
{
return ' href="'
.utils::GetAbsoluteUrlAppRoot()
."pages/$this->sUIPage?operation=details&class=$this->sTargetClass&id=$id&$this->sAppContext"
.'" target="_blank"';
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Application\UI\Links\Set;
use AttributeLinkedSet;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\Set;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\SetUIBlockFactory;
use Combodo\iTop\Service\Links\LinksBulkDataPostProcessor;
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\Links\LinkSetRepository;
use iDBObjectSetIterator;
/**
* Class LinksSetUIBlockFactory
*
* @api
*
* @since 3.1.0
* @package Combodo\iTop\Application\UI\Links\Set
*/
class LinksSetUIBlockFactory extends SetUIBlockFactory
{
/**
* Make a link set block.
*
* @param string $sId Block identifier
* @param AttributeLinkedSet $oAttDef Link set attribute definition
* @param iDBObjectSetIterator $oDbObjectSet Link set value
* @param string $sWizardHelperJsVarName Wizard helper name
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName): Set
{
$sTargetClass = LinkSetModel::GetTargetClass($oAttDef);
$sTargetField = LinkSetModel::GetTargetField($oAttDef);
// Set UI block for OQL
$oSetUIBlock = SetUIBlockFactory::MakeForOQL($sId, $sTargetClass, $oAttDef->GetValuesDef()->GetFilterExpression(), $sWizardHelperJsVarName);
// Current value
$aCurrentValues = LinkSetDataTransformer::Decode($oDbObjectSet, $sTargetClass, $sTargetField);
// Initial options data
$aInitialOptions = LinkSetRepository::LinksDbSetToTargetObjectArray($oDbObjectSet, $sTargetClass, $sTargetField);
if ($aInitialOptions !== null) {
$oSetUIBlock->GetDataProvider()->SetOptions($aInitialOptions);
// Set value
$oSetUIBlock->SetValue(json_encode($aCurrentValues));
} else {
$oSetUIBlock->SetHasError(true);
}
return $oSetUIBlock;
}
/**
* Make a link set block for bulk modify.
*
* @param string $sId Block identifier
* @param AttributeLinkedSet $oAttDef Link set attribute definition
* @param iDBObjectSetIterator $oDbObjectSet Link set value
* @param string $sWizardHelperJsVarName Wizard helper name
* @param array $aBulkContext
*
* @return \Combodo\iTop\Application\UI\Base\Component\Input\Set\Set
*/
public static function MakeForBulkLinkSet(string $sId, AttributeLinkedSet $oAttDef, iDBObjectSetIterator $oDbObjectSet, string $sWizardHelperJsVarName, array $aBulkContext): Set
{
$oSetUIBlock = self::MakeForLinkSet($sId, $oAttDef, $oDbObjectSet, $sWizardHelperJsVarName);
// Bulk modify specific
$oSetUIBlock->GetDataProvider()->SetGroupField('group');
$oSetUIBlock->SetIsMultiValuesSynthesis(true);
// Data post processing
$aBinderSettings = [
'bulk_oql' => $aBulkContext['oql'],
'link_class' => LinkSetModel::GetLinkedClass($oAttDef),
'target_field' => LinkSetModel::GetTargetField($oAttDef),
'origin_field' => $oAttDef->GetExtKeyToMe(),
];
// Initial options
$aOptions = $oSetUIBlock->GetDataProvider()->GetOptions();
$aOptions = LinksBulkDataPostProcessor::Execute($aOptions, $aBinderSettings);
$oSetUIBlock->GetDataProvider()->SetOptions($aOptions);
// Data provider post processor
/** @var \Combodo\iTop\Application\UI\Base\Component\Input\Set\DataProvider\AjaxDataProvider $oDataProvider */
$oDataProvider = $oSetUIBlock->GetDataProvider();
$oDataProvider->SetPostParam('data_post_processor', [
'class_name' => addslashes(LinksBulkDataPostProcessor::class),
'settings' => $aBinderSettings,
]);
return $oSetUIBlock;
}
}

View File

@@ -14,6 +14,7 @@ use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper; use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper;
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
use Combodo\iTop\Controller\AbstractController; use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Service\Base\ObjectRepository;
use CoreCannotSaveObjectException; use CoreCannotSaveObjectException;
use DeleteException; use DeleteException;
use Dict; use Dict;
@@ -543,4 +544,44 @@ JS;
'js/jquery.blockUI.js', 'js/jquery.blockUI.js',
]; ];
} }
/**
* OperationSearch.
*
* Search objects via an oql and a friendly name search string
*
* @return JsonPage
*/
public function OperationSearch(): JsonPage
{
$oPage = new JsonPage();
// Retrieve query params
$sObjectClass = utils::ReadParam('object_class', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
$sOql = utils::ReadParam('oql', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$aFieldsToLoad = json_decode(utils::ReadParam('fields_to_load', '', false, utils::ENUM_SANITIZATION_FILTER_STRING));
$sSearch = utils::ReadParam('search', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
// Retrieve this reference object (for OQL)
$sThisObjectData = utils::ReadPostedParam('this_object_data', null, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$oThisObj = ObjectRepository::GetObjectFromWizardHelperData($sThisObjectData);
// Retrieve data post processor
$aDataProcessor = utils::ReadParam('data_post_processor', null, false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
// Search objects
$aResult = ObjectRepository::SearchFromOql($sObjectClass, $aFieldsToLoad, $sOql, $sSearch, $oThisObj);
// Data post processor
// Note: Data post processor allow you to perform actions on search result (compute object result statistics, add others information...).
if ($aResult !== null && $aDataProcessor !== null) {
$aResult = call_user_func(array($aDataProcessor['class_name'], 'Execute'), $aResult, $aDataProcessor['settings']);
}
return $oPage->SetData([
'search_data' => $aResult,
'success' => $aResult !== null,
]);
}
} }

View File

@@ -10,9 +10,10 @@ use AjaxPage;
use cmdbAbstractObject; use cmdbAbstractObject;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Controller\AbstractController; use Combodo\iTop\Controller\AbstractController;
use Exception;
use JsonPage;
use CoreException; use CoreException;
use DBObject; use DBObject;
use JsonPage;
use MetaModel; use MetaModel;
use UserRights; use UserRights;
use utils; use utils;
@@ -58,7 +59,7 @@ class LinkSetController extends AbstractController
$sErrorMessage = json_encode($oDeletionPlan->GetIssues()); $sErrorMessage = json_encode($oDeletionPlan->GetIssues());
} }
} }
catch (\Exception $e) { catch (Exception $e) {
$sErrorMessage = $e->getMessage(); $sErrorMessage = $e->getMessage();
} }
} else { } else {
@@ -102,7 +103,7 @@ class LinkSetController extends AbstractController
$oLinkedObject->DBWrite(); $oLinkedObject->DBWrite();
$bOperationSuccess = true; $bOperationSuccess = true;
} }
catch (\Exception $e) { catch (Exception $e) {
$sErrorMessage = $e->getMessage(); $sErrorMessage = $e->getMessage();
} }
} else { } else {

View File

@@ -0,0 +1,263 @@
<?php
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\Base;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use DBObject;
use DBObjectSearch;
use DBObjectSet;
use DBSearch;
use Exception;
use ExceptionLog;
use iDBObjectSetIterator;
use MetaModel;
use utils;
use WizardHelper;
/**
* Class ObjectRepository
*
*
* @internal
* @since 3.1.0
* @package Combodo\iTop\Service\Base
*/
class ObjectRepository
{
/**
* Search.
*
* @param string $sObjectClass Object class to search
* @param array $aFieldsToLoad Additional fields to load
* @param string $sSearch Friendly name search string
*
* @return array|null
*/
static public function Search(string $sObjectClass, array $aFieldsToLoad, string $sSearch): ?array
{
try {
// Create db search
$oDbObjectSearch = new DBObjectSearch($sObjectClass);
$oDbObjectSearch->SetShowObsoleteData(utils::ShowObsoleteData());
// Add a friendly name search condition
$oDbObjectSearch->AddCondition('friendlyname', $sSearch, 'Contains');
// Create db object set
$oSet = new DBObjectSet($oDbObjectSearch);
// Transform set to array
$aResult = ObjectRepository::DBSetToObjectArray($oSet, $sObjectClass, $aFieldsToLoad);
// Handle max results for autocomplete
if (Utils::IsNullOrEmptyString($sSearch)
&& count($aResult) > MetaModel::GetConfig()->Get('max_autocomplete_results')) {
return [];
}
return $aResult;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return null;
}
}
/**
* SearchFromOql.
*
* @param string $sObjectClass Object class to search
* @param array $aFieldsToLoad Additional fields to load
* @param string $sOql Oql expression
* @param string $sSearch Friendly name search string
* @param DBObject|null $oThisObject This object reference for oql
*
* @return array|null
*/
static public function SearchFromOql(string $sObjectClass, array $aFieldsToLoad, string $sOql, string $sSearch, DBObject $oThisObject = null): ?array
{
try {
// Create db search
$oDbObjectSearch = DBSearch::FromOQL($sOql);
$oDbObjectSearch->SetShowObsoleteData(utils::ShowObsoleteData());
$oDbObjectSearch->AddCondition('friendlyname', $sSearch, 'Contains');
// Create db set from db search
$oDbObjectSet = new DBObjectSet($oDbObjectSearch, [], ['this' => $oThisObject]);
// return object array
return ObjectRepository::DBSetToObjectArray($oDbObjectSet, $sObjectClass, $aFieldsToLoad);
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return null;
}
}
/**
* DBSetToObjectArray.
*
* @param iDBObjectSetIterator $oDbObjectSet Db object set
* @param string $sObjectClass Object class
* @param array $aFieldsToLoad Additional fields to load
*
* @return array
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \DictExceptionMissingString
*/
static private function DBSetToObjectArray(iDBObjectSetIterator $oDbObjectSet, string $sObjectClass, array $aFieldsToLoad): array
{
// Retrieve friendly name complementary specification
$aComplementAttributeSpec = MetaModel::GetNameSpec($sObjectClass, FriendlyNameType::COMPLEMENTARY);
// Retrieve image attribute code
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sObjectClass);
// Prepare fields to load
$aDefaultFieldsToLoad = ObjectRepository::GetDefaultFieldsToLoad($aComplementAttributeSpec, $sObjectImageAttCode);
$aFieldsToLoad = array_merge($aDefaultFieldsToLoad, $aFieldsToLoad);
// Optimize columns load
$oDbObjectSet->OptimizeColumnLoad([
$sObjectClass => $aFieldsToLoad,
]);
// Prepare result
$aResult = [];
// Iterate throw objects...
$oDbObjectSet->Rewind();
while ($oObject = $oDbObjectSet->Fetch()) {
// Prepare objet data
$aObjectData = [];
// Object key
$aObjectData['key'] = $oObject->GetKey();
// Fill loaded columns...
foreach ($aFieldsToLoad as $sField) {
$aObjectData[$sField] = $oObject->Get($sField);
}
// Compute others data
$aResult[] = ObjectRepository::ComputeOthersData($oObject, $sObjectClass, $aObjectData, $aComplementAttributeSpec, $sObjectImageAttCode);
}
return $aResult;
}
/**
* GetDefaultFieldsToLoad.
*
* Return attributes to load for any objects.
*
* @param array $aComplementAttributeSpec Friendly name complementary spec
* @param string $sObjectImageAttCode Image attribute code
*
* @return mixed
*/
static public function GetDefaultFieldsToLoad(array $aComplementAttributeSpec, string $sObjectImageAttCode)
{
// Friendly name complementary fields
$aFieldsToLoad = $aComplementAttributeSpec[1];
// Image attribute
if (!empty($sObjectImageAttCode)) {
$aFieldsToLoad[] = $sObjectImageAttCode;
}
// Add friendly name
$aFieldsToLoad[] = 'friendlyname';
return $aFieldsToLoad;
}
/**
* ComputeOthersData.
*
* @param DBObject $oDbObject Db object
* @param string $sClass Object class
* @param array $aData Object data to fill
* @param array $aComplementAttributeSpec Friendly name complementary spec
* @param string $sObjectImageAttCode Image attribute code
*
* @return array
*/
static public function ComputeOthersData(DBObject $oDbObject, string $sClass, array $aData, array $aComplementAttributeSpec, string $sObjectImageAttCode): array
{
try {
// Obsolescence flag
$aData['obsolescence_flag'] = $oDbObject->IsObsolete();
// Additional fields
if (count($aComplementAttributeSpec[1]) > 0) {
$aArguments = [];
foreach ($aComplementAttributeSpec[1] as $sAdditionalField) {
$aArguments[] = $oDbObject->Get($sAdditionalField);
}
$aData['additional_field'] = utils::HtmlEntities(vsprintf($aComplementAttributeSpec[0], $aArguments));
$aData['full_description'] = "{$aData['friendlyname']}<br><i><small>{$aData['additional_field']}</small></i>";
} else {
$aData['full_description'] = $aData['friendlyname'];
}
// Image
if (!empty($sObjectImageAttCode)) {
$aData['has_image'] = true;
/** @var \ormDocument $oImage */
$oImage = $oDbObject->Get($sObjectImageAttCode);
if (!$oImage->IsEmpty()) {
$aData['picture_url'] = "url('{$oImage->GetDisplayURL($sClass, $oDbObject->GetKey(), $sObjectImageAttCode)}')";
$aData['initials'] = '';
} else {
$aData['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oDbObject->Get('friendlyname')));
}
}
return $aData;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return $aData;
}
}
/**
* GetObjectFromWizardHelperData
*
* @param string $sData
*
* @return DBObject|null
*/
public static function GetObjectFromWizardHelperData(string $sData): ?DBObject
{
try {
$oThisObj = null;
if ($sData != null) {
$oWizardHelper = WizardHelper::FromJSON($sData);
$oThisObj = $oWizardHelper->GetTargetObject();
}
return $oThisObj;
}
catch (Exception $e) {
return null;
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Service\Base;
/**
* Interface iDataPostProcessor
*
* @package Combodo\iTop\Service\Base
* @since 3.1.0
*/
interface iDataPostProcessor
{
/**
* Execute.
*
* @param array $aData Array of elements
* @param array $aSettings Array of settings
*
* @return array
*/
public static function Execute(array $aData, array $aSettings): array;
}

View File

@@ -0,0 +1,176 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Service\Links;
use Exception;
use ExceptionLog;
use iDBObjectSetIterator;
use MetaModel;
use ormLinkSet;
use utils;
/**
* Class LinksSetDataTransformer
*
* @api
*
* @since 3.1.0
* @package Combodo\iTop\Service\Links
*/
class LinkSetDataTransformer
{
/**
* Decode.
*
* Convert db object set to array.
* This array will be provided to set component view.
*
* @param \iDBObjectSetIterator $oDbObjectSet Db object set
* @param string $sTargetClass Target class name
* @param string|null $sTargetField Target field
*
* @return array
*/
static public function Decode(iDBObjectSetIterator $oDbObjectSet, string $sTargetClass, string $sTargetField = null): array
{
try {
// Prepare result
$aResult = [];
// Ensure start at set beginning
$oDbObjectSet->Rewind();
// Iterate throw objects...
while ($oObject = $oDbObjectSet->Fetch()) {
// In case ot indirect link
if ($sTargetField !== null) {
$oObject = MetaModel::GetObject($sTargetClass, $oObject->Get($sTargetField));
}
if (!utils::ShowObsoleteData() && $oObject->IsObsolete()) {
continue;
}
// Append object key
$aResult[] = $oObject->GetKey();
}
return $aResult;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return [];
}
}
/**
* Encode.
*
* Convert array from view to arrays used by UI.php to apply link set modifications.
*
* @see cmdbAbstractObject::PrepareValueFromPostedForm
*
* @param array $aElements Link set elements
* @param string $sLinkClass Link class name
* @param string|null $sExtKeyToRemote External key to remote
*
* @return array{to_be_created: array, to_be_deleted: array, to_be_added: array, to_be_removed: array}
*/
static public function Encode(array $aElements, string $sLinkClass, string $sExtKeyToRemote = null): array
{
// Result arrays
$aToBeCreate = [];
$aToBeDelete = [];
$aToBeAdd = [];
$aToBeRemove = [];
// Iterate throw data...
foreach ($aElements as $aData) {
switch ($aData['operation']) {
// OPERATION ADD
case 'add':
if ($sExtKeyToRemote === null) {
// Direct link attach
$aToBeAdd[] = $aData['data']['key'];
} else {
// Indirect link creation
$aToBeCreate[] = [
'class' => $sLinkClass,
'data' => [
$sExtKeyToRemote => $aData['data']['key'],
],
];
}
break;
// OPERATION REMOVE
case 'remove':
if ($sExtKeyToRemote === null) {
// Direct link detach
$aToBeRemove[] = $aData['data']['key'];
} else {
// Indirect link deletion
foreach ($aData['data']['link_keys'] as $sKey) {
$aToBeDelete[] = $sKey;
}
}
break;
}
}
return [
'to_be_created' => $aToBeCreate,
'to_be_deleted' => $aToBeDelete,
'to_be_added' => $aToBeAdd,
'to_be_removed' => $aToBeRemove,
];
}
/**
* Convert string representation of an orm linked set to object ormLinkSet.
*
* @param string $sValue
* @param \ormLinkSet $oOrmLinkSet
*
*/
static public function StringToOrmLinkSet(string $sValue, ormLinkSet $oOrmLinkSet)
{
try {
$aItems = explode(" ", $sValue);
foreach ($aItems as $sItem) {
if (!empty($sItem)) {
$oItem = MetaModel::GetObject($oOrmLinkSet->GetClass(), intval($sItem));
if (!utils::ShowObsoleteData() && $oItem->IsObsolete()) {
continue;
}
$oOrmLinkSet->AddItem($oItem);
}
}
}
catch (Exception $e) {
ExceptionLog::LogException($e);
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\Links;
use AttributeExternalKey;
use AttributeLinkedSet;
use AttributeLinkedSetIndirect;
use Exception;
use MetaModel;
/**
* Class LinkSetModel
*
* @internal
* @since 3.1.0
* @package Combodo\iTop\Service\Links
*/
class LinkSetModel
{
/**
* GetTargetClass.
*
* @param AttributeLinkedSet $oAttDef
*
* @return string
*/
static public function GetTargetClass(AttributeLinkedSet $oAttDef): string
{
try {
if ($oAttDef instanceof AttributeLinkedSetIndirect) {
/** @var AttributeExternalKey $oLinkingAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $oAttDef->GetExtKeyToRemote());
return $oLinkingAttDef->GetTargetClass();
} else {
return $oAttDef->GetLinkedClass();
}
}
catch (Exception $e) {
return 'unknown';
}
}
/**
* GetLinkedClass.
*
* @param AttributeLinkedSet $oAttDef
*
* @return string
*/
static public function GetLinkedClass(AttributeLinkedSet $oAttDef): string
{
return $oAttDef->GetLinkedClass();
}
/**
* GetTargetField.
*
* @param AttributeLinkedSet $oAttDef
*
* @return string|null
*/
static public function GetTargetField(AttributeLinkedSet $oAttDef): ?string
{
if ($oAttDef instanceof AttributeLinkedSetIndirect) {
return $oAttDef->GetExtKeyToRemote();
} else {
return null;
}
}
}

View File

@@ -0,0 +1,100 @@
<?php
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\Links;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Base\ObjectRepository;
use Exception;
use ExceptionLog;
use iDBObjectSetIterator;
use MetaModel;
use utils;
/**
* Class LinkSetRepository
*
*
* @internal
* @since 3.1.0
* @package Combodo\iTop\Service\Links
*/
class LinkSetRepository
{
/**
* LinksDbSetToTargetObjectArray.
*
* @param iDBObjectSetIterator $oDbObjectSet Db object set
* @param string $sTargetClass Target class name
* @param string|null $sTargetField Target field
*
* @return array|null
*/
static public function LinksDbSetToTargetObjectArray(iDBObjectSetIterator $oDbObjectSet, string $sTargetClass, string $sTargetField = null): ?array
{
try {
// Retrieve friendly name complementary specification
$aComplementAttributeSpec = MetaModel::GetNameSpec($sTargetClass, FriendlyNameType::COMPLEMENTARY);
// Retrieve image attribute code
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sTargetClass);
// Prepare fields to load
$aFieldsToLoad = ObjectRepository::GetDefaultFieldsToLoad($aComplementAttributeSpec, $sObjectImageAttCode);
// Optimize columns load
$oDbObjectSet->OptimizeColumnLoad([
$sTargetClass => $aFieldsToLoad,
]);
// Prepare result
$aResult = [];
// Iterate throw objects...
$oDbObjectSet->Rewind();
while ($oObject = $oDbObjectSet->Fetch()) {
// Ignore obsolete data
if (!utils::ShowObsoleteData() && $oObject->IsObsolete()) {
continue;
}
// Prepare objet data
$aObjectData = [];
// Link keys
$aObjectData['link_keys'] = [$oObject->GetKey()];
// In case ot indirect link
if ($sTargetField != null) {
$oObject = MetaModel::GetObject($sTargetClass, $oObject->Get($sTargetField));
}
// Remote key
$aObjectData['key'] = $oObject->GetKey();
// Fill loaded columns...
foreach ($aFieldsToLoad as $sField) {
$aObjectData[$sField] = $oObject->Get($sField);
}
// Compute others data
$aResult[] = ObjectRepository::ComputeOthersData($oObject, $sTargetClass, $aObjectData, $aComplementAttributeSpec, $sObjectImageAttCode);
}
return $aResult;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return null;
}
}
}

View File

@@ -0,0 +1,139 @@
<?php
/**
* Copyright (C) 2013-2022 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
namespace Combodo\iTop\Service\Links;
use CMDBSource;
use Combodo\iTop\Service\Base\iDataPostProcessor;
use DBObjectSet;
use DBSearch;
use Exception;
use ExceptionLog;
use FieldExpression;
/**
* Class LinksBulkDataPostProcessor
*
* @api
*
* @since 3.1.0
* @package Combodo\iTop\Service\Links
*/
class LinksBulkDataPostProcessor implements iDataPostProcessor
{
/** @inheritDoc */
public static function Execute(array $aData, array $aSettings): array
{
return self::ComputeScopeData($aData, $aSettings['bulk_oql'], $aSettings['link_class'], $aSettings['origin_field'], $aSettings['target_field']);
}
/**
* ComputeScopeData.
*
* @param array $aResult
* @param string $sScope
* @param string $sLinkClass
* @param string $sOriginField
* @param string $sTargetField
*
* @return array
*/
public static function ComputeScopeData(array $aResult, string $sScope, string $sLinkClass, string $sOriginField, string $sTargetField): array
{
if (!empty($sScope)) {
try {
// OQL to select bulk object selection
$oDbObjectSearchBulkObjects = DBSearch::FromOQL($sScope);
$oDbObjectSetBulkObjects = new DBObjectSet($oDbObjectSearchBulkObjects);
$aBulksObjects = $oDbObjectSetBulkObjects->GetColumnAsArray('id', false);
$sBulkList = implode(',', $aBulksObjects);
// Get all links attached to object selection
$sOqlGroupBy = "SELECT $sLinkClass AS lnk WHERE lnk.$sOriginField IN ($sBulkList)";
$oDbObjectSearch = DBSearch::FromOQL($sOqlGroupBy);
// Group by links attached to object selection
$oFieldExp = new FieldExpression($sTargetField, 'lnk');
$sQuery = $oDbObjectSearch->MakeGroupByQuery([$sTargetField], array('grouped_by_1' => $oFieldExp), true);
$aGroupResult = CMDBSource::QueryToArray($sQuery, MYSQLI_ASSOC);
// Iterate throw result...
foreach ($aResult as &$aItem) {
// Find group by object to extract link count
$aFound = null;
foreach ($aGroupResult as $aItemGroup) {
if ($aItem['key'] === $aItemGroup['grouped_by_1']) {
$aFound = $aItemGroup;
}
}
// If found, get information
if ($aFound !== null) {
$aItem['group'] = 'Objects already linked';
$aItem['occurrence'] = $aFound['_itop_count_'];
$aItem['occurrence_label'] = "Link on {$aFound['_itop_count_']} Objects(s)";
$aItem['occurrence_info'] = "({$aFound['_itop_count_']})";
$aItem['full'] = ($aFound['_itop_count_'] == $oDbObjectSetBulkObjects->Count());
// Retrieve linked objects keys
$sOqlLinkKeys = "SELECT $sLinkClass AS lnk WHERE lnk.$sOriginField IN ($sBulkList) AND lnk.$sTargetField = {$aItem['key']}";
$oDbSearchLinkKeys = DBSearch::FromOQL($sOqlLinkKeys);
$aLinkedObjects = new DBObjectSet($oDbSearchLinkKeys);
$aItem['link_keys'] = $aLinkedObjects->GetColumnAsArray('id', false);
} else {
$aItem['group'] = 'Others';
$aItem['occurrence'] = '';
$aItem['empty'] = true;
}
}
// Order items
usort($aResult, [self::class, "CompareItems"]);
}
catch (Exception $e) {
ExceptionLog::LogException($e);
}
}
return $aResult;
}
/**
* CompareItems.
*
* @param $aItemA
* @param $aItemB
*
* @return array|int
*/
static private function CompareItems($aItemA, $aItemB): int
{
if ($aItemA['occurrence'] === $aItemB['occurrence']) {
return 0;
}
return ($aItemA['occurrence'] > $aItemB['occurrence']) ? -1 : 1;
}
}

View File

@@ -0,0 +1,15 @@
<div class="friendlyname">
{# Obsolescence #}
<span class="object-ref-icon text_decoration" data-template-condition="obsolescence_flag">
<span class="fas fa-eye-slash object-obsolete fa-1x fa-fw"></span>
</span>
{# Friendly name #}
<span data-template-text="friendlyname"></span>
{# Additional content #}
{% block additional_content %}
{% endblock %}
</div>

View File

@@ -0,0 +1,29 @@
<div class="option ibo-input-select--autocomplete-item" role="option" >
{# Image #}
<span class="ibo-input-select--autocomplete-item-image" data-template-condition="has_image" data-template-css-background-image="picture_url" data-template-text="initials"></span>
{# Desc #}
<span class="ibo-input-select--autocomplete-item-txt">
{# Obsolescnce #}
<span class="object-ref-icon text_decoration" data-template-condition="obsolescence_flag">
<span class="fas fa-eye-slash object-obsolete fa-1x fa-fw"></span>
</span>
{# Friendly name #}
<span data-template-text="friendlyname"></span>
{# Additional content #}
{% block additional_content %}
{% endblock %}
{# Additional field #}
<div>
<i data-template-text="additional_field"></i>
</div>
</span>
</div>

View File

@@ -0,0 +1,13 @@
<div data-template-attr-title="friendlyname">
{# obsolescence #}
{% if obsolescence_flag %}
<span class="object-ref-icon text_decoration">
<span class="fas fa-eye-slash object-obsolete fa-1x fa-fw"></span>
</span>
{% endif %}
{# friendly name #}
<span data-template-text="friendlyname">{{ friendlyname }}</span>
</div>

View File

@@ -1,7 +1,8 @@
{% block iboInputLabel %} {% block iboInputLabel %}
{% endblock %} {% endblock %}
{% block iboInput %} {% block iboInput %}
<input type="{{ oUIBlock.GetType() }}" id="{{ oUIBlock.GetId() }}" name="{{ oUIBlock.GetName() }}" value="{{ oUIBlock.GetValue()|raw }}"
<input type="{{ oUIBlock.GetType() }}" id="{{ oUIBlock.GetId() }}" name="{{ oUIBlock.GetName() }}" value="{{ oUIBlock.GetValue() }}"
class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }} {{ oUIBlock.GetAdditionalCSSClassesAsString() }} {% if oUIBlock.IsHidden() %}ibo-is-hidden{% endif %}" class="{{ oUIBlock.GetBlocksInheritanceCSSClassesAsString() }} {{ oUIBlock.GetAdditionalCSSClassesAsString() }} {% if oUIBlock.IsHidden() %}ibo-is-hidden{% endif %}"
data-role="ibo-input" data-role="ibo-input"
{% if oUIBlock.IsChecked() %} checked="checked"{% endif %} {% if oUIBlock.IsChecked() %} checked="checked"{% endif %}

View File

@@ -0,0 +1,26 @@
{# @copyright Copyright (C) 2010-2021 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{# Set ui block #}
<select
id="{{ oUIBlock.GetId() }}"
name="{{ oUIBlock.GetName() }}"
multiple
style="display: none;"
>
</select>
{# Options template #}
{% if oUIBlock.HasOptionsTemplate() %}
<template id="{{ oUIBlock.GetId() }}_options_template">
{% include oUIBlock.GetOptionsTemplate() %}
</template>
{% endif %}
{# Items template #}
{% if oUIBlock.HasItemsTemplate() %}
<template id="{{ oUIBlock.GetId() }}_items_template">
{% include oUIBlock.GetItemsTemplate() %}
</template>
{% endif %}

View File

@@ -0,0 +1,206 @@
{# @copyright Copyright (C) 2010-2021 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
{# SET WIDGET #}
{% set oDataProvider = oUIBlock.GetDataProvider() %}
let oWidget{{ oUIBlock.GetId() }} = $('#{{ oUIBlock.GetId() }}').selectize({
{# Global options #}
{% if oDataProvider.IsAjaxProviderType %}
preload: true, {# call ajax directly #}
loadingClass: '',
{% endif %}
itemClass: 'item attribute-set-item',
hasError: {{ oUIBlock.HasError()|var_export() }},
placeholder: '{{ oUIBlock.GetPlaceholder() }}',
{# Remove button plugin #}
plugins: {
{# PLUGIN update operations #}
'combodo_update_operations' : {},
{# PLUGIN combodo auto position #}
'combodo_auto_position' : {
maxDropDownHeight: 300, {# in px #}
},
{# PLUGIN combodo add button #}
{% if oUIBlock.HasAddOptionButton() %}
'combodo_add_button' : {
title: '{{ oUIBlock.GetAddButtonTitle() }}'
},
{% endif %}
{% if oUIBlock.IsMultiValuesSynthesis() %}
'combodo_multi_values_synthesis' : {
tooltip_links_will_be_created_for_all_objects: '{{ 'UI:Links:Bulk:LinkWillBeCreatedForAllObjects'|dict_s }}',
tooltip_links_will_be_deleted_from_all_objects: '{{ 'UI:Links:Bulk:LinkWillBeDeletedFromAllObjects'|dict_s }}',
tooltip_links_will_be_created_for_one_object: '{{ 'UI:Links:Bulk:LinkWillBeCreatedFor1Object'|dict_s }}',
tooltip_links_will_be_deleted_from_one_object: '{{ 'UI:Links:Bulk:LinkWillBeDeletedFrom1Object'|dict_s }}',
tooltip_links_will_be_created_for_x_objects: '{{ 'UI:Links:Bulk:LinkWillBeCreatedForXObjects'|dict_s }}',
tooltip_links_will_be_deleted_from_x_objects: '{{ 'UI:Links:Bulk:LinkWillBeDeletedFromXObjects'|dict_s }}',
tooltip_links_exist_for_all_objects: '{{ 'UI:Links:Bulk:LinkExistForAllObjects'|dict_s }}',
tooltip_links_exist_for_one_object: '{{ 'UI:Links:Bulk:LinkExistForOneObject'|dict_s }}',
tooltip_links_exist_for_x_objects: '{{ 'UI:Links:Bulk:LinkExistForXObjects'|dict_s }}',
},
{% endif %}
{# PLUGIN remove button #}
{% if oUIBlock.HasRemoveItemButton() %}
'remove_button' : {},
{% endif %}
},
{# Max items you can select #}
{% if oUIBlock.GetMaxItems() is not empty %}
maxItems: {{ oUIBlock.GetMaxItems() }},
{% endif %}
{# Max options available #}
{% if oUIBlock.GetMaxOptions() is not empty %}
maxOptions: {{ oUIBlock.GetMaxOptions() }},
{% endif %}
{# Data fields #}
valueField: '{{ oDataProvider.GetDataValueField() }}',
labelField: '{{ oDataProvider.GetDataLabelField() }}',
searchField: {{ oDataProvider.GetDataSearchFields()|json_encode()|raw }},
optgroupField: '{{ oDataProvider.GetGroupField() }}',
tooltipField: '{{ oDataProvider.GetTooltipField() }}',
{# Initial options data, may be oveeride by ajax load method #}
options: {{ oDataProvider.GetOptions()|json_encode()|raw }},
{# Groups data #}
optgroups: {{ oDataProvider.GetOptionsGroups()|json_encode()|raw }},
{# Items data #}
initial: {{ oUIBlock.GetValue()|raw }},
items: {{ oUIBlock.GetValue()|raw }},
inputClass: 'ibo-input ibo-input-selectize ibo-input-set attribute-set selectize-input',
{# Ajax data load #}
{% if oDataProvider.IsAjaxProviderType %}
load: function (query, callback) {
let me = this;
$.ajax({
url: '{{ get_absolute_url_app_root() }}pages/ajax.render.php?route={{ oDataProvider.GetRoute() }}&search=' + query + '{{ oDataProvider.GetParamsAsQueryString|raw }}',
type: 'POST',
dataType: 'json',
data: me.convertParamArray('{{ oDataProvider.GetPostParamsAsJsonString()|raw }}'),
error: function (e) {
callback();
console.error(e);
if(!me.settings.hasError) {
me.toggleErrorClass(true);
}
},
success: function (res) {
// Handle errors
if(!me.settings.hasError){
me.toggleErrorClass(!res.data.success);
if(!res.data.success) return;
}
// Retrieve current input value
let aSelectedItems = me.getValue();
// Filter old options data to keep selected values
let options = Object.values(me.options);
options = options.filter(item => aSelectedItems.includes(item['{{ oDataProvider.GetDataValueField() }}']));
// Merge kept and new values
options = $.merge(options, res.data.search_data);
// Compute groups
$.each(options, function(index, value) {
me.addOptionGroup(value['{{ oDataProvider.GetGroupField() }}'], {
label: value['{{ oDataProvider.GetGroupField() }}'],
value: value['{{ oDataProvider.GetGroupField() }}']
});
});
// Clear all options
me.clearOptions();
// Add merged values
callback(options);
// Restore input value
me.addItems(aSelectedItems, true);
}
});
},
{% endif %}
{# Renderers #}
render: {
{# Options #}
{% if oUIBlock.HasOptionsTemplate() %}
option: function(option) {
return CombodoGlobalToolbox.RenderTemplate('#{{ oUIBlock.GetId() }}_options_template', option, this.settings.optionClass)[0].outerHTML;
},
{% endif %}
{# Items #}
{% if oUIBlock.HasItemsTemplate() %}
item: function (item) {
return CombodoGlobalToolbox.RenderTemplate('#{{ oUIBlock.GetId() }}_items_template', item, this.settings.itemClass)[0].outerHTML;
},
{% endif %}
},
onInitialize: function(){
/**
* Function to convert param array.
*
* EVAL_JAVASCRIPT{code_to_eval}
*/
this.convertParamArray = function(paramArray){
let postParam = JSON.parse(paramArray);
let data = {};
Object.entries(postParam).forEach(([key, value]) => {
let matches = null;
if(typeof(value) === 'string'){
matches = value.match(/^EVAL_JAVASCRIPT{(.*)}$/);
}
if(matches != null){
data[key] = eval(matches[1]);
}
else{
data[key] = value;
}
});
return data;
};
/**
* Function to show error.
*
*/
this.toggleErrorClass = function(bValue){
this.$control.toggleClass('selectize-input-error', bValue);
};
{# Error #}
this.toggleErrorClass(this.settings.hasError);
},
{# On item add #}
onItemAdd: function(value, $item){
$item.addClass(this.settings.itemClass);
$item.attr({
'data-tooltip-content': this.options[value][this.settings.tooltipField],
'data-tooltip-html-enabled': true
});
CombodoTooltip.InitTooltipFromMarkup($item);
},
{# plugin combodo_add_button #}
{% if oUIBlock.HasAddOptionButton() %}
onAdd: function(){
alert('todo on add option');
},
{% endif %}
});

View File

@@ -0,0 +1,15 @@
{# @copyright Copyright (C) 2010-2021 Combodo SARL #}
{# @license http://opensource.org/licenses/AGPL-3.0 #}
<div class="option simple-option-renderer" role="option">
<div class="simple-option-renderer--container" data-template-add-class="class">
<i class="simple-option-renderer--container--icon" data-template-add-class="icon"></i>
<span class="simple-option-renderer--container--label" data-template-text="label">
</span>
</div>
</div>

View File

@@ -28,13 +28,12 @@ use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroup; use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroup;
use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection; use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
use Combodo\iTop\Application\UI\Base\Component\Dashlet\DashletBadge;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTable;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet; use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet;
use Combodo\iTop\Application\UI\Base\Component\Html\Html; use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Set\SetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel; use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Pill\PillFactory; use Combodo\iTop\Application\UI\Base\Component\Pill\PillFactory;
@@ -407,24 +406,163 @@ $oPage->AddUiBlock(DataTableUIBlockFactory::MakeForStaticData('Static datatable'
), array( ), array(
'a' => 'A3', 'b' => 'B3', 'c' => 'C3', 'd' => 'D3' 'a' => 'A3', 'b' => 'B3', 'c' => 'C3', 'd' => 'D3'
), array( ), array(
'a' => 'A4', 'b' => 'B4', 'c' => 'C4', 'd' => 'D4' 'a' => 'A4',
),array( 'b' => 'B4',
'@class' => 'ibo-is-red','a' => 'A5 (Red highlighting)', 'b' => 'B5', 'c' => 'C5', 'd' => 'D5' 'c' => 'C4',
),array( 'd' => 'D4',
'@class' => 'ibo-is-danger','a' => 'A6 (Danger highlighting)', 'b' => 'B6', 'c' => 'C6', 'd' => 'D6' ),
),array( array(
'@class' => 'ibo-is-orange','a' => 'A7 (Orange highlighting)', 'b' => 'B7', 'c' => 'C7', 'd' => 'D7' '@class' => 'ibo-is-red',
),array( 'a' => 'A5 (Red highlighting)',
'@class' => 'ibo-is-warning','a' => 'A8 (Warning highlighting)', 'b' => 'B8', 'c' => 'C8', 'd' => 'D8' 'b' => 'B5',
),array( 'c' => 'C5',
'@class' => 'ibo-is-blue','a' => 'A9 (Blue highlighting)', 'b' => 'B9', 'c' => 'C9', 'd' => 'D9' 'd' => 'D5',
),array( ),
'@class' => 'ibo-is-info','a' => 'A10 (Info highlighting)', 'b' => 'B10', 'c' => 'C10', 'd' => 'D10' array(
),array( '@class' => 'ibo-is-danger',
'@class' => 'ibo-is-green','a' => 'A11 (Green highlighting)', 'b' => 'B11', 'c' => 'C11', 'd' => 'D11' 'a' => 'A6 (Danger highlighting)',
),array( 'b' => 'B6',
'@class' => 'ibo-is-success','a' => 'A12 (Success highlighting)', 'b' => 'B12', 'c' => 'C12', 'd' => 'D12' 'c' => 'C6',
), 'd' => 'D6',
))); ),
array(
'@class' => 'ibo-is-orange',
'a' => 'A7 (Orange highlighting)',
'b' => 'B7',
'c' => 'C7',
'd' => 'D7',
),
array(
'@class' => 'ibo-is-warning',
'a' => 'A8 (Warning highlighting)',
'b' => 'B8',
'c' => 'C8',
'd' => 'D8',
),
array(
'@class' => 'ibo-is-blue',
'a' => 'A9 (Blue highlighting)',
'b' => 'B9',
'c' => 'C9',
'd' => 'D9',
),
array(
'@class' => 'ibo-is-info',
'a' => 'A10 (Info highlighting)',
'b' => 'B10',
'c' => 'C10',
'd' => 'D10',
),
array(
'@class' => 'ibo-is-green',
'a' => 'A11 (Green highlighting)',
'b' => 'B11',
'c' => 'C11',
'd' => 'D11',
),
array(
'@class' => 'ibo-is-success',
'a' => 'A12 (Success highlighting)',
'b' => 'B12',
'c' => 'C12',
'd' => 'D12',
),
)));
/////////
// Set
/////////
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Set examples', 2));
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Simple', 4));
$aOptions = [
[
'label' => 'Chien',
'value' => 'dog',
'icon' => 'fas fa-dog',
'group' => 'Domestique',
],
[
'label' => 'Chat',
'value' => 'cat',
'icon' => 'fas fa-cat',
'group' => 'Domestique',
],
[
'label' => 'Cheval',
'value' => 'horse',
'icon' => 'fas fa-horse',
'group' => 'Domestique',
],
[
'label' => 'Araignée',
'value' => 'spider',
'icon' => 'fas fa-spider',
'class' => 'demo_set',
'group' => 'Sauvage',
],
[
'label' => 'Otarie',
'value' => 'otter',
'icon' => 'fas fa-otter',
'group' => 'Sauvage',
],
[
'label' => 'Poisson',
'value' => 'fish',
'icon' => 'fas fa-fish',
'group' => 'Domestique',
],
[
'label' => 'Grenouille',
'value' => 'frog',
'icon' => 'fas fa-frog',
'group' => 'Sauvage',
],
[
'label' => 'Hippopotame',
'value' => 'hippo',
'icon' => 'fas fa-hippo',
'group' => 'Sauvage',
],
];
$oPage->add_style('.demo_set{color:red;}');
$oSimpleSetBlock = SetUIBlockFactory::MakeForSimple('SetSimple', $aOptions, 'label', 'value', ['label']);
$oSimpleSetBlock->SetName('SimpleSetBlock');
$oPage->AddUiBlock($oSimpleSetBlock);
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Add Option Button', 3));
$oSimpleAddSetBlock = SetUIBlockFactory::MakeForSimple('SetWithAddOption', $aOptions, 'label', 'value', ['label']);
$oSimpleAddSetBlock->SetName('SetWithAddOption');
$oSimpleAddSetBlock->SetHasAddOptionButton(true);
$oPage->AddUiBlock($oSimpleAddSetBlock);
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Renderer', 3));
$oSimpleSetBlockRenderer = SetUIBlockFactory::MakeForSimple('SetRenderer', $aOptions, 'label', 'value', ['label']);
$oSimpleSetBlockRenderer->SetName('SimpleSetBlockWithRenderer');
$oSimpleSetBlockRenderer->SetOptionsTemplate('base/components/input/set/simple_option_renderer.html.twig');
$oSimpleSetBlockRenderer->SetItemsTemplate('base/components/input/set/simple_option_renderer.html.twig');
$oPage->AddUiBlock($oSimpleSetBlockRenderer);
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Grouping', 3));
$oSimpleSetBlockGroup = SetUIBlockFactory::MakeForSimple('SetGroup', $aOptions, 'label', 'value', ['label'], 'group');
$oSimpleSetBlockGroup->SetName('SimpleSetBlockWithGroup');
$oPage->AddUiBlock($oSimpleSetBlockGroup);
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('OQL', 3));
$oSimpleSetBlockOql = SetUIBlockFactory::MakeForOQL('SetOql', 'Person', 'SELECT Person');
$oSimpleSetBlockOql->SetName('OqlSet');
$oPage->AddUiBlock($oSimpleSetBlockOql);
$oSimpleSetBlockOql2 = SetUIBlockFactory::MakeForOQL('SetOql2', 'Location', 'SELECT Location');
$oSimpleSetBlockOql2->SetName('OqlSet2');
$oPage->AddUiBlock($oSimpleSetBlockOql2);
$oPage->output(); $oPage->output();