Finish b931

This commit is contained in:
Pierre Goiffon
2018-09-28 16:42:49 +02:00
102 changed files with 17351 additions and 5546 deletions

View File

@@ -2,11 +2,7 @@
<profile version="1.0">
<option name="myName" value="Combodo" />
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="JSUnfilteredForInLoop" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="JSUnusedLocalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myIgnoreUnusedFunctionParameters" value="true" />
<option name="myIgnoreParametersBeforeUsed" value="false" />
</inspection_tool>
<inspection_tool class="MysqlParsingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpIncludeInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpMethodParametersCountMismatchInspection" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="PhpTooManyParametersInspection" enabled="true" level="WARNING" enabled_by_default="true">
@@ -15,11 +11,34 @@
<inspection_tool class="PhpUndefinedClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="DONT_REPORT_MULTI_RESOLVE" value="true" />
</inspection_tool>
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUnhandledExceptionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PhpUnusedLocalVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="DONT_REPORT_INSIDE_LIST" value="true" />
</inspection_tool>
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="DONT_REPORT_ABSTRACT_CLASS" value="true" />
</inspection_tool>
<inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlCheckUsingColumnsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlConstantConditionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDeprecateTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDerivedTableAliasInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlDropIndexedColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlSideEffectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlStorageInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlTypeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SqlUnusedVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="projectProfile" value="Combodo" />
<option name="PROJECT_PROFILE" value="Combodo" />
<version value="1.0" />
</settings>
</component>

View File

@@ -1165,6 +1165,14 @@ class RestUtils
}
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
}
elseif ($oAttDef instanceof AttributeTagSet)
{
if (!is_array($value))
{
throw new Exception("A tag set must be defined by an array of tag codes");
}
$value = $oAttDef->FromJSONToValue($value);
}
else
{
$value = $oAttDef->FromJSONToValue($value);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<portals>
<portal id="legacy_portal" _delta="define">
<url>portal/index.php</url>

View File

@@ -259,6 +259,36 @@ EOF;
EOF
);
// Attribute set tooltip on items
$this->add_ready_script(
<<<EOF
$('.attribute-set-item').each(function(){
var sLabel = $('<div/>').text($(this).attr('data-label')).html();
var sDescription = $(this).attr('data-description');
// Make nice tooltip if item has a description, otherwise just make a title attribute so the truncated label can be read.
if(sDescription !== '')
{
$(this).qtip({
content: {
// Encoding only title as the content is already sanitized by the HTML attribute.
text: sDescription,
title: { text: sLabel},
},
show: { delay: 300, when: 'mouseover' },
hide: { delay: 140, when: 'mouseout', fixed: true },
style: { name: 'dark', tip: 'bottomLeft' },
position: { corner: { target: 'topMiddle', tooltip: 'bottomLeft' }}
});
}
else
{
$(this).attr('title', sLabel);
}
});
EOF
);
$this->add_init_script(
<<< EOF
try

View File

@@ -62,6 +62,7 @@ class NiceWebPage extends WebPage
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_field.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_numeric.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_enum.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_tag_set.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_external_key.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_hierarchical_key.js');
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_abstract.js');

View File

@@ -47,7 +47,7 @@ abstract class Query extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("fields", array("allowed_values"=>null, "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeQueryAttCodeSet("fields", array("allowed_values"=>null,"max_items" => 1000, "query_field" => "oql", "sql"=>"fields", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array('oql'))));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'fields')); // Attributes to be displayed for the complete details
@@ -136,6 +136,39 @@ class QueryOQL extends Query
}
return $aFieldsMap;
}
public function ComputeValues()
{
parent::ComputeValues();
// Remove unwanted attribute codes
$aChanges = $this->ListChanges();
if (isset($aChanges['fields']))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), 'fields');
$aArgs = array('this' => $this);
$aAllowedValues = $oAttDef->GetAllowedValues($aArgs);
/** @var \ormSet $oValue */
$oValue = $this->Get('fields');
$aValues = $oValue->GetValues();
$bChanged = false;
foreach($aValues as $key => $sValue)
{
if (!isset($aAllowedValues[$sValue]))
{
unset($aValues[$key]);
$bChanged = true;
}
}
if ($bChanged)
{
$oValue->SetValues($aValues);
$this->Set('fields', $oValue);
}
}
}
}
?>

View File

@@ -311,7 +311,10 @@ class WebPage implements Page
}
/**
* Add a script (as an include, i.e. link) to the header of the page
* Add a script (as an include, i.e. link) to the header of the page.<br>
* Handles duplicates : calling twice with the same script will add the script only once
*
* @param string $s_linked_script
*/
public function add_linked_script($s_linked_script)
{

View File

@@ -174,6 +174,14 @@ class WizardHelper
}
$oObj->Set($sAttCode, $value);
}
else if ($oAttDef instanceof AttributeSet) // AttributeDate is derived from AttributeDateTime
{
$value = json_decode($value, true);
$oTagSet = new ormTagSet(get_class($oObj), $sAttCode);
$oTagSet->SetValues($value['orig_value']);
$oTagSet->ApplyDelta($value);
$oObj->Set($sAttCode, $oTagSet);
}
else
{
$oObj->Set($sAttCode, $value);

View File

@@ -8,7 +8,8 @@
"ext-soap": "*",
"ext-json": "*",
"ext-zip": "*",
"ext-mysqli": "*"
"ext-mysqli": "*",
"ext-dom": "*"
},
"config": {
"platform": {

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ MetaModel::IncludeModule('core/action.class.inc.php');
MetaModel::IncludeModule('core/trigger.class.inc.php');
MetaModel::IncludeModule('core/bulkexport.class.inc.php');
MetaModel::IncludeModule('core/ownershiplock.class.inc.php');
MetaModel::IncludeModule('core/tagfield.class.inc.php');
MetaModel::IncludeModule('core/tagsetfield.class.inc.php');
MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
MetaModel::IncludeModule('core/inlineimage.class.inc.php');

View File

@@ -242,6 +242,64 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
return $sResult;
}
}
/**
* Record the modification of a tag set attribute
*
* @package iTopORM
*/
class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute
{
public static function Init()
{
$aParams = array
(
"category" => "core/cmdb",
"key_type" => "",
"name_attcode" => "change",
"state_attcode" => "",
"reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_tagset",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
}
/**
* Describe (as a text string) the modifications corresponding to this change
*/
public function GetDescription()
{
$sResult = '';
$sTargetObjectClass = $this->Get('objclass');
$oTargetObjectKey = $this->Get('objkey');
$sAttCode = $this->Get('attcode');
$oTargetSearch = new DBObjectSearch($sTargetObjectClass);
$oTargetSearch->AddCondition('id', $oTargetObjectKey, '=');
$oMonoObjectSet = new DBObjectSet($oTargetSearch);
if (UserRights::IsActionAllowedOnAttribute($sTargetObjectClass, $sAttCode, UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES)
{
if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes...
$oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode'));
$sAttName = $oAttDef->GetLabel();
$sNewValue = $this->Get('newvalue');
$sOldValue = $this->Get('oldvalue');
$sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue);
}
return $sResult;
}
}
/**
* Record the modification of an URL
*

View File

@@ -409,7 +409,19 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("newvalue", $value);
$iId = $oMyChangeOp->DBInsertNoReload();
}
else
elseif ($oAttDef instanceOf AttributeTagSet)
{
// Tag Set
//
$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeTagSet");
$oMyChangeOp->Set("objclass", get_class($this));
$oMyChangeOp->Set("objkey", $this->GetKey());
$oMyChangeOp->Set("attcode", $sAttCode);
$oMyChangeOp->Set("oldvalue", implode(' ', $original->GetValues()));
$oMyChangeOp->Set("newvalue", implode(' ', $value->GetValues()));
$iId = $oMyChangeOp->DBInsertNoReload();
}
else
{
// Scalars
//
@@ -551,6 +563,12 @@ abstract class CMDBObject extends DBObject
$this->DBUpdate();
}
/**
* @param null $oDeletionPlan
*
* @return \DeletionPlan|null
* @throws \DeleteException
*/
public function DBDelete(&$oDeletionPlan = null)
{
return $this->DBDeleteTracked_Internal($oDeletionPlan);
@@ -563,6 +581,12 @@ abstract class CMDBObject extends DBObject
$this->DBDeleteTracked_Internal($oDeletionPlan);
}
/**
* @param null $oDeletionPlan
*
* @return \DeletionPlan|null
* @throws \DeleteException
*/
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
$prevkey = $this->GetKey();

View File

@@ -408,6 +408,14 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'tag_set_item_separator' => array(
'type' => 'string',
'description' => 'Tag set from string: tag label separator',
'default' => '|',
'value' => '|',
'source_of_value' => '',
'show_in_conf_sample' => true,
),
'cron_max_execution_time' => array(
'type' => 'integer',
'description' => 'Duration (seconds) of the page cron.php, must be shorter than php setting max_execution_time and shorter than the web server response timeout',

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<user_rights>
<profiles>
<profile id="1024" _delta="define">

View File

@@ -978,6 +978,28 @@ abstract class DBObject implements iDisplay
return MetaModel::GetClassIcon(get_class($this), $bImgTag);
}
/**
* Get the name as defined in the dictionary
* @return string (empty for default name scheme)
*/
public static function GetClassName($sClass)
{
$sStringCode = 'Class:'.$sClass;
return Dict::S($sStringCode, str_replace('_', ' ', $sClass));
}
/**
* Get the description as defined in the dictionary
* @param string $sClass
*
* @return string
*/
final static public function GetClassDescription($sClass)
{
$sStringCode = 'Class:'.$sClass.'+';
return Dict::S($sStringCode, '');
}
/**
* Gets the name of an object in a safe manner for displaying inside a web page
* @return string
@@ -1198,6 +1220,15 @@ abstract class DBObject implements iDisplay
// check if the given (or current) value is suitable for the attribute
// return true if successfull
// return the error desciption otherwise
/**
* @param $sAttCode
* @param null $value
*
* @return bool|string
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \OQLException
*/
public function CheckValue($sAttCode, $value = null)
{
if (!is_null($value))
@@ -1247,6 +1278,57 @@ abstract class DBObject implements iDisplay
}
}
}
elseif ($oAtt instanceof AttributeTagSet)
{
if (is_string($toCheck))
{
$oTag = new ormTagSet(get_class($this), $sAttCode);
try
{
$oTag->SetValues(explode(' ', $toCheck));
} catch (Exception $e)
{
return "Tag value '$toCheck' is not a valid tag list";
}
return true;
}
if ($toCheck instanceof ormTagSet)
{
return true;
}
return "Bad type";
}
elseif ($oAtt instanceof AttributeClassAttCodeSet)
{
if (is_string($toCheck))
{
$oTag = new ormSet(get_class($this), $sAttCode);
try
{
$aValues = array();
foreach(explode(',', $toCheck) as $sValue)
{
$aValues[] = trim($sValue);
}
$oTag->SetValues($aValues);
} catch (Exception $e)
{
return "Set value '$toCheck' is not a valid set";
}
return true;
}
if ($toCheck instanceof ormSet)
{
return true;
}
return "Bad type";
}
elseif ($oAtt->IsScalar())
{
$aValues = $oAtt->GetAllowedValues($this->ToArgsForQuery());
@@ -2159,6 +2241,17 @@ abstract class DBObject implements iDisplay
if (!MetaModel::DBIsReadOnly())
{
$this->OnDelete();
// Activate any existing trigger
$sClass = get_class($this);
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectDelete AS t WHERE t.target_class IN ('$sClassList')"));
while ($oTrigger = $oSet->Fetch())
{
/** @var \Trigger $oTrigger */
$oTrigger->DoActivate($this->ToArgs('this'));
}
$this->RecordObjDeletion($this->m_iKey); // May cause a reload for storing history information
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)

View File

@@ -480,6 +480,11 @@ class DBObjectSearch extends DBSearch
$oNewCondition = Expression::FromOQL($sOQLCondition);
break;
case 'MATCHES':
$oRightExpr = new ScalarExpression($value);
$oNewCondition = new MatchExpression($oField, $oRightExpr);
break;
case 'Contains':
case 'Begins with':
case 'Finishes with':
@@ -1314,6 +1319,13 @@ class DBObjectSearch extends DBSearch
$oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
return new BinaryExpression($oLeft, $sOperator, $oRight);
}
elseif ($oExpression instanceof MatchOqlExpression)
{
$oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases);
$oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
return new MatchExpression($oLeft, $oRight);
}
elseif ($oExpression instanceof FieldOqlExpression)
{
$sClassAlias = $oExpression->GetParent();

View File

@@ -815,8 +815,8 @@ class DBObjectSet implements iDBObjectSetIterator
if ($resQuery)
{
$aRow = CMDBSource::FetchArray($resQuery);
CMDBSource::FreeResult($resQuery);
$iCount = intval($aRow['COUNT']);
CMDBSource::FreeResult($resQuery);
}
else
{

View File

@@ -116,6 +116,22 @@ class Dict
self::$m_iErrorMode = $iErrorMode;
}
/**
* Check if a dictionary entry exists or not
* @param $sStringCode
*
* @return bool
*/
public static function Exists($sStringCode)
{
$sImpossibleString = 'aVlHYKEI3TZuDV5o0pghv7fvhYNYuzYkTk7WL0Zoqw8rggE7aq';
if (static::S($sStringCode, $sImpossibleString) === $sImpossibleString)
{
return false;
}
return true;
}
/**
* Returns a localised string from the dictonary
*

View File

@@ -188,11 +188,16 @@ EOF
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormDocument)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormDocument)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else if ($value instanceOf ormTagSet)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
$sRet = $oAttDef->GetAsCSV($value, '', '', $oObj);
}
else
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);

View File

@@ -320,8 +320,7 @@ abstract class MetaModel
final static public function GetName($sClass)
{
self::_check_subclass($sClass);
$sStringCode = 'Class:'.$sClass;
return Dict::S($sStringCode, str_replace('_', ' ', $sClass));
return $sClass::GetClassName($sClass);
}
/**
@@ -411,8 +410,7 @@ abstract class MetaModel
final static public function GetClassDescription($sClass)
{
self::_check_subclass($sClass);
$sStringCode = 'Class:'.$sClass.'+';
return Dict::S($sStringCode, '');
return $sClass::GetClassDescription($sClass);
}
/**
@@ -1024,7 +1022,7 @@ abstract class MetaModel
/**
* array of ("classname" => array of attributes)
*
* @var array
* @var \AttributeDefinition[]
*/
private static $m_aAttribDefs = array();
/**

View File

@@ -42,7 +42,7 @@
* @copyright 2006 Gregory Beaver
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
*/
require_once 'PEAR/Exception.php';
//require_once 'PEAR/Exception.php';
/**
* @package PHP_LexerGenerator
* @author Gregory Beaver <cellog@php.net>
@@ -51,5 +51,5 @@ require_once 'PEAR/Exception.php';
* @version @package_version@
* @since File available since Release 0.1.0
*/
class PHP_LexerGenerator_Exception extends PEAR_Exception {}
class PHP_LexerGenerator_Exception extends Exception {}
?>

View File

@@ -22,6 +22,9 @@ class MissingQueryArgument extends CoreException
}
/**
* @method Check($oModelReflection, array $aAliases, $sSourceQuery)
*/
abstract class Expression
{
/**
@@ -575,6 +578,7 @@ class BinaryExpression extends Expression
* @param null $oAttDef
*
* @return array
* @throws \MissingQueryArgument
*/
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
@@ -673,6 +677,10 @@ class BinaryExpression extends Expression
}
}
}
if (isset($aCriteriaLeft['widget']) && isset($aCriteriaRight['widget']) && ($aCriteriaLeft['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET) && ($aCriteriaRight['widget'] == AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET))
{
$aCriteriaOverride['operator'] = 'MATCHES';
}
}
return array_merge($aCriteriaLeft, $aCriteriaRight, $aCriteriaOverride);
@@ -722,7 +730,9 @@ class MatchExpression extends BinaryExpression
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
{
/** @var \FieldExpression $oLeft */
$oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
/** @var \ScalarExpression $oRight */
$oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
return new static($oLeft, $oRight);
@@ -883,20 +893,20 @@ class ScalarExpression extends UnaryExpression
public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
{
$aCriteria = array();
$aCriterion = array();
switch ((string)($this->m_value))
{
case '%Y-%m-%d':
$aCriteria['unit'] = 'DAY';
$aCriterion['unit'] = 'DAY';
break;
case '%Y-%m':
$aCriteria['unit'] = 'MONTH';
$aCriterion['unit'] = 'MONTH';
break;
case '%w':
$aCriteria['unit'] = 'WEEKDAY';
$aCriterion['unit'] = 'WEEKDAY';
break;
case '%H':
$aCriteria['unit'] = 'HOUR';
$aCriterion['unit'] = 'HOUR';
break;
default:
$aValue = array();
@@ -914,13 +924,47 @@ class ScalarExpression extends UnaryExpression
$oObj = MetaModel::GetObject($sTarget, $this->GetValue());
$aValue['label'] = $oObj->Get("friendlyname");
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
}
else
{
$aValue['label'] = Dict::S('Enum:Undefined');
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
}
} catch (Exception $e)
{
IssueLog::Error($e->getMessage());
}
catch (Exception $e)
break;
case ($oAttDef instanceof AttributeTagSet):
try
{
if (!empty($this->GetValue()))
{
$aValues = array();
$oValue = $this->GetValue();
if (is_string($oValue))
{
$oValue = $oAttDef->GetExistingTagsFromString($oValue, true);
}
/** @var \ormTagSet $oValue */
$aTags = $oValue->GetTags();
foreach($aTags as $oTag)
{
$aValue['label'] = $oTag->Get('label');
$aValue['value'] = $oTag->Get('code');
$aValues[] = $aValue;
}
$aCriterion['values'] = $aValues;
}
else
{
$aCriterion['has_undefined'] = true;
}
} catch (Exception $e)
{
IssueLog::Error($e->getMessage());
}
@@ -934,13 +978,16 @@ class ScalarExpression extends UnaryExpression
$sTarget = $oAttDef->GetTargetClass();
$oObj = MetaModel::GetObject($sTarget, $this->GetValue(), true, true);
$aValue['label'] = $oObj->Get("friendlyname");
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
}
else
{
$aValue['label'] = Dict::S('Enum:Undefined');
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
}
}
catch (Exception $e)
} catch (Exception $e)
{
// This object cannot be seen... ignore
}
@@ -949,23 +996,22 @@ class ScalarExpression extends UnaryExpression
try
{
$aValue['label'] = $oAttDef->GetAsPlainText($this->GetValue());
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
} catch (Exception $e)
{
$aValue['label'] = $this->GetValue();
$aValue['value'] = $this->GetValue();
$aCriterion['values'] = array($aValue);
}
break;
}
}
if (!empty($aValue))
{
// only if a label is found
$aValue['value'] = $this->GetValue();
$aCriteria['values'] = array($aValue);
}
break;
}
$aCriteria['oql'] = $this->RenderExpression(false, $aArgs, $bRetrofitParams);
return $aCriteria;
$aCriterion['oql'] = $this->RenderExpression(false, $aArgs, $bRetrofitParams);
return $aCriterion;
}
}
@@ -1572,7 +1618,7 @@ class ListExpression extends Expression
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar($aArgs);
}
else
{
@@ -2116,7 +2162,7 @@ class CharConcatExpression extends Expression
{
if ($oExpr instanceof VariableExpression)
{
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar();
$this->m_aExpressions[$idx] = $oExpr->GetAsScalar($aArgs);
}
else
{

View File

@@ -140,6 +140,7 @@ class OQLLexerRaw
'/\GNOT LIKE/ ',
'/\GIN/ ',
'/\GNOT IN/ ',
'/\GMATCHES/ ',
'/\GINTERVAL/ ',
'/\GIF/ ',
'/\GELT/ ',
@@ -441,204 +442,209 @@ class OQLLexerRaw
function yy_r1_33($yy_subpatterns)
{
$this->token = OQLParser::INTERVAL;
$this->token = OQLParser::MATCHES;
}
function yy_r1_34($yy_subpatterns)
{
$this->token = OQLParser::F_IF;
$this->token = OQLParser::INTERVAL;
}
function yy_r1_35($yy_subpatterns)
{
$this->token = OQLParser::F_ELT;
$this->token = OQLParser::F_IF;
}
function yy_r1_36($yy_subpatterns)
{
$this->token = OQLParser::F_COALESCE;
$this->token = OQLParser::F_ELT;
}
function yy_r1_37($yy_subpatterns)
{
$this->token = OQLParser::F_ISNULL;
$this->token = OQLParser::F_COALESCE;
}
function yy_r1_38($yy_subpatterns)
{
$this->token = OQLParser::F_CONCAT;
$this->token = OQLParser::F_ISNULL;
}
function yy_r1_39($yy_subpatterns)
{
$this->token = OQLParser::F_SUBSTR;
$this->token = OQLParser::F_CONCAT;
}
function yy_r1_40($yy_subpatterns)
{
$this->token = OQLParser::F_TRIM;
$this->token = OQLParser::F_SUBSTR;
}
function yy_r1_41($yy_subpatterns)
{
$this->token = OQLParser::F_DATE;
$this->token = OQLParser::F_TRIM;
}
function yy_r1_42($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_FORMAT;
$this->token = OQLParser::F_DATE;
}
function yy_r1_43($yy_subpatterns)
{
$this->token = OQLParser::F_CURRENT_DATE;
$this->token = OQLParser::F_DATE_FORMAT;
}
function yy_r1_44($yy_subpatterns)
{
$this->token = OQLParser::F_NOW;
$this->token = OQLParser::F_CURRENT_DATE;
}
function yy_r1_45($yy_subpatterns)
{
$this->token = OQLParser::F_TIME;
$this->token = OQLParser::F_NOW;
}
function yy_r1_46($yy_subpatterns)
{
$this->token = OQLParser::F_TO_DAYS;
$this->token = OQLParser::F_TIME;
}
function yy_r1_47($yy_subpatterns)
{
$this->token = OQLParser::F_FROM_DAYS;
$this->token = OQLParser::F_TO_DAYS;
}
function yy_r1_48($yy_subpatterns)
{
$this->token = OQLParser::F_YEAR;
$this->token = OQLParser::F_FROM_DAYS;
}
function yy_r1_49($yy_subpatterns)
{
$this->token = OQLParser::F_MONTH;
$this->token = OQLParser::F_YEAR;
}
function yy_r1_50($yy_subpatterns)
{
$this->token = OQLParser::F_DAY;
$this->token = OQLParser::F_MONTH;
}
function yy_r1_51($yy_subpatterns)
{
$this->token = OQLParser::F_HOUR;
$this->token = OQLParser::F_DAY;
}
function yy_r1_52($yy_subpatterns)
{
$this->token = OQLParser::F_MINUTE;
$this->token = OQLParser::F_HOUR;
}
function yy_r1_53($yy_subpatterns)
{
$this->token = OQLParser::F_SECOND;
$this->token = OQLParser::F_MINUTE;
}
function yy_r1_54($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_ADD;
$this->token = OQLParser::F_SECOND;
}
function yy_r1_55($yy_subpatterns)
{
$this->token = OQLParser::F_DATE_SUB;
$this->token = OQLParser::F_DATE_ADD;
}
function yy_r1_56($yy_subpatterns)
{
$this->token = OQLParser::F_ROUND;
$this->token = OQLParser::F_DATE_SUB;
}
function yy_r1_57($yy_subpatterns)
{
$this->token = OQLParser::F_FLOOR;
$this->token = OQLParser::F_ROUND;
}
function yy_r1_58($yy_subpatterns)
{
$this->token = OQLParser::F_INET_ATON;
$this->token = OQLParser::F_FLOOR;
}
function yy_r1_59($yy_subpatterns)
{
$this->token = OQLParser::F_INET_NTOA;
$this->token = OQLParser::F_INET_ATON;
}
function yy_r1_60($yy_subpatterns)
{
$this->token = OQLParser::BELOW;
$this->token = OQLParser::F_INET_NTOA;
}
function yy_r1_61($yy_subpatterns)
{
$this->token = OQLParser::BELOW_STRICT;
$this->token = OQLParser::BELOW;
}
function yy_r1_62($yy_subpatterns)
{
$this->token = OQLParser::NOT_BELOW;
$this->token = OQLParser::BELOW_STRICT;
}
function yy_r1_63($yy_subpatterns)
{
$this->token = OQLParser::NOT_BELOW_STRICT;
$this->token = OQLParser::NOT_BELOW;
}
function yy_r1_64($yy_subpatterns)
{
$this->token = OQLParser::ABOVE;
$this->token = OQLParser::NOT_BELOW_STRICT;
}
function yy_r1_65($yy_subpatterns)
{
$this->token = OQLParser::ABOVE_STRICT;
$this->token = OQLParser::ABOVE;
}
function yy_r1_66($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE;
$this->token = OQLParser::ABOVE_STRICT;
}
function yy_r1_67($yy_subpatterns)
{
$this->token = OQLParser::NOT_ABOVE_STRICT;
$this->token = OQLParser::NOT_ABOVE;
}
function yy_r1_68($yy_subpatterns)
{
$this->token = OQLParser::HEXVAL;
$this->token = OQLParser::NOT_ABOVE_STRICT;
}
function yy_r1_69($yy_subpatterns)
{
$this->token = OQLParser::NUMVAL;
$this->token = OQLParser::HEXVAL;
}
function yy_r1_70($yy_subpatterns)
{
$this->token = OQLParser::STRVAL;
$this->token = OQLParser::NUMVAL;
}
function yy_r1_71($yy_subpatterns)
{
$this->token = OQLParser::NAME;
$this->token = OQLParser::STRVAL;
}
function yy_r1_72($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
$this->token = OQLParser::NAME;
}
function yy_r1_73($yy_subpatterns)
{
$this->token = OQLParser::VARNAME;
}
function yy_r1_74($yy_subpatterns)
{
$this->token = OQLParser::DOT;
@@ -666,25 +672,25 @@ class OQLLexer extends OQLLexerRaw
function yylex()
{
try
{
return parent::yylex();
}
catch (Exception $e)
{
$sMessage = $e->getMessage();
if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
{
$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
{
$iLine = $aMatches[1];
$sUnexpected = $aMatches[2];
throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
}
}
// Default: forward the exception
throw $e;
try
{
return parent::yylex();
}
catch (Exception $e)
{
$sMessage = $e->getMessage();
if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
{
$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
{
$iLine = $aMatches[1];
$sUnexpected = $aMatches[2];
throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
}
}
// Default: forward the exception
throw $e;
}
}
}

View File

@@ -139,6 +139,7 @@ f_round = "ROUND"
f_floor = "FLOOR"
f_inet_aton = "INET_ATON"
f_inet_ntoa = "INET_NTOA"
matches = "MATCHES"
below = "BELOW"
below_strict = "BELOW STRICT"
not_below = "NOT BELOW"
@@ -273,6 +274,9 @@ in {
not_in {
$this->token = OQLParser::NOT_IN;
}
matches {
$this->token = OQLParser::MATCHES;
}
interval {
$this->token = OQLParser::INTERVAL;
}
@@ -419,25 +423,25 @@ class OQLLexer extends OQLLexerRaw
function yylex()
{
try
{
return parent::yylex();
}
catch (Exception $e)
{
$sMessage = $e->getMessage();
if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
{
$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
{
$iLine = $aMatches[1];
$sUnexpected = $aMatches[2];
throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
}
}
// Default: forward the exception
throw $e;
try
{
return parent::yylex();
}
catch (Exception $e)
{
$sMessage = $e->getMessage();
if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
{
$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
{
$iLine = $aMatches[1];
$sUnexpected = $aMatches[2];
throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
}
}
// Default: forward the exception
throw $e;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ Example:
%left TIMES DIVIDE MOD.
%right EXP NOT.
TODO : solve the 2 remaining shift-reduce conflicts (JOIN)
later : solve the 2 remaining shift-reduce conflicts (JOIN)
*/
@@ -108,7 +108,16 @@ expression_prio1(A) ::= expression_basic(X). { A = X; }
expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio2(A) ::= expression_prio1(X). { A = X; }
expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z).{
if (Y == 'MATCHES')
{
A = new MatchOqlExpression(X, Z);
}
else
{
A = new BinaryOqlExpression(X, Y, Z);
}
}
expression_prio3(A) ::= expression_prio2(X). { A = X; }
expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); }
@@ -180,18 +189,22 @@ str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));}
operator1(A) ::= num_operator1(X). {A=X;}
operator1(A) ::= bitwise_operator1(X). {A=X;}
operator2(A) ::= num_operator2(X). {A=X;}
operator2(A) ::= str_operator(X). {A=X;}
operator2(A) ::= REGEXP(X). {A=X;}
operator2(A) ::= EQ(X). {A=X;}
operator2(A) ::= NOT_EQ(X). {A=X;}
operator3(A) ::= LOG_AND(X). {A=X;}
operator3(A) ::= bitwise_operator3(X). {A=X;}
operator4(A) ::= LOG_OR(X). {A=X;}
operator4(A) ::= bitwise_operator4(X). {A=X;}
num_operator1(A) ::= MATH_DIV(X). {A=X;}
num_operator1(A) ::= MATH_MULT(X). {A=X;}
num_operator2(A) ::= MATH_PLUS(X). {A=X;}
num_operator2(A) ::= MATH_MINUS(X). {A=X;}
num_operator2(A) ::= GT(X). {A=X;}
@@ -201,10 +214,13 @@ num_operator2(A) ::= LE(X). {A=X;}
str_operator(A) ::= LIKE(X). {A=X;}
str_operator(A) ::= NOT_LIKE(X). {A=X;}
str_operator(A) ::= MATCHES(X). {A=X;}
bitwise_operator1(A) ::= BITWISE_LEFT_SHIFT(X). {A=X;}
bitwise_operator1(A) ::= BITWISE_RIGHT_SHIFT(X). {A=X;}
bitwise_operator3(A) ::= BITWISE_AND(X). {A=X;}
bitwise_operator4(A) ::= BITWISE_OR(X). {A=X;}
bitwise_operator4(A) ::= BITWISE_XOR(X). {A=X;}

View File

@@ -160,6 +160,26 @@ class BinaryOqlExpression extends BinaryExpression implements CheckableExpressio
}
}
class MatchOqlExpression extends MatchExpression implements CheckableExpression
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
{
$this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
$this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery);
// Only field MATCHES scalar is allowed
if (!$this->m_oLeftExpr instanceof FieldExpression)
{
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0));
}
// Only field MATCHES scalar is allowed
if (!$this->m_oRightExpr instanceof ScalarExpression)
{
throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0));
}
}
}
class ScalarOqlExpression extends ScalarExpression implements CheckableExpression
{
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)

View File

@@ -1 +1 @@
2015-08-31
2018-08-31

380
core/ormset.class.inc.php Normal file
View File

@@ -0,0 +1,380 @@
<?php
/**
* Copyright (c) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* Date: 24/08/2018
* Time: 14:35
*/
class ormSet
{
protected $sClass; // class of the field
protected $sAttCode; // attcode of the field
protected $aOriginalObjects = null;
/**
* Object from the original set, minus the removed objects
*/
protected $aPreserved = array();
/**
* New items
*/
protected $aAdded = array();
/**
* Removed items
*/
protected $aRemoved = array();
/**
* Modified items (mass edit)
*/
protected $aModified = array();
/**
* @var int Max number of tags in collection
*/
protected $iLimit;
/**
* __toString magical function overload.
*/
public function __toString()
{
$aValue = $this->GetValues();
if (!empty($aValue))
{
return implode(', ', $aValue);
}
else
{
return ' ';
}
}
/**
* ormSet constructor.
*
* @param string $sClass
* @param string $sAttCode
* @param int $iLimit
*
* @throws \Exception
*/
public function __construct($sClass, $sAttCode, $iLimit = 12)
{
$this->sAttCode = $sAttCode;
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if (!$oAttDef instanceof AttributeSet)
{
throw new Exception("ormSet: field {$sClass}:{$sAttCode} is not a set");
}
$this->sClass = $sClass;
$this->iLimit = $iLimit;
}
/**
* @return string
*/
public function GetClass()
{
return $this->sClass;
}
/**
* @return string
*/
public function GetAttCode()
{
return $this->sAttCode;
}
/**
*
* @param array $aItems
*
* @throws \CoreException
* @throws \CoreUnexpectedValue when a code is invalid
*/
public function SetValues($aItems)
{
if (!is_array($aItems))
{
throw new CoreUnexpectedValue("Wrong value {$aItems} for {$this->sClass}:{$this->sAttCode}");
}
$oValues = array();
$iCount = 0;
$bError = false;
foreach($aItems as $oItem)
{
$iCount++;
if (($this->iLimit != 0) && ($iCount > $this->iLimit))
{
$bError = true;
continue;
}
$oValues[] = $oItem;
}
$this->aPreserved = &$oValues;
$this->aRemoved = array();
$this->aAdded = array();
$this->aModified = array();
$this->aOriginalObjects = $oValues;
if ($bError)
{
throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}");
}
}
public function Count()
{
return count($this->aPreserved) + count($this->aAdded) - count($this->aRemoved);
}
/**
* @return array of codes
*/
public function GetValues()
{
$aValues = array_merge($this->aPreserved, $this->aAdded);
return $aValues;
}
/**
* @return array of tag labels indexed by code for only the added tags
*/
private function GetAdded()
{
return $this->aAdded;
}
/**
* @return array of tag labels indexed by code for only the removed tags
*/
private function GetRemoved()
{
return $this->aRemoved;
}
/** Get the delta with another ItemSet
*
* $aDelta['added] = array of tag codes for only the added tags
* $aDelta['removed'] = array of tag codes for only the removed tags
*
* @param \ormSet $oOtherSet
*
* @return array
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function GetDelta(ormSet $oOtherSet)
{
$oSet = new ormSet($this->sClass, $this->sAttCode);
// Set the initial value
$aOrigItems = $this->GetValues();
$oSet->SetValues($aOrigItems);
// now remove everything
foreach($aOrigItems as $oItem)
{
$oSet->Remove($oItem);
}
// now add the tags of the other ItemSet
foreach($oOtherSet->GetValues() as $oItem)
{
$oSet->Add($oItem);
}
$aDelta = array();
$aDelta['added'] = $oSet->GetAdded();
$aDelta['removed'] = $oSet->GetRemoved();
return $aDelta;
}
/**
* @return string[] list of codes for partial entries
*/
public function GetModified()
{
return $this->aModified;
}
/**
* Apply a delta to the current ItemSet
* $aDelta['added] = array of added items
* $aDelta['removed'] = array of removed items
*
* @param $aDelta
*
* @throws \CoreException
*/
public function ApplyDelta($aDelta)
{
if (isset($aDelta['removed']))
{
foreach($aDelta['removed'] as $oItem)
{
$this->Remove($oItem);
}
}
if (isset($aDelta['added']))
{
foreach($aDelta['added'] as $oItem)
{
$this->Add($oItem);
}
}
// Reset the object
$this->SetValues($this->GetValues());
}
/**
* @param string $oItem
*
* @throws \CoreException
*/
public function Add($oItem)
{
if ($this->Count() === $this->iLimit)
{
throw new CoreException("Maximum number of items ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}");
}
if ($this->IsItemInList($this->aPreserved, $oItem) || $this->IsItemInList($this->aAdded, $oItem))
{
// nothing to do, already existing tag
return;
}
// if removed and added again
if (($this->RemoveItemFromList($this->aRemoved, $oItem)) !== false)
{
// put it back into preserved
$this->aPreserved[] = $oItem;
// no need to add it to aModified : was already done when calling RemoveItem method
}
else
{
$this->aAdded[] = $oItem;
$this->aModified[] = $oItem;
}
}
/**
* @param $oItem
*/
public function Remove($oItem)
{
if ($this->IsItemInList($this->aRemoved, $oItem))
{
// nothing to do, already removed tag
return;
}
if ($this->RemoveItemFromList($this->aAdded, $oItem) !== false)
{
$this->aModified[] = $oItem;
return; // if present in added, can't be in preserved !
}
if ($this->RemoveItemFromList($this->aPreserved, $oItem) !== false)
{
$this->aModified[] = $oItem;
$this->aRemoved[] = $oItem;
}
}
private function IsItemInList($aItemList, $oItem)
{
return in_array($oItem, $aItemList);
}
/**
* @param \DBObject[] $aItemList
* @param $oItem
*
* @return bool|\DBObject false if not found, else the removed element
*/
private function RemoveItemFromList(&$aItemList, $oItem)
{
if (!($this->IsItemInList($aItemList, $oItem)))
{
return false;
}
foreach ($aItemList as $index => $value)
{
if ($value === $oItem)
{
unset($aItemList[$index]);
return $oItem;
}
}
return false;
}
/**
* Populates the added and removed arrays for bulk edit
*
* @param string[] $aItems
*
* @throws \CoreException
*/
public function GenerateDiffFromArray($aItems)
{
foreach($this->GetValues() as $oCurrentItem)
{
if (!in_array($oCurrentItem, $aItems))
{
$this->Remove($oCurrentItem);
}
}
foreach($aItems as $oNewItem)
{
$this->Add($oNewItem);
}
}
/**
* Compare Item Set
*
* @param \ormSet $other
*
* @return bool true if same tag set
*/
public function Equals(ormSet $other)
{
return implode(', ', $this->GetValue()) === implode(', ', $other->GetValue());
}
}

View File

@@ -0,0 +1,478 @@
<?php
/**
* Copyright (c) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* Created by PhpStorm.
* Date: 24/08/2018
* Time: 14:35
*/
final class ormTagSet extends ormSet
{
/**
* ormTagSet constructor.
*
* @param string $sClass
* @param string $sAttCode
* @param int $iLimit
*
* @throws \Exception
*/
public function __construct($sClass, $sAttCode, $iLimit = 12)
{
parent::__construct($sClass, $sAttCode, $iLimit);
}
/**
*
* @param array $aTagCodes
*
* @throws \CoreException
* @throws \CoreUnexpectedValue when a code is invalid
*/
public function SetValues($aTagCodes)
{
if (!is_array($aTagCodes))
{
throw new CoreUnexpectedValue("Wrong value {$aTagCodes} for {$this->sClass}:{$this->sAttCode}");
}
$oTags = array();
$iCount = 0;
$bError = false;
foreach($aTagCodes as $sTagCode)
{
$iCount++;
if (($this->iLimit != 0) && ($iCount > $this->iLimit))
{
$bError = true;
continue;
}
$oTag = $this->GetTagFromCode($sTagCode);
$oTags[$sTagCode] = $oTag;
}
$this->aPreserved = &$oTags;
$this->aRemoved = array();
$this->aAdded = array();
$this->aModified = array();
$this->aOriginalObjects = $oTags;
if ($bError)
{
throw new CoreException("Maximum number of tags ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}");
}
}
/**
* @return array of tag codes
*/
public function GetValues()
{
$aValues = array();
foreach($this->aPreserved as $sTagCode => $oTag)
{
$aValues[] = $sTagCode;
}
foreach($this->aAdded as $sTagCode => $oTag)
{
$aValues[] = $sTagCode;
}
sort($aValues);
return $aValues;
}
/**
* @return array of tag labels indexed by code
*/
public function GetLabels()
{
$aTags = array();
/** @var \TagSetFieldData $oTag */
foreach($this->aPreserved as $sTagCode => $oTag)
{
try
{
$aTags[$sTagCode] = $oTag->Get('label');
} catch (CoreException $e)
{
IssueLog::Error($e->getMessage());
}
}
foreach($this->aAdded as $sTagCode => $oTag)
{
try
{
$aTags[$sTagCode] = $oTag->Get('label');
} catch (CoreException $e)
{
IssueLog::Error($e->getMessage());
}
}
ksort($aTags);
return $aTags;
}
/**
* @return array of tags indexed by code
*/
public function GetTags()
{
$aTags = array();
foreach($this->aPreserved as $sTagCode => $oTag)
{
$aTags[$sTagCode] = $oTag;
}
foreach($this->aAdded as $sTagCode => $oTag)
{
$aTags[$sTagCode] = $oTag;
}
ksort($aTags);
return $aTags;
}
/**
* @return array of tag labels indexed by code for only the added tags
*/
private function GetAddedCodes()
{
$aTags = array();
foreach($this->aAdded as $sTagCode => $oTag)
{
$aTags[] = $sTagCode;
}
ksort($aTags);
return $aTags;
}
/**
* @return array of tag labels indexed by code for only the removed tags
*/
private function GetRemovedCodes()
{
$aTags = array();
foreach($this->aRemoved as $sTagCode => $oTag)
{
$aTags[] = $sTagCode;
}
ksort($aTags);
return $aTags;
}
/**
* @return array of tag labels indexed by code for only the added tags
*/
private function GetAddedTags()
{
$aTags = array();
foreach($this->aAdded as $sTagCode => $oTag)
{
$aTags[$sTagCode] = $oTag;
}
ksort($aTags);
return $aTags;
}
/**
* @return array of tag labels indexed by code for only the removed tags
*/
private function GetRemovedTags()
{
$aTags = array();
foreach($this->aRemoved as $sTagCode => $oTag)
{
$aTags[$sTagCode] = $oTag;
}
ksort($aTags);
return $aTags;
}
/** Get the delta with another TagSet
*
* $aDelta['added] = array of tag codes for only the added tags
* $aDelta['removed'] = array of tag codes for only the removed tags
*
* @param \ormTagSet $oOtherTagSet
*
* @return array
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function GetDelta(ormSet $oOtherTagSet)
{
$oTag = new ormTagSet($this->sClass, $this->sAttCode);
// Set the initial value
$aOrigTagCodes = $this->GetValues();
$oTag->SetValues($aOrigTagCodes);
// now remove everything
foreach($aOrigTagCodes as $sTagCode)
{
$oTag->Remove($sTagCode);
}
// now add the tags of the other TagSet
foreach($oOtherTagSet->GetValues() as $sTagCode)
{
$oTag->Add($sTagCode);
}
$aDelta = array();
$aDelta['added'] = $oTag->GetAddedCodes();
$aDelta['removed'] = $oTag->GetRemovedCodes();
return $aDelta;
}
/** Get the delta with another TagSet
*
* $aDelta['added] = array of tag labels indexed by code for only the added tags
* $aDelta['removed'] = array of tag labels indexed by code for only the removed tags
*
* @param \ormTagSet $oOtherTagSet
*
* @return array
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function GetDeltaTags(ormTagSet $oOtherTagSet)
{
$oTag = new ormTagSet($this->sClass, $this->sAttCode);
// Set the initial value
$aOrigTagCodes = $this->GetValues();
$oTag->SetValues($aOrigTagCodes);
// now remove everything
foreach($aOrigTagCodes as $sTagCode)
{
$oTag->Remove($sTagCode);
}
// now add the tags of the other TagSet
foreach($oOtherTagSet->GetValues() as $sTagCode)
{
$oTag->Add($sTagCode);
}
$aDelta = array();
$aDelta['added'] = $oTag->GetAddedTags();
$aDelta['removed'] = $oTag->GetRemovedTags();
return $aDelta;
}
/**
* @return string[] list of codes for partial entries
*/
public function GetModified()
{
$aModifiedTagCodes = array_keys($this->aModified);
sort($aModifiedTagCodes);
return $aModifiedTagCodes;
}
/**
* Check whether a tag code is valid or not for this TagSet
*
* @param string $sTagCode
*
* @return bool
*/
public function IsValidTag($sTagCode)
{
try
{
$this->GetTagFromCode($sTagCode);
return true;
} catch (Exception $e)
{
return false;
}
}
/**
* @param string $sTagCode
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public function Add($sTagCode)
{
if ($this->Count() === $this->iLimit)
{
throw new CoreException("Maximum number of tags ({$this->iLimit}) reached for {$this->sClass}:{$this->sAttCode}");
}
if ($this->IsTagInList($this->aPreserved, $sTagCode) || $this->IsTagInList($this->aAdded, $sTagCode))
{
// nothing to do, already existing tag
return;
}
// if removed then added again
if (($oTag = $this->RemoveTagFromList($this->aRemoved, $sTagCode)) !== false)
{
// put it back into preserved
$this->aPreserved[$sTagCode] = $oTag;
// no need to add it to aModified : was already done when calling Remove method
}
else
{
$oTag = $this->GetTagFromCode($sTagCode);
$this->aAdded[$sTagCode] = $oTag;
$this->aModified[$sTagCode] = $oTag;
}
}
/**
* @param $sTagCode
*/
public function Remove($sTagCode)
{
if ($this->IsTagInList($this->aRemoved, $sTagCode))
{
// nothing to do, already removed tag
return;
}
$oTag = $this->RemoveTagFromList($this->aAdded, $sTagCode);
if ($oTag !== false)
{
$this->aModified[$sTagCode] = $oTag;
return; // if present in added, can't be in preserved !
}
$oTag = $this->RemoveTagFromList($this->aPreserved, $sTagCode);
if ($oTag !== false)
{
$this->aModified[$sTagCode] = $oTag;
$this->aRemoved[$sTagCode] = $oTag;
}
}
private function IsTagInList($aTagList, $sTagCode)
{
return isset($aTagList[$sTagCode]);
}
/**
* @param \DBObject[] $aTagList
* @param string $sTagCode
*
* @return bool|\DBObject false if not found, else the removed element
*/
private function RemoveTagFromList(&$aTagList, $sTagCode)
{
if (!($this->IsTagInList($aTagList, $sTagCode)))
{
return false;
}
$oTag = $aTagList[$sTagCode];
unset($aTagList[$sTagCode]);
return $oTag;
}
/**
* @param $sTagCode
*
* @return DBObject tag
* @throws \CoreUnexpectedValue
* @throws \CoreException
*/
private function GetTagFromCode($sTagCode)
{
$aAllowedTags = $this->GetAllowedTags();
foreach($aAllowedTags as $oAllowedTag)
{
if ($oAllowedTag->Get('code') === $sTagCode)
{
return $oAllowedTag;
}
}
throw new CoreUnexpectedValue("{$sTagCode} is not defined as a valid tag for {$this->sClass}:{$this->sAttCode}");
}
/**
* @param $sTagCode
*
* @return DBObject tag
* @throws \CoreUnexpectedValue
* @throws \CoreException
*/
public function GetTagFromLabel($sTagLabel)
{
$aAllowedTags = $this->GetAllowedTags();
foreach($aAllowedTags as $oAllowedTag)
{
if ($oAllowedTag->Get('label') === $sTagLabel)
{
return $oAllowedTag->Get('code');
}
}
throw new CoreUnexpectedValue("{$sTagLabel} is not defined as a valid tag for {$this->sClass}:{$this->sAttCode}");
}
/**
* @return \TagSetFieldData[]
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
private function GetAllowedTags()
{
return TagSetFieldData::GetAllowedValues($this->sClass, $this->sAttCode);
}
/**
* Compare Tag Set
*
* @param \ormTagSet $other
*
* @return bool true if same tag set
*/
public function Equals(ormSet $other)
{
if (!($other instanceof ormTagSet))
{
return false;
}
if ($this->GetTagDataClass() !== $other->GetTagDataClass())
{
return false;
}
return implode(' ', $this->GetValues()) === implode(' ', $other->GetValues());
}
public function GetTagDataClass()
{
return TagSetFieldData::GetTagDataClassName($this->sClass, $this->sAttCode);
}
}

View File

@@ -1,81 +0,0 @@
<?php
// Copyright (C) 2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* <p>Stores data for {@link AttributeTagSet} fields
*
* <p>We will have an implementation for each class/field to be able to customize rights (generated in \MFCompiler::CompileClass).<br>
* Only this abstract class will exists in the DB : the implementations won't had any new field.
*
* @since 2.6 N°931 tag fields
*/
abstract class TagSetFieldData extends cmdbAbstractObject
{
public static function Init()
{
$aParams = array
(
'category' => 'bizmodel',
'key_type' => 'autoincrement',
'name_attcode' => array('tag_label'),
'state_attcode' => '',
'reconc_keys' => array('tag_code'),
'db_table' => 'priv_tagfielddata',
'db_key_field' => 'id',
'db_finalclass_field' => 'finalclass',
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("tag_code", array(
"allowed_values" => null,
"sql" => 'tag_code',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeString("tag_label", array(
"allowed_values" => null,
"sql" => 'tag_label',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeString("tag_description", array(
"allowed_values" => null,
"sql" => 'tag_description',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeBoolean("is_default", array(
"allowed_values" => null,
"sql" => "is_default",
"default_value" => false,
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'is_default'));
MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default'));
MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default'));
}
}

View File

@@ -0,0 +1,385 @@
<?php
// Copyright (C) 2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* <p>Stores data for {@link AttributeTagSet} fields
*
* <p>We will have an implementation for each class/field to be able to customize rights (generated in
* \MFCompiler::CompileClass).<br> Only this abstract class will exists in the DB : the implementations won't had any
* new field.
*
* @since 2.6 N°931 tag fields
*/
abstract class TagSetFieldData extends cmdbAbstractObject
{
private static $m_aAllowedValues = array();
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
'category' => 'bizmodel',
'key_type' => 'autoincrement',
'name_attcode' => array('label'),
'state_attcode' => '',
'reconc_keys' => array('code'),
'db_table' => 'priv_tagfielddata',
'db_key_field' => 'id',
'db_finalclass_field' => 'finalclass',
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("code", array(
"allowed_values" => null,
"sql" => 'code',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array(),
"validation_pattern" => '^[a-zA-Z0-9]{3,}$',
)));
MetaModel::Init_AddAttribute(new AttributeString("label", array(
"allowed_values" => null,
"sql" => 'label',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeHTML("description", array(
"allowed_values" => null,
"sql" => 'description',
"default_value" => '',
"is_null_allowed" => true,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeString("obj_class", array(
"allowed_values" => null,
"sql" => 'obj_class',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_AddAttribute(new AttributeString("obj_attcode", array(
"allowed_values" => null,
"sql" => 'obj_attcode',
"default_value" => '',
"is_null_allowed" => false,
"depends_on" => array()
)));
MetaModel::Init_SetZListItems('details', array('code', 'label', 'description'));
MetaModel::Init_SetZListItems('standard_search', array('code', 'label', 'description'));
MetaModel::Init_SetZListItems('list', array('code', 'label', 'description'));
}
public function ComputeValues()
{
$sClassName = get_class($this);
$aRes = static::ExtractTagFieldName($sClassName);
$this->_Set('obj_class', $aRes['obj_class']);
$this->_Set('obj_attcode', $aRes['obj_attcode']);
}
public static function GetTagDataClassName($sClass, $sAttCode)
{
$sTagSuffix = $sClass.'__'.$sAttCode;
return 'TagSetFieldDataFor_'.$sTagSuffix;
}
/**
* Extract Tag class and attcode from the TagFieldData class name
*
* @param $sClassName
*
* @return string[]
* @throws \CoreException
*/
public static function ExtractTagFieldName($sClassName)
{
$aRes = array();
// Extract class and attcode from class name using pattern TagSetFieldDataFor_<class>_<attcode>>;
if (preg_match('@^TagSetFieldDataFor_(?<class>\w+)__(?<attcode>\w+)$@', $sClassName, $aMatches))
{
$aRes['obj_class'] = $aMatches['class'];
$aRes['obj_attcode'] = $aMatches['attcode'];
}
else
{
throw new CoreException("Bad Class name format: $sClassName");
}
return $aRes;
}
/**
* @param \DeletionPlan $oDeletionPlan
*
* @throws \CoreException
*/
public function DoCheckToDelete(&$oDeletionPlan)
{
parent::DoCheckToDelete($oDeletionPlan);
$sTagCode = $this->Get('code');
if ($this->IsCodeUsed($sTagCode))
{
$this->m_aDeleteIssues[] = Dict::S('Core:TagSetFieldData:ErrorDeleteUsedTag');
}
// Clear cache
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode);
unset(self::$m_aAllowedValues[$sTagDataClass]);
}
/**
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \Exception
*/
public function DoCheckToWrite()
{
$this->ComputeValues();
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$iMaxLen = 20;
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeTagSet)
{
$iMaxLen = $oAttDef->GetTagCodeMaxLength();
}
$sTagCode = $this->Get('code');
// Check code syntax
if (!preg_match("@^[a-zA-Z0-9]{3,$iMaxLen}$@", $sTagCode))
{
$this->m_aCheckIssues[] = Dict::Format('Core:TagSetFieldData:ErrorTagCodeSyntax', $iMaxLen);
}
// Check that the code is not a MySQL stop word
$sSQL = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD";
try
{
$aResults = CMDBSource::QueryToArray($sSQL);
} catch (MySQLException $e)
{
IssueLog::Warning($e->getMessage());
$aResults = array();
}
foreach($aResults as $aResult)
{
if ($aResult['value'] == $sTagCode)
{
$this->m_aCheckIssues[] = Dict::S('Core:TagSetFieldData:ErrorTagCodeReservedWord');
break;
}
}
$sTagLabel = $this->Get('label');
$sSepItem = MetaModel::GetConfig()->Get('tag_set_item_separator');
if (empty($sTagLabel) || (strpos($sTagLabel, $sSepItem) !== false))
{
// Label must not contain | character
$this->m_aCheckIssues[] = Dict::Format('Core:TagSetFieldData:ErrorTagLabelSyntax', $sSepItem);
}
// Check that code and labels are uniques
$id = $this->GetKey();
$sClassName = get_class($this);
if (empty($id))
{
$oSearch = DBSearch::FromOQL("SELECT $sClassName WHERE (code = :tag_code OR label = :tag_label)");
}
else
{
$oSearch = DBSearch::FromOQL("SELECT $sClassName WHERE id != :id AND (code = :tag_code OR label = :tag_label)");
}
$aArgs = array('id' => $id, 'tag_code' => $sTagCode, 'tag_label' => $sTagLabel);
$oSet = new DBObjectSet($oSearch, array(), $aArgs);
if ($oSet->CountExceeds(0))
{
$this->m_aCheckIssues[] = Dict::S('Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel');
}
// Clear cache
$sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode);
unset(self::$m_aAllowedValues[$sTagDataClass]);
parent::DoCheckToWrite();
}
/**
* @throws \CoreException
*/
public function OnUpdate()
{
parent::OnUpdate();
$aChanges = $this->ListChanges();
if (array_key_exists('code', $aChanges))
{
$sTagCode = $this->GetOriginal('code');
if ($this->IsCodeUsed($sTagCode))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorCodeUpdateNotAllowed'));
}
}
if (array_key_exists('obj_class', $aChanges))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorClassUpdateNotAllowed'));
}
if (array_key_exists('obj_attcode', $aChanges))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed'));
}
}
private function IsCodeUsed($sTagCode)
{
try
{
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$oSearch = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oSet = new DBObjectSet($oSearch);
if ($oSet->CountExceeds(0))
{
return true;
}
}
catch (Exception $e)
{
IssueLog::Warning($e->getMessage());
}
return false;
}
/**
* Display Tag Usage
*
* @param \WebPage $oPage
* @param bool $bEditMode
*
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, $bEditMode);
if (!$bEditMode)
{
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$sTagCode = $this->Get('code');
$oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oSet = new DBObjectSet($oFilter);
$iCount = $oSet->Count();
$oPage->SetCurrentTab(Dict::Format('Core:TagSetFieldData:WhereIsThisTagTab', $iCount));
if ($iCount === 0)
{
$sNoEntries = Dict::S('Core:TagSetFieldData:NoEntryFound');
$oPage->add("<p>$sNoEntries</p>");
}
else
{
$aClassLabels = array();
foreach(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sCurrentClass)
{
$aClassLabels[$sCurrentClass] = MetaModel::GetName($sCurrentClass);
}
foreach($aClassLabels as $sClass => $sClassLabel)
{
$oFilter = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oSet = new DBObjectSet($oFilter);
if ($oSet->CountExceeds(0))
{
$oPage->add("<h2>$sClassLabel</h2>");
$oResultBlock = new DisplayBlock($oFilter, 'list', false);
$oResultBlock->Display($oPage, 1);
}
}
}
}
}
public static function GetClassName($sClass)
{
if ($sClass == 'TagSetFieldData')
{
$aWords = preg_split('/(?=[A-Z]+)/', $sClass);
return trim(implode(' ', $aWords));
}
try
{
$aTagFieldInfo = self::ExtractTagFieldName($sClass);
} catch (CoreException $e)
{
return $sClass;
}
$sClassDesc = MetaModel::GetName($aTagFieldInfo['obj_class']);
$sAttDesc = MetaModel::GetAttributeDef($aTagFieldInfo['obj_class'], $aTagFieldInfo['obj_attcode'])->GetLabel();
if (Dict::Exists("Class:$sClass"))
{
$sName = Dict::Format("Class:$sClass", $sClassDesc, $sAttDesc);
}
else
{
$sName = Dict::Format('Class:TagSetFieldData', $sClassDesc, $sAttDesc);
}
return $sName;
}
/**
* @param $sClass
* @param $sAttCode
*
* @return \TagSetFieldData[]
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public static function GetAllowedValues($sClass, $sAttCode)
{
$sClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
$sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode);
if (!isset(self::$m_aAllowedValues[$sTagDataClass]))
{
$oSearch = new DBObjectSearch($sTagDataClass);
$oSearch->AddCondition('obj_class', $sClass);
$oSearch->AddCondition('obj_attcode', $sAttCode);
$oSet = new DBObjectSet($oSearch);
self::$m_aAllowedValues[$sTagDataClass] = $oSet->ToArray();
}
return self::$m_aAllowedValues[$sTagDataClass];
}
}

View File

@@ -289,7 +289,7 @@ abstract class TriggerOnStateChange extends TriggerOnObject
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("state", array("allowed_values" => null, "sql" => "state", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeClassState("state", array("class_field" => 'target_class', "allowed_values" => null, "sql" => "state", "default_value" => null, "is_null_allowed" => false, "depends_on" => array('target_class'))));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'state', 'action_list')); // Attributes to be displayed for the complete details
@@ -402,6 +402,40 @@ class TriggerOnObjectCreate extends TriggerOnObject
}
}
/**
* Class TriggerOnObjectCreate
*/
class TriggerOnObjectDelete extends TriggerOnObject
{
/**
* @throws \CoreException
*/
public static function Init()
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb,application",
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array('description'),
"db_table" => "priv_trigger_onobjdelete",
"db_key_field" => "id",
"db_finalclass_field" => "",
"display_template" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
/**
* Class TriggerOnObjectCreate
*/
@@ -427,7 +461,7 @@ class TriggerOnObjectUpdate extends TriggerOnObject
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeObjectAttCodeSet('target_attcodes', array("allowed_values" => null, "class" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "depends_on" => array('target_class'))));
MetaModel::Init_AddAttribute(new AttributeClassAttCodeSet('target_attcodes', array("allowed_values" => null, "class_field" => "target_class", "sql" => "target_attcodes", "default_value" => null, "is_null_allowed" => true, "max_items" => 20, "min_items" => 0, "attribute_definition_list" => null, "depends_on" => array('target_class'))));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'target_class', 'filter', 'target_attcodes', 'action_list')); // Attributes to be displayed for the complete details
@@ -444,11 +478,13 @@ class TriggerOnObjectUpdate extends TriggerOnObject
}
// Check the attribute
$aAttCodes = $this->Get('target_attcodes');
$oAttCodeSet = $this->Get('target_attcodes');
$aAttCodes = $oAttCodeSet->GetValues();
if (empty($aAttCodes))
{
return true;
}
foreach($aAttCodes as $sAttCode)
{
if (array_key_exists($sAttCode, $aChanges))
@@ -458,6 +494,39 @@ class TriggerOnObjectUpdate extends TriggerOnObject
}
return false;
}
public function ComputeValues()
{
parent::ComputeValues();
// Remove unwanted attribute codes
$aChanges = $this->ListChanges();
if (isset($aChanges['target_attcodes']))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), 'target_attcodes');
$aArgs = array('this' => $this);
$aAllowedValues = $oAttDef->GetAllowedValues($aArgs);
/** @var \ormSet $oValue */
$oValue = $this->Get('target_attcodes');
$aValues = $oValue->GetValues();
$bChanged = false;
foreach($aValues as $key => $sValue)
{
if (!isset($aAllowedValues[$sValue]))
{
unset($aValues[$key]);
$bChanged = true;
}
}
if ($bChanged)
{
$oValue->SetValues($aValues);
$this->Set('target_attcodes', $oValue);
}
}
}
}
/**

View File

@@ -26,13 +26,12 @@ $frame-background-color: $gray-extra-light;
$text-color: #000;
$box-radius: 0px;
$box-shadow-regular: 0 1px 1px rgba(0, 0, 0, 0.15);
// - Boxes
//$search-criteria-box-color: #2D2D2D;
//$search-criteria-box-bg-color: #f0f3f5;
//$search-criteria-box-border-color: #3f7294;
//$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
//$search-criteria-box-radius: 1px;
//
$hyperlink-color: $complement-color;
$hyperlink-text-decoration: none;
////////////
// Search //
$search-criteria-box-color: #2D2D2D;
$search-criteria-box-picto-color: #E87C1E;
$search-criteria-box-bg-color: #EEEEEE;
@@ -50,4 +49,4 @@ $search-button-box-bg-color: $white;
$search-button-box-bg-hover-color: $gray-extra-light;
// Beware the version number MUST be enclosed with quotes otherwise v2.3.0 becomes v2 0.3 .0
$version: "v2.5.0-beta";
$version: "v2.6.0-dev";

View File

@@ -92,21 +92,21 @@ table.listResults td .view-image img {
margin-left: auto;
margin-right: auto;
}
table.listResults > tbody > tr.selected > * {
background-color: #f5c093;
}
table.listResults > tbody > tr > * {
transition: background-color 200ms ease-in-out;
transition: background-color 400ms linear;
}
table.listResults > tbody > tr:hover > * {
cursor: pointer;
}
table.listResults > tbody > tr.selected:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: #f1a564;
background-color: #f3b37b;
color: #000;
}
table.listResults > tbody > tr.selected:hover a {
background-color: transparent;
table.listResults > tbody > tr:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: #fdf5d0;
color: #000;
}
.edit-image .view-image {
display: inline-block;
@@ -195,7 +195,6 @@ tr.green td, .wizContainer tr.green td {
background-color: #b3e5b4;
}
tr td.hover, tr.even td.hover, .hover a, .hover a:visited, .hover a:hover, .wizContainer tr.even td.hover, .wizContainer tr td.hover {
background-color: #fdf5d0;
color: #000;
}
th {
@@ -343,10 +342,10 @@ a.small_action {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
background: #ea7d1e url(../images/actions_left.png?v=v2.5.0-beta) no-repeat left;
background: #ea7d1e url(../images/actions_left.png?v=v2.6.0-dev) no-repeat left;
}
.actions_details span {
background: url(../images/actions_right.png?v=v2.5.0-beta) no-repeat right;
background: url(../images/actions_right.png?v=v2.6.0-dev) no-repeat right;
color: #fff;
font-weight: bold;
padding-top: 2px;
@@ -520,7 +519,7 @@ div.actions_menu > ul {
nowidth: 70px;
padding-left: 5px;
/* Nasty work-around for IE... en attendant mieux */
background: #ea7d1e url(../images/actions_left.png?v=v2.5.0-beta) no-repeat top left;
background: #ea7d1e url(../images/actions_left.png?v=v2.6.0-dev) no-repeat top left;
cursor: pointer;
margin: 0;
}
@@ -532,7 +531,7 @@ div.actions_menu > ul > li {
height: 17px;
padding-right: 16px;
padding-left: 4px;
background: url(../images/actions_right.png?v=v2.5.0-beta) no-repeat top right transparent;
background: url(../images/actions_right.png?v=v2.6.0-dev) no-repeat top right transparent;
font-weight: bold;
color: #fff;
vertical-align: middle;
@@ -675,7 +674,7 @@ td a.dp-choose-date, a.dp-choose-date, td a.dp-choose-date:hover, a.dp-choose-da
display: block;
text-indent: -2000px;
overflow: hidden;
background: url(../images/calendar.png?v=v2.5.0-beta) no-repeat;
background: url(../images/calendar.png?v=v2.6.0-dev) no-repeat;
}
td a.dp-choose-date.dp-disabled, a.dp-choose-date.dp-disabled {
background-position: 0 -20px;
@@ -887,6 +886,9 @@ input.dp-applied {
left: 0px;
margin-top: -1px;
}
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_more_criterion .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_button .sfc_form_group .sfc_fg_buttons, .search_form_handler .sf_criterion_area .search_form_criteria .sfm_content .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content .sfc_fg_buttons, .search_form_handler .sf_criterion_area .sf_button .sfm_content .sfc_fg_buttons {
white-space: nowrap;
}
.search_form_handler .sf_criterion_area .search_form_criteria {
/* Non editable criteria */
/* Draft criteria (modifications not applied) */
@@ -1130,6 +1132,15 @@ input.dp-applied {
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_enum .sfc_form_group .sfc_fg_operator_in > label .sfc_op_content {
width: 100%;
}
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_tag_set .sfc_form_group .sfc_fg_operator_in > label {
display: inline-block;
width: 100%;
line-height: initial;
white-space: nowrap;
}
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_tag_set .sfc_form_group .sfc_fg_operator_in > label .sfc_op_content {
width: 100%;
}
.search_form_handler .sf_criterion_area .search_form_criteria.search_form_criteria_numeric .sfc_fg_operators .sfc_fg_operator.sfc_fg_operator_between .sfc_op_content_from_outer {
display: inline;
}
@@ -1316,19 +1327,19 @@ input.dp-applied {
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.odd td.truncated, table.listResults tr td.truncated, .wizContainer table.listResults tr.odd td.truncated, .wizContainer table.listResults tr td.truncated {
background: url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x;
background: url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.even td.truncated, .wizContainer table.listResults tr.even td.truncated {
background: #f9f9f1 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x;
background: #f9f9f1 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.even td.hover.truncated, .wizContainer table.listResults tr.even td.hover.truncated {
background: #fdf5d0 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x;
background: #fdf5d0 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x;
}
/* Beware: IE6 does not support multiple selector with multiple classes, only the last class is used */
table.listResults tr.odd td.hover.truncated, table.listResults tr td.hover.truncated, .wizContainer table.listResults tr.odd td.hover.truncated, .wizContainer table.listResults tr td.hover.truncated {
background: #fdf5d0 url(../images/truncated.png?v=v2.5.0-beta) bottom repeat-x;
background: #fdf5d0 url(../images/truncated.png?v=v2.6.0-dev) bottom repeat-x;
}
table.listResults.truncated {
border-bottom: 0;
@@ -1436,7 +1447,7 @@ div#logo {
div#logo div {
height: 88px;
width: 244px;
background: url(../images/itop-logo-2.png?v=v2.5.0-beta) left no-repeat;
background: url(../images/itop-logo-2.png?v=v2.6.0-dev) left no-repeat;
}
#left-pane .ui-layout-north {
overflow: hidden;
@@ -1528,7 +1539,7 @@ div#logo div {
}
#global-search-image {
vertical-align: middle;
background: url(../images/search.png?v=v2.5.0-beta) center center no-repeat;
background: url(../images/search.png?v=v2.6.0-dev) center center no-repeat;
display: inline-block;
width: 28px;
height: 30px;
@@ -1557,7 +1568,7 @@ span.ui-icon {
margin: 0 2px;
}
.ui-layout-button-pin-down {
background: url(../images/splitter-bkg.png?v=v2.5.0-beta) transparent;
background: url(../images/splitter-bkg.png?v=v2.6.0-dev) transparent;
width: 16px;
background-position: -144px -144px;
}
@@ -1933,6 +1944,22 @@ fieldset .details > .field_container {
.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_extkey > .field_input_btn > img {
max-width: 20px;
}
.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control {
width: 100%;
}
.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-dropdown, .field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input, .field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input input {
font-size: 12px;
}
.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input {
padding: 2px 2px 0px 2px;
/* padding-bottom = padding-top - item margin-bottom */
border: 1px solid #ababab;
border-radius: 0;
}
.field_container > div > div.field_value .attribute-edit .field_input_zone.field_input_set .selectize-control .selectize-input .attribute-set-item.partial-code {
color: rgba(34, 34, 34, 0.6);
background-color: #eaeaea;
}
.one-col-details .details .field_container.field_small {
/* On a single column, field labels can take more width but they are limited so it doesn't feel weird when all labels are short */
}
@@ -2057,7 +2084,7 @@ img.prev, img.first, img.next, img.last {
}
div.actions_button {
float: right;
background: #ea7d1e url("../images/actions_left.png?v=v2.5.0-beta") no-repeat scroll left top;
background: #ea7d1e url("../images/actions_left.png?v=v2.6.0-dev") no-repeat scroll left top;
padding-left: 5px;
margin-top: 0;
margin-right: 10px;
@@ -2065,7 +2092,7 @@ div.actions_button {
vertical-align: middle;
}
div.actions_button a, .actions_button a:hover, .actions_button a:visited {
background: #ea7d1e url(../images/actions_bkg.png?v=v2.5.0-beta) no-repeat scroll right top;
background: #ea7d1e url(../images/actions_bkg.png?v=v2.6.0-dev) no-repeat scroll right top;
color: #fff;
padding-right: 8px;
cursor: pointer;
@@ -2089,10 +2116,10 @@ select#org_id {
cursor: not-allowed;
}
.dragHover {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.5.0-beta);
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.6.0-dev);
}
.edit_mode .dashlet {
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.5.0-beta);
background: url(./ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png?v=v2.6.0-dev);
padding: 5px;
margin: 0;
position: relative;
@@ -2126,7 +2153,7 @@ table.prop_table {
top: 0;
right: 0;
z-index: 10;
background: transparent url(../images/delete.png?v=v2.5.0-beta) no-repeat center;
background: transparent url(../images/delete.png?v=v2.6.0-dev) no-repeat center;
}
td.prop_value {
text-align: left;
@@ -2328,17 +2355,17 @@ a.summary, a.summary:hover {
}
.message_info {
border: 1px solid #993;
background: url(../images/info-mini.png?v=v2.5.0-beta) 1em 1em no-repeat #ffc;
background: url(../images/info-mini.png?v=v2.6.0-dev) 1em 1em no-repeat #ffc;
padding-left: 3em;
}
.message_ok {
border: 1px solid #393;
background: url(../images/ok.png?v=v2.5.0-beta) 1em 1em no-repeat #cfc;
background: url(../images/ok.png?v=v2.6.0-dev) 1em 1em no-repeat #cfc;
padding-left: 3em;
}
.message_error {
border: 1px solid #933;
background: url(../images/error.png?v=v2.5.0-beta) 1em 1em no-repeat #fcc;
background: url(../images/error.png?v=v2.6.0-dev) 1em 1em no-repeat #fcc;
padding-left: 3em;
}
.fg-menu a img {
@@ -2469,18 +2496,18 @@ div.explain-printable {
}
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter span {
padding-left: 20px;
background: url(../images/eye-open-555.png?v=v2.5.0-beta) 2px center no-repeat;
background: url(../images/eye-open-555.png?v=v2.6.0-dev) 2px center no-repeat;
}
#hiddeable_chapters .ui-tabs .ui-tabs-nav li.hideable-chapter.strikethrough span {
text-decoration: line-through;
background: url(../images/eye-closed-555.png?v=v2.5.0-beta) 2px center no-repeat;
background: url(../images/eye-closed-555.png?v=v2.6.0-dev) 2px center no-repeat;
}
.printable-version legend {
padding-left: 26px;
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.5.0-beta) 8px center no-repeat;
background: #1c94c4 url(../images/eye-open-fff.png?v=v2.6.0-dev) 8px center no-repeat;
}
.printable-version .strikethrough legend {
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.5.0-beta) 8px center no-repeat;
background: #1c94c4 url(../images/eye-closed-fff.png?v=v2.6.0-dev) 8px center no-repeat;
}
.printable-version fieldset.strikethrough span {
display: none;
@@ -2631,7 +2658,7 @@ span.search-button, span.refresh-button {
#itop-breadcrumb .breadcrumb-item a::after {
content: '';
position: absolute;
background-image: url(../images/breadcrumb-separator.png?v=v2.5.0-beta);
background-image: url(../images/breadcrumb-separator.png?v=v2.6.0-dev);
background-repeat: no-repeat;
width: 8px;
height: 16px;
@@ -2865,3 +2892,46 @@ table.listResults .originColor {
.menu-icon-select > .ui-menu-item {
padding: 0.3em 3%;
}
.attribute.attribute-set .attribute-set-item::after {
content: ",";
margin-right: 0.5em;
}
.attribute.attribute-set .attribute-set-item:last-of-type::after {
content: "";
margin-right: 0;
}
.attribute-edit .attribute-set .attribute-set-item, .attribute-tag-set.attribute-set .attribute-set-item, .selectize-control > .selectize-input > .item[data-value], .selectize-control.multi > .selectize-input > .item[data-value], .selectize-control > .selectize-input > .item[data-value].active, .selectize-control.multi > .selectize-input > .item[data-value].active {
display: inline-block;
margin-right: 3px;
margin-bottom: 3px;
padding: 4px 6px;
max-width: 120px;
background: #fdfdfd none;
border-radius: 2px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px #f1f1f1;
color: #222;
text-shadow: none;
vertical-align: middle;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.attribute-edit .attribute-set .attribute-set-item::after, .attribute-tag-set.attribute-set .attribute-set-item::after, .selectize-control > .selectize-input > .item[data-value]::after, .selectize-control.multi > .selectize-input > .item[data-value]::after, .selectize-control > .selectize-input > .item[data-value].active::after, .selectize-control.multi > .selectize-input > .item[data-value].active::after {
content: "";
margin-right: 0;
}
.selectize-control > .selectize-input.has-items:after, .selectize-control.multi > .selectize-input.has-items:after {
content: "+";
display: inline-block;
font-size: 17px;
font-weight: bold;
color: #222;
}
.selectize-control > .selectize-input > .item[data-value], .selectize-control.multi > .selectize-input > .item[data-value] {
padding-right: 1.5em !important;
border: 1px solid #ddd;
}
.selectize-control > .selectize-input > .item[data-value] > .remove, .selectize-control.multi > .selectize-input > .item[data-value] > .remove {
padding-top: 0.3em;
border: none;
}

View File

@@ -117,11 +117,10 @@ table.listResults td .view-image {
}
table.listResults > tbody > tr.selected > * {
background-color: lighten($combodo-orange, 25%);
}
table.listResults > tbody > tr > * {
transition: background-color 200ms ease-in-out;
transition: background-color 400ms linear;
}
table.listResults > tbody > tr:hover > * {
@@ -130,10 +129,14 @@ table.listResults > tbody > tr:hover > * {
table.listResults > tbody > tr.selected:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: lighten($combodo-orange, 15%);
background-color: lighten($combodo-orange, 20%);
color: $text-color;
}
table.listResults > tbody > tr.selected:hover a {
background-color: transparent;
table.listResults > tbody > tr:hover > * {
/* hover on lines is currently done toggling td.hover, and having a rule for links */
background-color: #fdf5d0;
color: $text-color;
}
.edit-image {
@@ -245,7 +248,7 @@ tr.green td, .wizContainer tr.green td {
}
tr td.hover, tr.even td.hover, .hover a, .hover a:visited, .hover a:hover, .wizContainer tr.even td.hover, .wizContainer tr td.hover {
background-color: #fdf5d0;
//background-color: #fdf5d0;
color: $text-color;
}
@@ -994,6 +997,10 @@ input.dp-applied {
min-width: 100%;
left: 0px;
margin-top: -1px;
.sfc_fg_buttons{
white-space: nowrap;
}
}
}
@@ -1307,6 +1314,22 @@ input.dp-applied {
}
}
}
&.search_form_criteria_tag_set{
.sfc_form_group{
.sfc_fg_operator_in{
> label{
display: inline-block;
width: 100%;
line-height: initial;
white-space: nowrap;
.sfc_op_content{
width: 100%;
}
}
}
}
}
&.search_form_criteria_numeric {
//.sfc_form_group.advanced {
// .sfc_fg_operator_between {
@@ -2241,6 +2264,28 @@ fieldset .details>.field_container {
}
}
}
&.field_input_set{
.selectize-control{
width: 100%;
.selectize-dropdown,
.selectize-input,
.selectize-input input{
font-size: 12px;
}
.selectize-input{
padding: 2px 2px 0px 2px; /* padding-bottom = padding-top - item margin-bottom */
border: 1px solid #ABABAB;
border-radius: 0;
.attribute-set-item.partial-code{
color: transparentize($gray-darker, 0.4);
background-color: lighten($gray-lighter, 5%);
}
}
}
}
}
}
}
@@ -3262,4 +3307,100 @@ table.listResults .originColor{
}
.menu-icon-select > .ui-menu-item{
padding: .3em 3%;
}
}
//////////////////////
// Set attribute //
// - Readonly (object viewing, objects list)
.attribute {
&.attribute-set {
.attribute-set-item{
&::after{
content: ",";
margin-right: 0.5em;
}
&:last-of-type::after{
content: "";
margin-right: 0;
}
}
&.history-added {
.attribute-set-item {
font-weight: bold;
}
}
&.history-removed {
.attribute-set-item {
text-decoration: line-through;
font-style: italic;
}
}
}
}
// - Edit is always styled like the selectize items, see below.
%attribute-set-item-edition{
display: inline-block;
margin-right: 3px;
margin-bottom: 3px;
padding: 4px 6px;
max-width: 120px;
background: #fdfdfd none;
border-radius: 2px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb(241, 241, 241, 0.7);
color: $gray-darker;
text-shadow: none;
vertical-align: middle;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&::after{
content: "";
margin-right: 0;
}
}
.attribute-edit .attribute-set .attribute-set-item{
@extend %attribute-set-item-edition;
}
//////////////////////
// TagSet attribute //
// Always styled like the selectize items, see below.
.attribute-tag-set.attribute-set{
.attribute-set-item{
@extend %attribute-set-item-edition;
}
}
//////////////////////
// Selectize widget //
//
.selectize-control,
.selectize-control.multi{
> .selectize-input{
&.has-items:after {
content: "+";
display: inline-block;
font-size: 17px;
font-weight: bold;
color: $gray-darker;
}
> .item[data-value]{
@extend %attribute-set-item-edition;
padding-right: 1.5em !important;
border: 1px solid $gray-lighter;
&.active{
@extend %attribute-set-item-edition;
}
> .remove {
padding-top: 0.3em;
border: none;
}
}
}
}

394
css/selectize.default.css Normal file
View File

@@ -0,0 +1,394 @@
/**
* selectize.default.css (v0.12.4) - Default Theme
* Copyright (c) 20132015 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
visibility: visible !important;
background: #f2f2f2 !important;
background: rgba(0, 0, 0, 0.06) !important;
border: 0 none !important;
-webkit-box-shadow: inset 0 0 12px 4px #ffffff;
box-shadow: inset 0 0 12px 4px #ffffff;
}
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
content: '!';
visibility: hidden;
}
.selectize-control.plugin-drag_drop .ui-sortable-helper {
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.selectize-dropdown-header {
position: relative;
padding: 5px 8px;
border-bottom: 1px solid #d0d0d0;
background: #f8f8f8;
-webkit-border-radius: 3px 3px 0 0;
-moz-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
}
.selectize-dropdown-header-close {
position: absolute;
right: 8px;
top: 50%;
color: #303030;
opacity: 0.4;
margin-top: -12px;
line-height: 20px;
font-size: 20px !important;
}
.selectize-dropdown-header-close:hover {
color: #000000;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup {
border-right: 1px solid #f2f2f2;
border-top: 0 none;
float: left;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
border-right: 0 none;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
display: none;
}
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
border-top: 0 none;
}
.selectize-control.plugin-remove_button [data-value] {
position: relative;
padding-right: 24px !important;
}
.selectize-control.plugin-remove_button [data-value] .remove {
z-index: 1;
/* fixes ie bug (see #392) */
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 17px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: inherit;
text-decoration: none;
vertical-align: middle;
display: inline-block;
padding: 2px 0 0 0;
border-left: 1px solid #0073bb;
-webkit-border-radius: 0 2px 2px 0;
-moz-border-radius: 0 2px 2px 0;
border-radius: 0 2px 2px 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.selectize-control.plugin-remove_button [data-value] .remove:hover {
background: rgba(0, 0, 0, 0.05);
}
.selectize-control.plugin-remove_button [data-value].active .remove {
border-left-color: #00578d;
}
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
background: none;
}
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
border-left-color: #aaaaaa;
}
.selectize-control.plugin-remove_button .remove-single {
position: absolute;
right: 28px;
top: 6px;
font-size: 23px;
}
.selectize-control {
position: relative;
}
.selectize-dropdown,
.selectize-input,
.selectize-input input {
color: #303030;
font-family: inherit;
font-size: 13px;
line-height: 18px;
-webkit-font-smoothing: inherit;
}
.selectize-input,
.selectize-control.single .selectize-input.input-active {
background: #ffffff;
cursor: text;
display: inline-block;
}
.selectize-input {
border: 1px solid #d0d0d0;
padding: 8px 8px;
display: inline-block;
width: 100%;
overflow: hidden;
position: relative;
z-index: 1;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.selectize-control.multi .selectize-input.has-items {
padding: 5px 8px 2px;
}
.selectize-input.full {
background-color: #ffffff;
}
.selectize-input.disabled,
.selectize-input.disabled * {
cursor: default !important;
}
.selectize-input.focus {
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
}
.selectize-input.dropdown-active {
-webkit-border-radius: 3px 3px 0 0;
-moz-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
}
.selectize-input > * {
vertical-align: baseline;
display: -moz-inline-stack;
display: inline-block;
zoom: 1;
*display: inline;
}
.selectize-control.multi .selectize-input > div {
cursor: pointer;
margin: 0 3px 3px 0;
padding: 2px 6px;
background: #1da7ee;
color: #ffffff;
border: 1px solid #0073bb;
}
.selectize-control.multi .selectize-input > div.active {
background: #92c836;
color: #ffffff;
border: 1px solid #00578d;
}
.selectize-control.multi .selectize-input.disabled > div,
.selectize-control.multi .selectize-input.disabled > div.active {
color: #ffffff;
background: #d2d2d2;
border: 1px solid #aaaaaa;
}
.selectize-input > input {
display: inline-block !important;
padding: 0 !important;
min-height: 0 !important;
max-height: none !important;
max-width: 100% !important;
margin: 0 1px !important;
text-indent: 0 !important;
border: 0 none !important;
background: none !important;
line-height: inherit !important;
-webkit-user-select: auto !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
.selectize-input > input::-ms-clear {
display: none;
}
.selectize-input > input:focus {
outline: none !important;
}
.selectize-input::after {
content: ' ';
display: block;
clear: left;
}
.selectize-input.dropdown-active::before {
content: ' ';
display: block;
position: absolute;
background: #f0f0f0;
height: 1px;
bottom: 0;
left: 0;
right: 0;
}
.selectize-dropdown {
position: absolute;
z-index: 10;
border: 1px solid #d0d0d0;
background: #ffffff;
margin: -1px 0 0 0;
border-top: 0 none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-webkit-border-radius: 0 0 3px 3px;
-moz-border-radius: 0 0 3px 3px;
border-radius: 0 0 3px 3px;
}
.selectize-dropdown [data-selectable] {
cursor: pointer;
overflow: hidden;
}
.selectize-dropdown [data-selectable] .highlight {
background: rgba(125, 168, 208, 0.2);
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.selectize-dropdown [data-selectable],
.selectize-dropdown .optgroup-header {
padding: 5px 8px;
}
.selectize-dropdown .optgroup:first-child .optgroup-header {
border-top: 0 none;
}
.selectize-dropdown .optgroup-header {
color: #303030;
background: #ffffff;
cursor: default;
}
.selectize-dropdown .active {
background-color: #f5fafd;
color: #495c68;
}
.selectize-dropdown .active.create {
color: #495c68;
}
.selectize-dropdown .create {
color: rgba(48, 48, 48, 0.5);
}
.selectize-dropdown-content {
overflow-y: auto;
overflow-x: hidden;
max-height: 200px;
-webkit-overflow-scrolling: touch;
}
.selectize-control.single .selectize-input,
.selectize-control.single .selectize-input input {
cursor: pointer;
}
.selectize-control.single .selectize-input.input-active,
.selectize-control.single .selectize-input.input-active input {
cursor: text;
}
.selectize-control.single .selectize-input:after {
content: ' ';
display: block;
position: absolute;
top: 50%;
right: 15px;
margin-top: -3px;
width: 0;
height: 0;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #808080 transparent transparent transparent;
}
.selectize-control.single .selectize-input.dropdown-active:after {
margin-top: -4px;
border-width: 0 5px 5px 5px;
border-color: transparent transparent #808080 transparent;
}
.selectize-control.rtl.single .selectize-input:after {
left: 15px;
right: auto;
}
.selectize-control.rtl .selectize-input > input {
margin: 0 4px 0 -2px !important;
}
.selectize-control .selectize-input.disabled {
opacity: 0.5;
background-color: #fafafa;
}
.selectize-control.multi .selectize-input.has-items {
padding-left: 5px;
padding-right: 5px;
}
.selectize-control.multi .selectize-input.disabled [data-value] {
color: #999;
text-shadow: none;
background: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.selectize-control.multi .selectize-input.disabled [data-value],
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
border-color: #e6e6e6;
}
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
background: none;
}
.selectize-control.multi .selectize-input [data-value] {
text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background-color: #1b9dec;
background-image: -moz-linear-gradient(top, #1da7ee, #178ee9);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#1da7ee), to(#178ee9));
background-image: -webkit-linear-gradient(top, #1da7ee, #178ee9);
background-image: -o-linear-gradient(top, #1da7ee, #178ee9);
background-image: linear-gradient(to bottom, #1da7ee, #178ee9);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1da7ee', endColorstr='#ff178ee9', GradientType=0);
-webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
}
.selectize-control.multi .selectize-input [data-value].active {
background-color: #0085d4;
background-image: -moz-linear-gradient(top, #008fd8, #0075cf);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#008fd8), to(#0075cf));
background-image: -webkit-linear-gradient(top, #008fd8, #0075cf);
background-image: -o-linear-gradient(top, #008fd8, #0075cf);
background-image: linear-gradient(to bottom, #008fd8, #0075cf);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff008fd8', endColorstr='#ff0075cf', GradientType=0);
}
.selectize-control.single .selectize-input {
-webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
background-color: #f9f9f9;
background-image: -moz-linear-gradient(top, #fefefe, #f2f2f2);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2));
background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2);
background-image: -o-linear-gradient(top, #fefefe, #f2f2f2);
background-image: linear-gradient(to bottom, #fefefe, #f2f2f2);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0);
}
.selectize-control.single .selectize-input,
.selectize-dropdown.single {
border-color: #b8b8b8;
}
.selectize-dropdown .optgroup-header {
padding-top: 7px;
font-weight: bold;
font-size: 0.85em;
}
.selectize-dropdown .optgroup {
border-top: 1px solid #f0f0f0;
}
.selectize-dropdown .optgroup:first-child {
border-top: 0 none;
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Attachment" _delta="define">
<parent>DBObject</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<menus>
<menu id="BackupStatus" xsi:type="WebPageMenuNode" _delta="define">
<rank>15</rank>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="lnkVirtualDeviceToVolume" _delta="define">
<parent>cmdbAbstractObject</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Change" _delta="define">
<parent>Ticket</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Change" _delta="define">
<parent>Ticket</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Organization" _delta="define">
<parent>cmdbAbstractObject</parent>
@@ -8423,5 +8423,12 @@
</cells>
</definition>
</menu>
<menu id="TagAdminMenu" xsi:type="WebPageMenuNode" _delta="define">
<rank>100</rank>
<parent>Catalogs</parent>
<url>$pages/tagadmin.php</url>
<enable_class>TagSetFieldData</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
</menu>
</menus>
</itop_design>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<menus>
<menu id="ConfigEditor" xsi:type="WebPageMenuNode" _delta="define">
<rank>50</rank>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Rack" _delta="define">
<parent>PhysicalDevice</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="TelephonyCI" _delta="define">
<parent>PhysicalDevice</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design version="1.6">
<classes>
<class id="Ticket">
<methods>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<constants>
</constants>
<classes>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<constants>
<constant id="PORTAL_TYPE_TO_CLASS" xsi:type="string" _delta="redefine"><![CDATA[{"service_request":"UserRequest","incident":"Incident"}]]></constant>
<constant id="PORTAL_INCIDENT_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="KnownError" _delta="define">
<parent>cmdbAbstractObject</parent>

View File

@@ -32,7 +32,9 @@ use DBObjectSet;
use DBSearch;
use DBObjectSearch;
use InlineImage;
use ormTagSet;
use AttributeDateTime;
use AttributeTagSet;
use AttachmentPlugIn;
use Combodo\iTop\Form\FormManager;
use Combodo\iTop\Form\Form;
@@ -1120,7 +1122,18 @@ class ObjectFormManager extends FormManager
// Setting value in the object
$this->oObject->Set($sAttCode, $oLinkSet);
}
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
elseif ($oAttDef instanceof AttributeTagSet)
{
/** @var \ormTagSet $oTagSet */
$oTagSet = $this->oObject->Get($sAttCode);
if (is_null($oTagSet))
{
$oTagSet = new ormTagSet(get_class($this->oObject), $sAttCode);
}
$oTagSet->ApplyDelta(json_decode($value, true));
$this->oObject->Set($sAttCode, $oTagSet);
}
elseif ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
if ($value != null)
{

View File

@@ -45,6 +45,7 @@
<link href="{{ app['combodo.absolute_url'] ~ 'css/font-awesome/css/font-awesome.min.css'|add_itop_version }}" rel="stylesheet">
{# - Misc libs #}
<link href="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/css/typeaheadjs.bootstrap.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ 'css/selectize.default.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ 'css/magnific-popup.css'|add_itop_version }}" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] ~ 'css/c3.min.css'|add_itop_version }}" rel="stylesheet">
{# - Bootstrap theme #}
@@ -85,6 +86,7 @@
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.magnific-popup.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.iframe-transport.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.fileupload.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/utils.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/d3.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/c3.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/bootstrap/js/bootstrap.min.js'|add_itop_version }}"></script>
@@ -114,6 +116,9 @@
{# Typeahead files for autocomplete #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/typeahead/js/typeahead.bundle.min.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'lib/handlebars/js/handlebars.min-768ddbd.js'|add_itop_version }}"></script>
{# Selectize for sets #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/selectize.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/jquery.itop-set-widget.js'|add_itop_version }}"></script>
{# Form files #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_handler.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] ~ 'js/form_field.js'|add_itop_version }}"></script>
@@ -123,6 +128,7 @@
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_handler.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field_html.js'|add_itop_version }}"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] ~ 'js/portal_form_field_set.js'|add_itop_version }}"></script>
{# UI Extensions JS, in an undefined order #}
{% if app['combodo.portal.instance.conf'].ui_extensions.js_files is defined %}
{% for js_file in app['combodo.portal.instance.conf'].ui_extensions.js_files %}

View File

@@ -0,0 +1,55 @@
//iTop Portal Form field Set
//Used for field containing tagset ...
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'portal_form_field' the widget name
$.widget( 'itop.portal_form_field_set', $.itop.portal_form_field,
{
// the constructor
_create: function()
{
this.element
.addClass('portal_form_field_tagset');
this.element.find('input[type="hidden"]').set_widget();
this._super();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('portal_form_field_tagset');
this._super();
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
validate: function(oEvent, oData)
{
var oResult = { is_valid: true, error_messages: [] };
// Doing data validation
if(this.options.validators !== null)
{
// TODO
}
this.options.on_validation_callback(this, oResult);
return oResult;
}
});
});

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<portals>
<portal id="legacy_portal" _delta="delete" /> <!-- TODO: voir si opportun de renommer au lieu de delete+define -->
<portal id="itop-portal" _delta="define"><!-- ID must match module_design[id] -->

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Problem" _delta="define">
<parent>Ticket</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design version="1.6">
<classes/>
<user_rights>
<groups>
@@ -27,6 +27,7 @@
<class id="Typology"/>
<class id="NASFileSystem"/>
<class id="LogicalVolume"/>
<class id="TagSetFieldData"/>
<class id="Tape"/>
<class id="VLAN"/>
</classes>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<constants>
<constant id="PORTAL_TYPE_TO_CLASS" xsi:type="string" _delta="redefine"><![CDATA[{"service_request":"UserRequest","incident":"Incident"}]]></constant>
<constant id="PORTAL_USERREQUEST_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
@@ -1500,6 +1500,9 @@
<item id="priority">
<rank>40</rank>
</item>
<item id="tagfield">
<rank>76</rank>
</item>
</items>
</item>
<item id="fieldset:Ticket:contact">

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Organization">
<fields>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="Organization">
<fields>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="StorageSystem" _delta="define">
<parent>DatacenterDevice</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<constants>
<constant id="RESPONSE_TICKET_SLT_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT SLT AS slt JOIN lnkSLAToSLT AS l1 ON l1.slt_id=slt.id JOIN SLA AS sla ON l1.sla_id=sla.id JOIN lnkCustomerContractToService AS l2 ON l2.sla_id=sla.id JOIN CustomerContract AS sc ON l2.customercontract_id=sc.id WHERE slt.metric = :metric AND l2.service_id = :this->service_id AND sc.org_id = :this->org_id AND slt.request_type = :request_type AND slt.priority = :this->priority]]></constant>
<constant id="PORTAL_POWER_USER_PROFILE" xsi:type="string" _delta="define"><![CDATA[Portal power user]]></constant>
@@ -198,6 +198,9 @@
</field>
<field id="tagfield" xsi:type="AttributeTagSet">
<sql>tagfield</sql>
<is_null_allowed>true</is_null_allowed>
<tracking_level>all</tracking_level>
<max_items>12</max_items>
</field>
</fields>
<methods>
@@ -282,6 +285,9 @@
<item id="operational_status">
<rank>75</rank>
</item>
<item id="tagfield">
<rank>76</rank>
</item>
<item id="start_date">
<rank>80</rank>
</item>

View File

@@ -74,8 +74,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:Ticket/Attribute:close_date+' => '',
'Class:Ticket/Attribute:private_log' => 'Private log',
'Class:Ticket/Attribute:private_log+' => '',
'Class:Ticket/Attribute:contacts_list' => 'Contacts',
'Class:Ticket/Attribute:contacts_list' => 'Contacts',
'Class:Ticket/Attribute:contacts_list+' => 'All the contacts linked to this ticket',
'Class:Ticket/Attribute:tagfield' => 'Tags',
'Class:Ticket/Attribute:functionalcis_list' => 'CIs',
'Class:Ticket/Attribute:functionalcis_list+' => 'All the configuration items impacted by this ticket. Items marked as "Computed" have been automatically marked as impacted. Items marked as "Not impacted" are excluded from the impact.',
'Class:Ticket/Attribute:workorders_list' => 'Work orders',

View File

@@ -63,7 +63,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Ticket/Attribute:private_log+' => '',
'Class:Ticket/Attribute:contacts_list' => 'Contacts',
'Class:Ticket/Attribute:contacts_list+' => '',
'Class:Ticket/Attribute:functionalcis_list' => 'CIs',
'Class:Ticket/Attribute:tagfield' => 'Etiquette',
'Class:Ticket/Attribute:functionalcis_list' => 'CIs',
'Class:Ticket/Attribute:functionalcis_list+' => 'Tous les éléments de configuration impactés par ce ticket. Les éléments marqués comme "Calculés" sont le résultat du calcul de l\'analyse d\'impact. Les éléments marqués comme "Non impactés" sont exclus de cette analyse.',
'Class:Ticket/Attribute:workorders_list' => 'Tâches',
'Class:Ticket/Attribute:workorders_list+' => '',

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="VirtualDevice" _delta="define">
<parent>FunctionalCI</parent>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.5">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.6">
<classes>
<class id="AbstractResource" _delta="define">
<parent>cmdbAbstractObject</parent>

View File

@@ -34,7 +34,17 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributeLinkedSet' => 'Array of objects',
'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass',
'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)',
'Core:AttributeTagSet' => 'List of tags',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'click to add',
'Core:AttributeCaseLog' => 'Log',
'Core:AttributeCaseLog+' => '',
'Core:AttributeMetaEnum' => 'Computed enum',
'Core:AttributeMetaEnum+' => '',
'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)',
'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class',
'Core:AttributeInteger' => 'Integer',
@@ -578,6 +588,15 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnObjectCreate+' => 'Trigger on object creation of [a child class of] the given class',
));
//
// Class: TriggerOnObjectDelete
//
Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnObjectDelete' => 'Trigger (on object deletion)',
'Class:TriggerOnObjectDelete+' => 'Trigger on object deletion of [a child class of] the given class',
));
//
// Class: TriggerOnObjectUpdate
//
@@ -585,7 +604,7 @@ Dict::Add('EN US', 'English', 'English', array(
Dict::Add('EN US', 'English', 'English', array(
'Class:TriggerOnObjectUpdate' => 'Trigger (on object update)',
'Class:TriggerOnObjectUpdate+' => 'Trigger on object update of [a child class of] the given class',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target attributes',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Target fields',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes+' => '',
));
@@ -628,7 +647,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroDataSource/Attribute:name' => 'Name',
'Class:SynchroDataSource/Attribute:name+' => 'Name',
'Class:SynchroDataSource/Attribute:description' => 'Description',
'Class:SynchroDataSource/Attribute:status' => 'Status', //TODO: enum values
'Class:SynchroDataSource/Attribute:status' => 'Status',
'Class:SynchroDataSource/Attribute:scope_class' => 'Target class',
'Class:SynchroDataSource/Attribute:user_id' => 'User',
'Class:SynchroDataSource/Attribute:notify_contact_id' => 'Contact to notify',
@@ -637,7 +656,7 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyperlink a (small) image representing the application with which iTop is synchronized',
'Class:SynchroDataSource/Attribute:url_application' => 'Application\'s hyperlink',
'Class:SynchroDataSource/Attribute:url_application+' => 'Hyperlink to the iTop object in the external application with which iTop is synchronized (if applicable). Possible placeholders: $this->attribute$ and $replica->primary_key$',
'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy', //TODO enum values
'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy',
'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Full load interval',
'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'A complete reload of all data must occur at least as often as specified here',
'Class:SynchroDataSource/Attribute:action_on_zero' => 'Action on zero',
@@ -648,7 +667,6 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:SynchroDataSource/Attribute:action_on_multiple+' => 'Action taken when the search returns more than one object',
'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed',
'Class:SynchroDataSource/Attribute:user_delete_policy+' => 'Who is allowed to delete synchronized objects',
'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed',
'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Nobody',
'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Administrators only',
'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'All allowed users',
@@ -666,7 +684,7 @@ Dict::Add('EN US', 'English', 'English', array(
'SynchroDataSource:Definition' => 'Definition',
'Core:SynchroAttributes' => 'Attributes',
'Core:SynchroStatus' => 'Status',
'Core:Synchro:ErrorsLabel' => 'Errors',
'Core:Synchro:ErrorsLabel' => 'Errors',
'Core:Synchro:CreatedLabel' => 'Created',
'Core:Synchro:ModifiedLabel' => 'Modified',
'Core:Synchro:UnchangedLabel' => 'Unchanged',
@@ -693,18 +711,17 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Synchro:label_obj_disappeared_errors' => 'Errors (%1$s)',
'Core:Synchro:label_obj_disappeared_no_action' => 'No Action (%1$s)',
'Core:Synchro:label_obj_unchanged' => 'Unchanged (%1$s)',
'Core:Synchro:label_obj_updated' => 'Updated (%1$s)',
'Core:Synchro:label_obj_updated' => 'Updated (%1$s)',
'Core:Synchro:label_obj_updated_errors' => 'Errors (%1$s)',
'Core:Synchro:label_obj_new_unchanged' => 'Unchanged (%1$s)',
'Core:Synchro:label_obj_new_updated' => 'Updated (%1$s)',
'Core:Synchro:label_obj_created' => 'Created (%1$s)',
'Core:Synchro:label_obj_new_errors' => 'Errors (%1$s)',
'Core:Synchro:History' => 'Synchronization History',
'Core:SynchroLogTitle' => '%1$s - %2$s',
'Core:Synchro:Nb_Replica' => 'Replica processed: %1$s',
'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s',
'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.',
'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete',
'Class:SynchroDataSource/Error:AtLeastOneReconciliationKeyMustBeSpecified' => 'At Least one reconciliation key must be specified, or the reconciliation policy must be to use the primary key.',
'Class:SynchroDataSource/Error:DeleteRetentionDurationMustBeSpecified' => 'A delete retention period must be specified, since objects are to be deleted after being marked as obsolete',
'Class:SynchroDataSource/Error:DeletePolicyUpdateMustBeSpecified' => 'Obsolete objects are to be updated, but no update is specified.',
'Class:SynchroDataSource/Error:DataTableAlreadyExists' => 'The table %1$s already exists in the database. Please use another name for the synchro data table.',
'Core:SynchroReplica:PublicData' => 'Public Data',
@@ -833,16 +850,16 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:ExecProcess:Code255' => 'PHP Error (parsing, or runtime)',
// Attribute Duration
'Core:Duration_Seconds' => '%1$ds',
'Core:Duration_Minutes_Seconds' =>'%1$dmin %2$ds',
'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',
'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds',
'Core:Duration_Seconds' => '%1$ds',
'Core:Duration_Minutes_Seconds' =>'%1$dmin %2$ds',
'Core:Duration_Hours_Minutes_Seconds' => '%1$dh %2$dmin %3$ds',
'Core:Duration_Days_Hours_Minutes_Seconds' => '%1$sd %2$dh %3$dmin %4$ds',
// Explain working time computing
'Core:ExplainWTC:ElapsedTime' => 'Time elapsed (stored as "%1$s")',
'Core:ExplainWTC:StopWatch-TimeSpent' => 'Time spent for "%1$s"',
'Core:ExplainWTC:StopWatch-Deadline' => 'Deadline for "%1$s" at %2$d%%',
// Bulk export
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter "%1$s"',
'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter "query". There is no Query Phrasebook corresponding to the id: "%1$s".',
@@ -913,3 +930,29 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:Validator:MustBeInteger' => 'Must be an integer',
'Core:Validator:MustSelectOne' => 'Please, select one',
));
//
// Class: TagSetFieldData
//
Dict::Add('EN US', 'English', 'English', array(
'Class:TagSetFieldData' => '%2$s for class %1$s',
'Class:TagSetFieldData+' => '',
'Class:TagSetFieldData/Attribute:code' => 'Code',
'Class:TagSetFieldData/Attribute:code+' => 'Internal code. Must contain at least 3 alphanumeric characters',
'Class:TagSetFieldData/Attribute:label' => 'Label',
'Class:TagSetFieldData/Attribute:label+' => 'Displayed label',
'Class:TagSetFieldData/Attribute:description' => 'Description',
'Class:TagSetFieldData/Attribute:description+' => 'Description',
'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Used tags cannot be deleted',
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique',
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code must contain between 3 and %1$d alphanumeric characters',
'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'The chosen tag code is a reserved word',
'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Tags label must not contain \'%1$s\' nor be empty',
'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags Code cannot be changed when used',
'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Tags "Object Class" cannot be changed',
'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Tags "Attribute Code" cannot be changed',
'Core:TagSetFieldData:WhereIsThisTagTab' => 'Tag usage (%1$d)',
'Core:TagSetFieldData:NoEntryFound' => 'No entry found for this tag',
));

View File

@@ -956,7 +956,12 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:NotificationsMenu:OnStateLeave' => 'When an object leaves a given state',
'UI:NotificationsMenu:Actions' => 'Actions',
'UI:NotificationsMenu:AvailableActions' => 'Available actions',
'Menu:TagAdminMenu' => 'Tags configuration',
'Menu:TagAdminMenu+' => 'Tags values management',
'UI:TagAdminMenu:Title' => 'Tags configuration',
'UI:TagSetFieldData:Error' => 'Error: %1$s',
'Menu:AuditCategories' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...)
'Menu:AuditCategories+' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...)
'Menu:Notifications:Title' => 'Audit Categories', // Duplicated into itop-welcome-itil (will be removed from here...)
@@ -1447,6 +1452,8 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Search:Criteria:Title:Enum:In' => '%1$s: %2$s',
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s: %2$s and %3$s others',
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s: Any',
// - TagSet widget
'UI:Search:Criteria:Title:TagSet:Matches' => '%1$s: %2$s',
// - External key widget
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s is defined',
'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s is not defined',
@@ -1480,6 +1487,8 @@ When associated with a trigger, each action is given an "order" number, specifyi
'UI:Search:Criteria:Operator:Numeric:LessThan' => 'Less', // => '<',
'UI:Search:Criteria:Operator:Numeric:LessThanOrEquals' => 'Less / equals', // > '<=',
'UI:Search:Criteria:Operator:Numeric:Different' => 'Different', // => '≠',
// - Tag Set Widget
'UI:Search:Criteria:Operator:TagSet:Matches' => 'Matches',
// - Other translations
'UI:Search:Value:Filter:Placeholder' => 'Filter...',

View File

@@ -61,6 +61,8 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:TriggerOnStateLeave+' => '',
'Class:TriggerOnObjectCreate' => 'Déclencheur sur la création d\'un objet',
'Class:TriggerOnObjectCreate+' => '',
'Class:TriggerOnObjectDelete' => 'Déclencheur sur la suppression d\'un objet',
'Class:TriggerOnObjectDelete+' => '',
'Class:TriggerOnObjectUpdate' => 'Déclencheur sur la modification d\'un objet',
'Class:TriggerOnObjectUpdate+' => '',
'Class:TriggerOnObjectUpdate/Attribute:target_attcodes' => 'Attributs cible',
@@ -430,6 +432,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:appUserPreferences/Attribute:preferences+' => '',
'Core:AttributeLinkedSet' => 'Objets liés (1-n)',
'Core:AttributeLinkedSet+' => 'Liste d\'objets d\'une classe donnée et pointant sur l\'objet courant',
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
'Core:AttributeLinkedSetIndirect' => 'Objets liés (1-n)',
'Core:AttributeLinkedSetIndirect+' => 'Liste d\'objets d\'une classe donnée et liés à l\'objet courant via une classe intermédiaire',
'Core:AttributeInteger' => 'Nombre entier',
@@ -766,4 +771,26 @@ Opérateurs :<br/>
'Core:Validator:Mandatory' => 'Veuillez remplir ce champ',
'Core:Validator:MustBeInteger' => 'Ce champ ne peut contenir qu\'un nombre entier',
'Core:Validator:MustSelectOne' => 'Veuillez choisir une valeur',
'Class:TagSetFieldData' => '%2$s pour la classe %1$s',
'Class:TagSetFieldData+' => '',
'Class:TagSetFieldData/Attribute:code' => 'Code',
'Class:TagSetFieldData/Attribute:code+' => 'Code interne. Doit contenir au moins 3 caractères alphanumériques',
'Class:TagSetFieldData/Attribute:label' => 'Label',
'Class:TagSetFieldData/Attribute:label+' => 'Label',
'Class:TagSetFieldData/Attribute:description' => 'Description',
'Class:TagSetFieldData/Attribute:description+' => 'Description',
'Core:TagSetFieldData:ErrorDeleteUsedTag' => 'Impossible de supprimer une étiquette utilisée',
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Les codes et noms des étiquettes doivent être unique',
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Le code de l\'étiquette doit contenir entre 3 et %1$d caractères alphanumériques.',
'Core:TagSetFieldData:ErrorTagCodeReservedWord' => 'Le code de l\'étiquette un mot réservé.',
'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Le nom de l\'étiquette ne doit pas être vide ni contenir le caractère \'%1$s\'',
'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Le code de l\'étiquette ne peut pas être changé',
'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'La classe de l\'étiquette ne peut pas être changée',
'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'L\'attribut de l\'étiquette ne peut pas être changé',
'Core:TagSetFieldData:WhereIsThisTagTab' => 'Utilisation (%1$d)',
'Core:TagSetFieldData:NoEntryFound' => 'Pas d\'utilisation de cette étiquette',
));

View File

@@ -1280,6 +1280,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Criteria:Title:Enum:In' => '%1$s : %2$s',
'UI:Search:Criteria:Title:Enum:In:Many' => '%1$s : %2$s et %3$s autres',
'UI:Search:Criteria:Title:Enum:In:All' => '%1$s : Indifférent',
// - TagSet widget
'UI:Search:Criteria:Title:TagSet:Matches' => '%1$s : %2$s',
// - External key widget
'UI:Search:Criteria:Title:ExternalKey:Empty' => '%1$s est renseigné',
'UI:Search:Criteria:Title:ExternalKey:NotEmpty' => '%1$s n\'est pas renseigné',
@@ -1294,6 +1296,8 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Criteria:Title:HierarchicalKey:In' => '%1$s : %2$s',
'UI:Search:Criteria:Title:HierarchicalKey:In:Many' => '%1$s : %2$s et %3$s autres',
'UI:Search:Criteria:Title:HierarchicalKey:In:All' => '%1$s : Indifférent',
// - Tag Set Widget
'UI:Search:Criteria:Operator:TagSet:Matches' => 'Contient',
/// - Criteria operators
// - Default widget
@@ -1339,6 +1343,12 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:Search:Criteria:Raw:Filtered' => 'Filtré',
'UI:Search:Criteria:Raw:FilteredOn' => 'Filtré sur %1$s',
// - Tags admin
'Menu:TagAdminMenu' => 'Etiquettes',
'Menu:TagAdminMenu+' => 'Gestion des étiquettes',
'UI:TagAdminMenu:Title' => 'Gestion des étiquettes',
'UI:TagSetFieldData:Error' => 'Erreur: %1$s',
));

View File

@@ -0,0 +1,290 @@
/*
* Copyright (c) 2010-2018 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*
*/
/**
* <p>To be applied on a field containing a JSON value. The value will be updated on every change.<br>
* Exemple of JSON value :
* <code>
* {
* "possible_values": [
* {
* "code": "critical",
* "label": "Critical ticket"
* },
* {
* "code": "high",
* "label": "don't forget it !"
* },
* {
* "code": "normal",
* "label": "when time available"
* },
* {
* "code": "low",
* "label": "don't worry ;)"
* }
* ],
* "max_items_allowed": 20,
* "partial_values": [],
* "orig_value": [
* "critical"
* ],
* "added": [
* "normal",
* "high",
* "low"
* ],
* "removed": ["critical"]
* }
* </code>
*
* <p>Needs js/selectize.js already loaded !! (https://github.com/selectize/selectize.js)<br>
* In the future we could use WebPack... Or a solution like this :
* https://www.safaribooksonline.com/library/view/learning-javascript-design/9781449334840/ch13s09.html
*/
$.widget('itop.set_widget',
{
// default options
options: {isDebug: false},
PARENT_CSS_CLASS: "attribute-set",
ITEM_CSS_CLASS: "attribute-set-item",
POSSIBLE_VAL_KEY: 'possible_values',
PARTIAL_VAL_KEY: "partial_values",
ORIG_VAL_KEY: "orig_value",
ADDED_VAL_KEY: "added",
REMOVED_VAL_KEY: "removed",
STATUS_ADDED: "added",
STATUS_REMOVED: "removed",
STATUS_NEUTRAL: "unchanged",
MAX_ITEMS_ALLOWED_KEY: "max_items_allowed",
possibleValues: null,
partialValues: null,
originalValue: null,
/** will hold all interactions done : code as key and one of STATUS_* constant as value */
setItemsCodesStatus: null,
selectizeWidget: null,
maxItemsAllowed: null,
// the constructor
_create: function () {
var $this = this.element;
this._initWidgetData($this.val());
this._generateSelectionWidget($this);
this._bindEvents($this);
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function () {
this.refresh();
},
_initWidgetData: function (originalFieldValue) {
var dataArray = JSON.parse(originalFieldValue),
setWidget = this;
this.possibleValues = dataArray[this.POSSIBLE_VAL_KEY];
this.partialValues = ($.isArray(dataArray[this.PARTIAL_VAL_KEY])) ? dataArray[this.PARTIAL_VAL_KEY] : [];
this.originalValue = dataArray[this.ORIG_VAL_KEY];
this.maxItemsAllowed = dataArray[this.MAX_ITEMS_ALLOWED_KEY];
this.setItemsCodesStatus = {};
// load existing removed codes
// used for example in triggers update fields selection, after switching class
// class A + fields a,b selected, then switch to class B : the server sends fields a,b to as removed values
dataArray[this.REMOVED_VAL_KEY].forEach(function(setItemCode) {
setWidget.setItemsCodesStatus[setItemCode] = setWidget.STATUS_REMOVED;
});
},
_generateSelectionWidget: function ($widgetElement) {
var $parentElement = $widgetElement.parent(),
isWidgetElementDisabled = $widgetElement.prop("disabled"),
inputId = $widgetElement.attr("id") + "-setwidget-values";
$parentElement.append("<input id='" + inputId + "' value='" + this.originalValue.join(" ") + "'>");
var $inputWidget = $("#" + inputId);
if (isWidgetElementDisabled) {
$inputWidget.prop("disabled", true);
}
// create closure to have both set widget and Selectize instances available in callbacks
// selectize instance could also be retrieve on the source input DOM node (selectize property)
// I think this is much clearer this way !
var setWidget = this;
$inputWidget.selectize({
plugins: ['remove_button'],
delimiter: ' ',
maxItems: this.maxItemsAllowed,
hideSelected: true,
valueField: 'code',
labelField: 'label',
searchField: 'label',
options: this.possibleValues,
create: false,
placeholder: Dict.S("Core:AttributeSet:placeholder"),
onInitialize: function () {
var selectizeWidget = this;
setWidget._onInitialize(selectizeWidget);
},
onItemAdd: function (value, $item) {
var selectizeWidget = this;
setWidget._onTagAdd(value, $item, selectizeWidget);
},
onItemRemove: function (value) {
var selectizeWidget = this;
setWidget._onTagRemove(value, selectizeWidget);
}
});
this.selectizeWidget = $inputWidget[0].selectize; // keeping this for set widget public methods
},
_bindEvents: function($widgetElement) {
var setWidget = this;
$widgetElement.bind("update", function() {
console.debug("update event in Selectize !", this);
var $this = $(this);
if ($this.prop("disabled")) {
setWidget.disable();
} else {
setWidget.enable();
}
});
},
refresh: function () {
if (this.options.isDebug) {
console.debug("refresh");
}
var widgetPublicData = {}, addedValues = [], removedValues = [];
widgetPublicData[this.POSSIBLE_VAL_KEY] = this.possibleValues;
widgetPublicData[this.PARTIAL_VAL_KEY] = this.partialValues;
widgetPublicData[this.ORIG_VAL_KEY] = this.originalValue;
for (var setItemCode in this.setItemsCodesStatus) {
var setItemCodeStatus = this.setItemsCodesStatus[setItemCode];
switch (setItemCodeStatus) {
case this.STATUS_ADDED:
addedValues.push(setItemCode);
break;
case this.STATUS_REMOVED:
removedValues.push(setItemCode);
break;
}
}
widgetPublicData[this.ADDED_VAL_KEY] = addedValues;
widgetPublicData[this.REMOVED_VAL_KEY] = removedValues;
this.element.val(JSON.stringify(widgetPublicData, null, (this.options.isDebug ? 2 : null)));
},
disable: function () {
this.selectizeWidget.disable();
},
enable: function () {
this.selectizeWidget.enable();
},
/**
* <p>Updating selection widget :
* <ul>
* <li>handles bulk edit disabling on widget opening
* <li>adding specific CSS class to parent node
* <li>adding specific CSS classes to item node
* <li>items to have a specific rendering for partial codes.
* </ul>
*
* <p>For partial codes at first I was thinking about using the Selectize <code>render</code> callback, but it is called before <code>onItemAdd</code>/<code>onItemRemove</code> :(<br>
* Indeed as we only need to have partial items on first display, this callback is the right place O:)
*
* @param inputWidget Selectize object
* @private
*/
_onInitialize: function (inputWidget) {
var setWidget = this;
if (this.options.isDebug) {
console.debug("onInit", inputWidget, setWidget);
}
if (inputWidget.$input.prop("disabled")) {
inputWidget.disable(); // can't use this.selectizeWidget for now
}
inputWidget.$control.addClass(setWidget.PARENT_CSS_CLASS);
inputWidget.items.forEach(function (setItemCode) {
var $item = inputWidget.getItem(setItemCode);
$item.addClass(setWidget.ITEM_CSS_CLASS);
$item.addClass(setWidget.ITEM_CSS_CLASS + '-' + setItemCode); // no escape as codes are already pretty restrictive
if (setWidget._isCodeInPartialValues(setItemCode)) {
inputWidget.getItem(setItemCode).addClass("partial-code");
}
});
},
_onTagAdd: function (setItemCode, $item, inputWidget) {
if (this.options.isDebug) {
console.debug("tagAdd");
}
this.setItemsCodesStatus[setItemCode] = this.STATUS_ADDED;
if (this._isCodeInPartialValues(setItemCode)) {
this.partialValues = this.partialValues.filter(item => (item !== setItemCode));
} else {
if (this.originalValue.indexOf(setItemCode) !== -1) {
// do not add if was present initially and removed
this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL;
}
}
this.refresh();
},
_onTagRemove: function (setItemCode, inputWidget) {
this.setItemsCodesStatus[setItemCode] = this.STATUS_REMOVED;
if (this._isCodeInPartialValues(setItemCode)) {
// force rendering items again, otherwise partial class will be kept
// can'be in the onItemAdd callback as it is called after the render callback...
inputWidget.clearCache("item");
}
if (this.originalValue.indexOf(setItemCode) === -1) {
// do not remove if wasn't present initially
this.setItemsCodesStatus[setItemCode] = this.STATUS_NEUTRAL;
}
this.refresh();
},
_isCodeInPartialValues: function (setItemCode) {
return (this.partialValues.indexOf(setItemCode) >= 0);
}
});

View File

@@ -0,0 +1,91 @@
//iTop Search form criteria tag_set
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'search_form_criteria_tag_set' the widget name
$.widget( 'itop.search_form_criteria_tag_set', $.itop.search_form_criteria_enum,
{
// default options
options:
{
// Overload default operator
'operator': 'MATCHES',
// Available operators
'available_operators': {
'MATCHES': {
'label': Dict.S('UI:Search:Criteria:Operator:TagSet:MATCHES'),
'code': 'matches',
'rank': 10,
},
'IN': null,
'=': null, // Remove this one from tag_set widget.
'empty': null, // Remove as it will be handle by the "null" value in the "MATCHES" operator
'not_empty': null, // Remove as it will be handle by the "null" value in the "MATCHES" operator
},
// Null value
'null_value': {
'code': '',
'label': Dict.S('Enum:Undefined'),
},
},
// the constructor
_create: function()
{
var me = this;
this._super();
this.element.addClass('search_form_criteria_tag_set');
},
// called when created, and later when changing options
_refresh: function()
{
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element.removeClass('search_form_criteria_tag_set');
this._super();
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
//------------------
// Inherited methods
//------------------
_prepareMatchesOperator: function(oOpElem, sOpIdx, oOp)
{
this._prepareInOperator(oOpElem, sOpIdx, oOp);
},
// Operators helpers
// Reset operator's state
_resetMatchesOperator: function(oOpElem)
{
this._resetInOperator(oOpElem);
},
// Get operator's values
_getMatchesOperatorValues: function(oOpElem)
{
return this._getInOperatorValues(oOpElem);
},
// Set operator's values
_setMatchesOperatorValues: function(oOpElem, aValues)
{
return this._setInOperatorValues(oOpElem, aValues);
},
});
});

3829
js/selectize.js Normal file

File diff suppressed because it is too large Load Diff

1034
js/selectize.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -356,7 +356,7 @@ function CheckAll(sSelector, bValue) {
/**
* Toggle (enabled/disabled) the specified field of a form
*/
function ToogleField(value, field_id) {
function ToggleField(value, field_id) {
if (value) {
$('#'+field_id).prop('disabled', false);
// In case the field is rendered as a div containing several inputs (e.g. RedundancySettings)
@@ -410,7 +410,7 @@ function PropagateCheckBox(bCurrValue, aFieldsList, bCheck) {
if (bCurrValue == bCheck) {
for (var i = 0; i < aFieldsList.length; i++) {
$('#enable_'+aFieldsList[i]).prop('checked', bCheck);
ToogleField(bCheck, aFieldsList[i]);
ToggleField(bCheck, aFieldsList[i]);
}
}
}

View File

@@ -1244,7 +1244,7 @@ EOF
}
$aArgs = array('this' => $oObj);
$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sAttCode, '', $iExpectCode, $aArgs);
$sComments = '<input type="checkbox" checked id="enable_'.$sAttCode.'" onClick="ToogleField(this.checked, \''.$sAttCode.'\')"/>';
$sComments = '<input type="checkbox" checked id="enable_'.$sAttCode.'" onClick="ToggleField(this.checked, \''.$sAttCode.'\')"/>';
if (!isset($aValues[$sAttCode]))
{
$aValues[$sAttCode] = array();

142
pages/tagadmin.php Normal file
View File

@@ -0,0 +1,142 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Page to configuration the tag sets
*
* @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once('../approot.inc.php');
require_once(APPROOT.'application/application.inc.php');
require_once(APPROOT.'application/itopwebpage.class.inc.php');
require_once(APPROOT.'application/startup.inc.php');
require_once(APPROOT.'application/loginwebpage.class.inc.php');
try
{
LoginWebPage::DoLogin();
// Check user rights and prompt if needed
ApplicationMenu::CheckMenuIdEnabled("TagAdminMenu");
$oAppContext = new ApplicationContext();
// Main program
//
$oP = new iTopWebPage(Dict::S('Menu:TagAdminMenu+'));
$oP->add_linked_script("../js/json.js");
$oP->add_linked_script("../js/forms-json-utils.js");
$oP->add_linked_script("../js/wizardhelper.js");
$oP->add_linked_script("../js/wizard.utils.js");
$oP->add_linked_script("../js/linkswidget.js");
$oP->add_linked_script("../js/extkeywidget.js");
$oP->add_linked_script("../js/jquery.blockUI.js");
$sBaseClass = 'TagSetFieldData';
$sClass = utils::ReadParam('class', '', false, 'class');
$sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data');
$sFilter = utils::ReadParam('filter', '', false, 'raw_data');
$sOperation = utils::ReadParam('operation', '');
$oP->add('<div class="page_header" style="padding:0.5em;">');
$oP->add('<h1>'.dict::S('UI:TagAdminMenu:Title').'</h1>');
$oP->add('</div>');
$oP->SetBreadCrumbEntry('ui-tool-tag-admin', Dict::S('Menu:TagAdminMenu'), Dict::S('Menu:TagAdminMenu+'), '', utils::GetAbsoluteUrlAppRoot().'images/wrench.png');
$sSearchHeaderForceDropdown = '<select id="select_class" name="class" onChange="this.form.submit();">';
$aClassLabels = array();
foreach(MetaModel::EnumChildClasses($sBaseClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sCurrentClass)
{
$aClassLabels[$sCurrentClass] = MetaModel::GetName($sCurrentClass);
}
asort($aClassLabels);
foreach($aClassLabels as $sCurrentClass => $sLabel)
{
if (empty($sClass))
{
$sClass = $sCurrentClass;
}
$sSelected = ($sCurrentClass == $sClass) ? " SELECTED" : "";
$sSearchHeaderForceDropdown .= "<option value=\"$sCurrentClass\" title=\"$sLabel\" $sSelected>$sLabel</option>";
}
$sSearchHeaderForceDropdown .= "</select>\n";
try
{
if ($sOperation == 'search_form')
{
$sOQL = "SELECT $sClass $sOQLClause";
$oFilter = DBObjectSearch::FromOQL($sOQL);
}
else
{
// Second part: advanced search form:
if (!empty($sFilter))
{
$oFilter = DBSearch::unserialize($sFilter);
}
else if (!empty($sClass))
{
$oFilter = new DBObjectSearch($sClass);
}
}
}
catch (CoreException $e)
{
$oFilter = new DBObjectSearch($sClass);
$oP->P("<b>".Dict::Format('UI:TagSetFieldData:Error', $e->getHtmlDesc())."</b>");
}
if ($oFilter != null)
{
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'search', false);
$aExtraParams = $oAppContext->GetAsHash();
$aExtraParams['open'] = true;
$aExtraParams['class'] = $sClass;
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
$aExtraParams['table_id'] = '1';
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
$oBlock->Display($oP, 0, $aExtraParams);
// Search results
$oResultBlock = new DisplayBlock($oFilter, 'list', false);
$oResultBlock->Display($oP, 1);
// Menu node
$sFilter = $oFilter->ToOQL();
$oP->add("\n<!-- $sFilter -->\n");
}
$oP->add("</div>\n");
$oP->output();
}
catch (Exception $e)
{
require_once(APPROOT.'setup/setuppage.class.inc.php');
$oP = new SetupPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
//$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
$oP->output();
IssueLog::Error($e->getMessage());
}

View File

@@ -1388,18 +1388,47 @@ EOF
{
$aParameters['handler_class'] = $this->GetMandatoryPropString($oField, 'handler_class');
}
else
{
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
$aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql');
$aParameters['default_value'] = $this->GetPropString($oField, 'default_value', '');
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
$aParameters['depends_on'] = $sDependencies;
}
if ($sAttType == 'AttributeTagSet')
elseif ($sAttType == 'AttributeTagSet')
{
$aTagFieldsInfo[] = $sAttCode;
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
$aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql');
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
$aParameters['depends_on'] = $sDependencies;
$aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12);
$aParameters['tag_code_max_len'] = $this->GetPropNumber($oField, 'tag_code_max_len', 20);
if ($aParameters['tag_code_max_len'] > 255)
{
$aParameters['tag_code_max_len'] = 255;
}
}
elseif ($sAttType == 'AttributeClassAttCodeSet')
{
$aTagFieldsInfo[] = $sAttCode;
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
$aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql');
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
$aParameters['depends_on'] = $sDependencies;
$aParameters['max_items'] = $this->GetPropNumber($oField, 'max_items', 12);
$aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field');
$aParameters['attribute_definition_list'] = $this->GetPropString($oField, 'attribute_definition_list', '');
}
elseif ($sAttType == 'AttributeClassState')
{
$aTagFieldsInfo[] = $sAttCode;
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
$aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql');
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
$aParameters['depends_on'] = $sDependencies;
$aParameters['class_field'] = $this->GetMandatoryPropString($oField, 'class_field');
}
else
{
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
$aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql');
$aParameters['default_value'] = $this->GetPropString($oField, 'default_value', '');
$aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false);
$aParameters['depends_on'] = $sDependencies;
}
// Optional parameters (more for historical reasons)
@@ -1837,17 +1866,16 @@ EOF;
(
'category' => 'bizmodel',
'key_type' => 'autoincrement',
'name_attcode' => array('tag_label'),
'name_attcode' => array('label'),
'state_attcode' => '',
'reconc_keys' => array('tag_code'),
'reconc_keys' => array('code'),
'db_table' => '', // no need to have a corresponding table : this class exists only for rights, no additional field
'db_key_field' => 'id',
'db_finalclass_field' => 'finalclass',
);
foreach ($aTagFieldsInfo as $sTagFieldName)
{
$sTagSuffix = $sClassName.'_'.$sTagFieldName;
$sTagClassName = 'TagSetFieldDataFor_'.$sTagSuffix;
$sTagClassName = static::GetTagDataClassName($sClassName, $sTagFieldName);
$sTagClassParams = var_export($aTagClassParams, true);
$sPHP .= $this->GeneratePhpCodeForClass($sTagClassName, $sTagClassParentClass, $sTagClassParams);
}
@@ -1856,6 +1884,12 @@ EOF;
return $sPHP;
}
private static function GetTagDataClassName($sClass, $sAttCode)
{
$sTagSuffix = $sClass.'__'.$sAttCode;
return 'TagSetFieldDataFor_'.$sTagSuffix;
}
/**
* @param $oMenu
@@ -2668,7 +2702,7 @@ EOF;
* @param bool $bIsAbstractClass
* @param string $sMethods
*
* @param string $aRequiredFiles
* @param array $aRequiredFiles
* @param string $sCodeComment
*
* @return string php code for the class

View File

@@ -34,8 +34,8 @@
* echo "Error, failed to upgrade the format, reason(s):\n".implode("\n", $oFormat->GetErrors());
* }
*/
define('ITOP_DESIGN_LATEST_VERSION', '1.5'); // iTop >= 2.5.0
define('ITOP_DESIGN_LATEST_VERSION', '1.6'); // iTop >= 2.6.0
class iTopDesignFormat
{
@@ -73,6 +73,12 @@ class iTopDesignFormat
'1.5' => array(
'previous' => '1.4',
'go_to_previous' => 'From15To14',
'next' => '1.6',
'go_to_next' => 'From15To16',
),
'1.6' => array(
'previous' => '1.5',
'go_to_previous' => 'From16To15',
'next' => null,
'go_to_next' => null,
),
@@ -629,6 +635,38 @@ class iTopDesignFormat
{
}
/**
* @param $oFactory
*
* @return void (Errors are logged)
*/
protected function From15To16($oFactory)
{
// nothing changed !
}
/**
* @param $oFactory
*
* @return void (Errors are logged)
*/
protected function From16To15($oFactory)
{
$oXPath = new DOMXPath($this->oDocument);
// Remove AttributeTagSet nodes
//
$sPath = "/itop_design/classes/class/fields/field[@xsi:type='AttributeTagSet']";
$oNodeList = $oXPath->query($sPath);
foreach ($oNodeList as $oNode)
{
$this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be removed.');
$this->DeleteNode($oNode);
}
}
/**
* Delete a node from the DOM and make sure to also remove the immediately following line break (DOMText), if any.
* This prevents generating empty lines in the middle of the XML

View File

@@ -273,6 +273,10 @@ class XMLDataLoader
$oDoc = new ormDocument($data, $sMimeType, $sFileName);
$oTargetObj->Set($sAttCode, $oDoc);
}
elseif ($oAttDef instanceof AttributeTagSet)
{
// TODO
}
else
{
$value = (string)$oSubNode;

View File

@@ -74,6 +74,7 @@ class CriterionToOQL extends CriterionConversionAbstract
self::OP_BETWEEN => 'BetweenToOql',
self::OP_REGEXP => 'RegexpToOql',
self::OP_IN => 'InToOql',
self::OP_MATCHES => 'MatchesToOql',
self::OP_ALL => 'AllToOql',
);
@@ -118,7 +119,10 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
if (empty($sValue))
{
return "1";
}
return "({$sRef} LIKE '%{$sValue}%')";
}
@@ -128,7 +132,10 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
if (empty($sValue))
{
return "1";
}
return "({$sRef} LIKE '{$sValue}%')";
}
@@ -138,7 +145,10 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
if (empty($sValue))
{
return "1";
}
return "({$sRef} LIKE '%{$sValue}')";
}
@@ -147,8 +157,10 @@ class CriterionToOQL extends CriterionConversionAbstract
{
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
if (empty($sValue) && (!(isset($aCriteria['has_undefined'])) || !($aCriteria['has_undefined'])))
{
return "1";
}
return "({$sRef} = '{$sValue}')";
}
@@ -158,11 +170,49 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
if (empty($sValue))
{
return "1";
}
return "({$sRef} REGEXP '{$sValue}')";
}
protected static function MatchesToOql($oSearch, $sRef, $aCriteria)
{
$aValues = self::GetValues($aCriteria);
$aRawValues = array();
$bHasUnDefined = isset($aCriteria['has_undefined']) ? $aCriteria['has_undefined'] : false;
for($i = 0; $i < count($aValues); $i++)
{
$sRawValue = self::GetValue($aValues, $i);
if (strlen($sRawValue) == 0)
{
$bHasUnDefined = true;
}
else
{
$aRawValues[] = $sRawValue;
}
}
$sValue = implode(' ', $aRawValues);
if (empty($sValue))
{
if ($bHasUnDefined)
{
return "({$sRef} = '')";
}
return "1";
}
if ($bHasUnDefined)
{
return "((({$sRef} MATCHES '{$sValue}') OR ({$sRef} = '')) AND 1)";
}
return "({$sRef} MATCHES '{$sValue}')";
}
protected static function EmptyToOql($oSearch, $sRef, $aCriteria)
{
if (isset($aCriteria['widget']))
@@ -197,18 +247,18 @@ class CriterionToOQL extends CriterionConversionAbstract
return "({$sRef} != '')";
}
/**
* @param \DBObjectSearch $oSearch
* @param string $sRef
* @param array $aCriteria
*
* @return mixed|string
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
/**
* @param \DBObjectSearch $oSearch
* @param string $sRef
* @param array $aCriteria
*
* @return mixed|string
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
protected static function InToOql($oSearch, $sRef, $aCriteria)
{
$sAttCode = $aCriteria['code'];
@@ -225,8 +275,7 @@ class CriterionToOQL extends CriterionConversionAbstract
try
{
$aAttributeDefs = MetaModel::ListAttributeDefs($sClass);
}
catch (\CoreException $e)
} catch (\CoreException $e)
{
return "1";
}
@@ -254,8 +303,7 @@ class CriterionToOQL extends CriterionConversionAbstract
try
{
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
}
catch (\CoreException $e)
} catch (\CoreException $e)
{
}
}
@@ -371,9 +419,10 @@ class CriterionToOQL extends CriterionConversionAbstract
else
{
// Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing
$sCondition = "(({$sCondition} OR {$sFilterOnUndefined}) AND 1)";
$sCondition = "(({$sCondition} OR {$sFilterOnUndefined}) AND 1)";
}
}
return $sCondition;
}
@@ -406,8 +455,7 @@ class CriterionToOQL extends CriterionConversionAbstract
$oDate = $oFormat->parse($sStartDate);
$sStartDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} >= '$sStartDate')";
}
catch (Exception $e)
} catch (Exception $e)
{
}
}
@@ -420,8 +468,7 @@ class CriterionToOQL extends CriterionConversionAbstract
$oDate = $oFormat->parse($sEndDate);
$sEndDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} <= '$sEndDate')";
}
catch (Exception $e)
} catch (Exception $e)
{
}
}

View File

@@ -71,6 +71,8 @@ class CriterionToSearchForm extends CriterionConversionAbstract
AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'ExternalKeyToSearchForm',
AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY => 'ExternalKeyToSearchForm',
AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'EnumToSearchForm',
AttributeDefinition::SEARCH_WIDGET_TYPE_SET => 'SetToSearchForm',
AttributeDefinition::SEARCH_WIDGET_TYPE_TAG_SET => 'TagSetToSearchForm',
);
foreach($aAndCriterionRaw as $aCriteria)
@@ -666,6 +668,67 @@ class CriterionToSearchForm extends CriterionConversionAbstract
return $aCriteria;
}
protected static function TagSetToSearchForm($aCriteria, $aFields)
{
$sOperator = $aCriteria['operator'];
switch ($sOperator)
{
case 'MATCHES':
// Nothing special to do
if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined'])
{
if (!isset($aCriteria['values']))
{
$aCriteria['values'] = array();
}
// Convention for 'undefined' tag set
$aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined'));
}
break;
case 'OR':
case 'ISNULL':
$aCriteria['operator'] = CriterionConversionAbstract::OP_EQUALS;
if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined'])
{
if (!isset($aCriteria['values']))
{
$aCriteria['values'] = array();
}
// Convention for 'undefined' tag set
$aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined'));
}
break;
case '=':
// TODO BUG SPLIT INTO AN 'AND' LIST OF MATCHES
$aCriteria['operator'] = CriterionConversionAbstract::OP_EQUALS;
if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined'])
{
if (!isset($aCriteria['values']))
{
$aCriteria['values'] = array();
}
// Convention for 'undefined' tag set
$aCriteria['values'][] = array('value' => '', 'label' => Dict::S('Enum:Undefined'));
}
break;
default:
// Unknown operator
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
break;
}
return $aCriteria;
}
// TODO New widget based on String but with match
protected static function SetToSearchForm($aCriteria, $aFields)
{
$aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
return $aCriteria;
}
protected static function ExternalKeyToSearchForm($aCriteria, $aFields)
{
$sOperator = $aCriteria['operator'];

View File

@@ -37,6 +37,7 @@ abstract class CriterionConversionAbstract
const OP_BETWEEN = 'between';
const OP_REGEXP = 'REGEXP';
const OP_ALL = 'all';
const OP_MATCHES = 'MATCHES';
}

View File

@@ -46,7 +46,6 @@ use WebPage;
class SearchForm
{
/**
* @param \WebPage $oPage
* @param \CMDBObjectSet $oSet
@@ -55,6 +54,7 @@ class SearchForm
* @return string
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
{
@@ -136,7 +136,8 @@ class SearchForm
ksort($aOptions);
$sContext = $oAppContext->GetForLink();
$sJsonExtraParams = htmlentities(json_encode($aListParams), ENT_QUOTES);
$sClassesCombo = "<select name=\"class\" onChange=\"ReloadSearchForm('$sSearchFormId', this.value, '$sRootClass', '$sContext', '{$aExtraParams['result_list_outer_selector']}', $sJsonExtraParams)\">\n".implode('',
$sOuterSelector = $aExtraParams['result_list_outer_selector'];
$sClassesCombo = "<select name=\"class\" onChange=\"ReloadSearchForm('$sSearchFormId', this.value, '$sRootClass', '$sContext', '$sOuterSelector', $sJsonExtraParams)\">\n".implode('',
$aOptions)."</select>\n";
}
else
@@ -497,7 +498,7 @@ class SearchForm
{
try
{
$sOQL = $oExpression->Render($aArgs);
$sOQL = $oExpression->RenderExpression(false, $aArgs);
$oExpression = Expression::FromOQL($sOQL);
}
catch (MissingQueryArgument $e)
@@ -515,7 +516,7 @@ class SearchForm
foreach($aAndExpressions as $oAndSubExpr)
{
/** @var Expression $oAndSubExpr */
if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->Render() == 1))
if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->RenderExpression(false) == 1))
{
continue;
}
@@ -595,21 +596,22 @@ class SearchForm
return $aFields;
}
/**
* @param string $sClass
* @param string $sClassAlias
* @param string $sAttCode
* @param \AttributeDefinition $oAttDef
* @param array $aFields
* @param bool $bHasIndex
*
* @return mixed
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
*/
/**
* @param string $sClass
* @param string $sClassAlias
* @param string $sAttCode
* @param \AttributeDefinition $oAttDef
* @param array $aFields
* @param bool $bHasIndex
*
* @return mixed
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \Exception
*/
private function AppendField($sClass, $sClassAlias, $sAttCode, $oAttDef, $aFields, $bHasIndex = false)
{
if (!is_null($oAttDef) && ($oAttDef->GetSearchType() != AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))

View File

@@ -45,6 +45,8 @@ require_once APPROOT . 'sources/form/field/multipleselectfield.class.inc.php';
require_once APPROOT . 'sources/form/field/selectobjectfield.class.inc.php';
require_once APPROOT . 'sources/form/field/checkboxfield.class.inc.php';
require_once APPROOT . 'sources/form/field/radiofield.class.inc.php';
require_once APPROOT . 'sources/form/field/setfield.class.inc.php';
require_once APPROOT . 'sources/form/field/tagsetfield.class.inc.php';
require_once APPROOT . 'sources/form/field/linkedsetfield.class.inc.php';
require_once APPROOT . 'sources/form/validator/validator.class.inc.php';
require_once APPROOT . 'sources/form/validator/mandatoryvalidator.class.inc.php';
@@ -56,6 +58,7 @@ require_once APPROOT . 'sources/renderer/renderingoutput.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/bsformrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssimplefieldrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsselectobjectfieldrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssetfieldrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bslinkedsetfieldrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bssubformfieldrenderer.class.inc.php';
require_once APPROOT . 'sources/renderer/bootstrap/fieldrenderer/bsfileuploadfieldrenderer.class.inc.php';

View File

@@ -0,0 +1,30 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Form\Field;
/**
* Description of SetField
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class SetField extends Field
{
}

View File

@@ -0,0 +1,30 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Form\Field;
/**
* Description of TagSetField
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class TagSetField extends Field
{
}

View File

@@ -55,6 +55,8 @@ class BsFormRenderer extends FormRenderer
$this->AddSupportedField('SubFormField', 'BsSubFormFieldRenderer');
$this->AddSupportedField('SelectObjectField', 'BsSelectObjectFieldRenderer');
$this->AddSupportedField('LinkedSetField', 'BsLinkedSetFieldRenderer');
$this->AddSupportedField('SetField', 'BsSetFieldRenderer');
$this->AddSupportedField('TagSetField', 'BsSetFieldRenderer');
$this->AddSupportedField('DateTimeField', 'BsSimpleFieldRenderer');
$this->AddSupportedField('DurationField', 'BsSimpleFieldRenderer');
$this->AddSupportedField('FileUploadField', 'BsFileUploadFieldRenderer');

View File

@@ -0,0 +1,133 @@
<?php
// Copyright (C) 2010-2018 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Renderer\Bootstrap\FieldRenderer;
use MetaModel;
use Combodo\iTop\Renderer\FieldRenderer;
use Combodo\iTop\Renderer\RenderingOutput;
/**
* Description of BsSetFieldRenderer
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class BsSetFieldRenderer extends FieldRenderer
{
/**
* @inheritdoc
*/
public function Render()
{
$oOutput = new RenderingOutput();
$oOutput->AddCssClass('form_field_' . $this->oField->GetDisplayMode());
$sFieldMandatoryClass = ($this->oField->GetMandatory()) ? 'form_mandatory' : '';
// Vars to build the table
// $sAttributesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay());
// $sAttCodesToDisplayAsJson = json_encode($this->oField->GetAttributesToDisplay(true));
// $aItems = array();
// $aItemIds = array();
// $this->PrepareItems($aItems, $aItemIds);
// $sItemsAsJson = json_encode($aItems);
// $sItemIdsAsJson = htmlentities(json_encode(array('current' => $aItemIds)), ENT_QUOTES, 'UTF-8');
// Rendering field
if (!$this->oField->GetHidden())
{
/** @var \ormSet $oOrmItemSet */
$oOrmItemSet = $this->oField->GetCurrentValue();
// Opening container
$oOutput->AddHtml('<div class="form-group form_group_small ' . $sFieldMandatoryClass . '">');
// Label
$oOutput->AddHtml('<div class="form_field_label">');
if ($this->oField->GetLabel() !== '')
{
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">')
->AddHtml($this->oField->GetLabel(), true)
->AddHtml('</label>');
}
$oOutput->AddHtml('</div>');
// Value
$oOutput->AddHtml('<div class="form_field_control">');
// ... in edit mode
if(!$this->oField->GetReadOnly())
{
$oAttDef = MetaModel::GetAttributeDef($oOrmItemSet->GetClass(), $oOrmItemSet->GetAttCode());
$sJSONForWidget = $oAttDef->GetJsonForWidget($oOrmItemSet);
// - Help block
$oOutput->AddHtml('<div class="help-block"></div>');
// - Value regarding the field type
$oOutput->AddHtml('<input type="hidden" id="' . $this->oField->GetGlobalId() . '" name="' . $this->oField->GetId() . '" value="')
->AddHtml($sJSONForWidget, true)
->AddHtml('" class="form-control" />');
// Attaching JS widget only if field is hidden or NOT read only
// JS Form field widget construct
$aValidators = array();
$sValidators = json_encode($aValidators);
$oOutput->AddJs(
<<<EOF
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").portal_form_field_set({
validators: $sValidators,
// Overloading default callback as the Selectize widget adds several inputs and we want to retrieve only the one with the value.
get_current_value_callback: function(me, oEvent, oData){
var value = null;
// Retrieving JSON value as a string and not an object
value = me.element.find('#{$this->oField->GetGlobalId()}').val();
return value;
},
});
EOF
);
}
// ... in view mode
else
{
$aItems = $oOrmItemSet->GetTags();
$oOutput->AddHtml('<div class="form-control-static">')
->AddHtml('<span class="label-group">');
foreach($aItems as $sItemCode => $oItem)
{
$sItemLabel = $oItem->Get('label');
$sItemDescription = $oItem->Get('description');
$oOutput->AddHtml('<span class="label label-default" data-code="'.$sItemCode.'" data-label="')
->AddHtml($sItemLabel, true)
->AddHtml('" data-description="')
->AddHtml($sItemDescription, true)
->AddHtml('">')
->AddHtml($sItemLabel, true)
->AddHtml('</span>');
}
$oOutput->AddHtml('</span>')
->AddHtml('</div>');
}
$oOutput->AddHtml('</div>');
}
return $oOutput;
}
}

View File

@@ -288,7 +288,7 @@ EOF
{
// TODO
}
// ... clasic rendering for fields with only one value
// ... classic rendering for fields with only one value
else
{
switch ($sFieldClass)

View File

@@ -38,12 +38,16 @@ use lnkContactToFunctionalCI;
use MetaModel;
use Person;
use Server;
use TagSetFieldData;
use Ticket;
use URP_UserProfile;
use VirtualHost;
use VirtualMachine;
define('TAG_CLASS', 'Ticket');
define('TAG_ATTCODE', 'tagfield');
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
@@ -51,7 +55,11 @@ use VirtualMachine;
*/
class ItopDataTestCase extends ItopTestCase
{
protected $testOrgId;
private $iTestOrgId;
// For cleanup
private $aCreatedObjects = array();
const USE_TRANSACTION = true;
/**
* @throws Exception
@@ -68,11 +76,11 @@ class ItopDataTestCase extends ItopTestCase
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
CMDBSource::Query('START TRANSACTION');
// Create a specific organization for the tests
$oOrg = $this->CreateOrganization('UnitTestOrganization');
$this->testOrgId = $oOrg->GetKey();
if (static::USE_TRANSACTION)
{
CMDBSource::Query('START TRANSACTION');
}
$this->CreateTestOrganization();
}
/**
@@ -80,7 +88,30 @@ class ItopDataTestCase extends ItopTestCase
*/
protected function tearDown()
{
CMDBSource::Query('ROLLBACK');
if (static::USE_TRANSACTION)
{
$this->debug("ROLLBACK !!!");
CMDBSource::Query('ROLLBACK');
}
else
{
$this->debug("");
$this->aCreatedObjects = array_reverse($this->aCreatedObjects);
foreach($this->aCreatedObjects as $oObject)
{
/** @var DBObject $oObject */
try
{
$sClass = get_class($oObject);
$iKey = $oObject->GetKey();
$this->debug("Removing $sClass::$iKey");
$oObject->DBDelete();
} catch (Exception $e)
{
$this->debug($e->getMessage());
}
}
}
}
/**
@@ -88,7 +119,7 @@ class ItopDataTestCase extends ItopTestCase
*/
public function getTestOrgId()
{
return $this->testOrgId;
return $this->iTestOrgId;
}
/////////////////////////////////////////////////////////////////////////////
@@ -101,7 +132,7 @@ class ItopDataTestCase extends ItopTestCase
* @return DBObject
* @throws Exception
*/
protected static function createObject($sClass, $aParams)
protected function createObject($sClass, $aParams)
{
$oMyObj = MetaModel::NewObject($sClass);
foreach($aParams as $sAttCode => $oValue)
@@ -109,6 +140,9 @@ class ItopDataTestCase extends ItopTestCase
$oMyObj->Set($sAttCode, $oValue);
}
$oMyObj->DBInsert();
$iKey = $oMyObj->GetKey();
$this->debug("Created $sClass::$iKey");
$this->aCreatedObjects[] = $oMyObj;
return $oMyObj;
}
@@ -137,16 +171,16 @@ class ItopDataTestCase extends ItopTestCase
* Create an Organization in database
*
* @param string $sName
* @return Organization
* @return \Organization
* @throws Exception
*/
protected function CreateOrganization($sName)
{
/** @var Organization $oObj */
$oObj = self::createObject('Organization', array(
/** @var \Organization $oObj */
$oObj = $this->createObject('Organization', array(
'name' => $sName,
));
$this->debug("\nCreated Organization {$oObj->Get('name')}");
$this->debug("Created Organization {$oObj->Get('name')}");
return $oObj;
}
@@ -160,31 +194,95 @@ class ItopDataTestCase extends ItopTestCase
protected function CreateTicket($iNum)
{
/** @var Ticket $oTicket */
$oTicket = self::createObject('UserRequest', array(
$oTicket = $this->createObject('UserRequest', array(
'ref' => 'Ticket_'.$iNum,
'title' => 'BUG 789_'.$iNum,
'title' => 'TICKET_'.$iNum,
//'request_type' => 'incident',
'description' => 'method UpdateImpactedItems() reconstruit le lnkContactToTicket donc impossible de rajouter des champs dans cette classe',
'description' => 'Created for unit tests.',
'org_id' => $this->getTestOrgId(),
));
$this->debug("\nCreated {$oTicket->Get('title')} ({$oTicket->Get('ref')})");
$this->debug("Created {$oTicket->Get('title')} ({$oTicket->Get('ref')})");
return $oTicket;
}
protected function RemoveTicket($iNum)
{
$this->RemoveObjects('UserRequest', "SELECT UserRequest WHERE ref = 'Ticket_$iNum'");
}
/**
* Create a Ticket in database
*
* @param string $sClass
* @param string $sAttCode
* @param string $sTagCode
* @param string $sTagLabel
* @param string $sTagDescription
*
* @return \TagSetFieldData
* @throws \CoreException
*/
protected function CreateTagData($sClass, $sAttCode, $sTagCode, $sTagLabel, $sTagDescription = '')
{
$sTagClass = TagSetFieldData::GetTagDataClassName($sClass, $sAttCode);
$oTagData = $this->createObject($sTagClass, array(
'code' => $sTagCode,
'label' => $sTagLabel,
'obj_class' => $sClass,
'obj_attcode' => $sAttCode,
'description' => $sTagDescription,
));
$this->debug("Created {$oTagData->Get('code')} ({$oTagData->Get('label')})");
/** @var \TagSetFieldData $oTagData */
return $oTagData;
}
/**
* Create a Ticket in database
*
* @param string $sClass
* @param string $sAttCode
* @param string $sTagCode
*
* @throws \CoreException
*/
protected function RemoveTagData($sClass, $sAttCode, $sTagCode)
{
$sTagClass = TagSetFieldData::GetTagDataClassName($sClass, $sAttCode);
$this->RemoveObjects($sTagClass, "SELECT $sTagClass WHERE code = '$sTagCode'");
}
private function RemoveObjects($sClass, $sOQL)
{
$oFilter = \DBSearch::FromOQL($sOQL);
$aRes = $oFilter->ToDataArray(array('id'));
foreach($aRes as $aRow)
{
$this->debug($aRow);
$iKey = $aRow['id'];
if (!empty($iKey))
{
$oObject = MetaModel::GetObject($sClass, $iKey);
$oObject->DBDelete();
}
}
}
/**
* Create a UserRequest in database
*
* @param int $iNum
* @param int $iTimeSpent
* @param int $iOrgId
* @param int $iCallerId
* @return UserRequest
* @return \UserRequest
* @throws Exception
*/
protected function CreateUserRequest($iNum, $iTimeSpent = 0, $iOrgId = 0, $iCallerId = 0)
{
/** @var UserRequest $oTicket */
$oTicket = self::createObject('UserRequest', array(
/** @var \UserRequest $oTicket */
$oTicket = $this->createObject('UserRequest', array(
'ref' => 'Ticket_'.$iNum,
'title' => 'BUG 1161_'.$iNum,
//'request_type' => 'incident',
@@ -193,7 +291,7 @@ class ItopDataTestCase extends ItopTestCase
'caller_id' => $iCallerId,
'org_id' => ($iOrgId == 0 ? $this->getTestOrgId() : $iOrgId),
));
$this->debug("\nCreated {$oTicket->Get('title')} ({$oTicket->Get('ref')})");
$this->debug("Created {$oTicket->Get('title')} ({$oTicket->Get('ref')})");
return $oTicket;
}
@@ -209,7 +307,7 @@ class ItopDataTestCase extends ItopTestCase
protected function CreateServer($iNum, $iRackUnit = null)
{
/** @var Server $oServer */
$oServer = self::createObject('Server', array(
$oServer = $this->createObject('Server', array(
'name' => 'Server_'.$iNum,
'org_id' => $this->getTestOrgId(),
'nb_u' => $iRackUnit,
@@ -228,7 +326,7 @@ class ItopDataTestCase extends ItopTestCase
*/
protected function CreatePhysicalInterface($iNum, $iSpeed, $iConnectableCiId)
{
$oObj = self::createObject('PhysicalInterface', array(
$oObj = $this->createObject('PhysicalInterface', array(
'name' => "$iNum",
'speed' => $iSpeed,
'connectableci_id' => $iConnectableCiId,
@@ -247,7 +345,7 @@ class ItopDataTestCase extends ItopTestCase
*/
protected function CreateFiberChannelInterface($iNum, $iSpeed, $iConnectableCiId)
{
$oObj = self::createObject('FiberChannelInterface', array(
$oObj = $this->createObject('FiberChannelInterface', array(
'name' => "$iNum",
'speed' => $iSpeed,
'datacenterdevice_id' => $iConnectableCiId,
@@ -266,7 +364,7 @@ class ItopDataTestCase extends ItopTestCase
protected function CreatePerson($iNum, $iOrgId = 0)
{
/** @var Person $oPerson */
$oPerson = self::createObject('Person', array(
$oPerson = $this->createObject('Person', array(
'name' => 'Person_'.$iNum,
'first_name' => 'Test',
'org_id' => ($iOrgId == 0 ? $this->getTestOrgId() : $iOrgId),
@@ -287,7 +385,7 @@ class ItopDataTestCase extends ItopTestCase
$oUserProfile->Set('profileid', $iProfileId);
$oUserProfile->Set('reason', 'UNIT Tests');
$oSet = DBObjectSet::FromObject($oUserProfile);
$oUser = self::createObject('UserLocal', array(
$oUser = $this->createObject('UserLocal', array(
'contactid' => 2,
'login' => $sLogin,
'password' => $sLogin,
@@ -310,7 +408,7 @@ class ItopDataTestCase extends ItopTestCase
protected function CreateHypervisor($iNum, $oServer, $oFarm = null)
{
/** @var Hypervisor $oHypervisor */
$oHypervisor = self::createObject('Hypervisor', array(
$oHypervisor = $this->createObject('Hypervisor', array(
'name' => 'Hypervisor_'.$iNum,
'org_id' => $this->getTestOrgId(),
'server_id' => $oServer->GetKey(),
@@ -337,7 +435,7 @@ class ItopDataTestCase extends ItopTestCase
protected function CreateFarm($iNum, $sRedundancy = '1')
{
/** @var Farm $oFarm */
$oFarm = self::createObject('Farm', array(
$oFarm = $this->createObject('Farm', array(
'name' => 'Farm_'.$iNum,
'org_id' => $this->getTestOrgId(),
'redundancy' => $sRedundancy,
@@ -356,7 +454,7 @@ class ItopDataTestCase extends ItopTestCase
protected function CreateVirtualMachine($iNum, $oVirtualHost)
{
/** @var VirtualMachine $oVirtualMachine */
$oVirtualMachine = self::createObject('VirtualMachine', array(
$oVirtualMachine = $this->createObject('VirtualMachine', array(
'name' => 'VirtualMachine_'.$iNum,
'org_id' => $this->getTestOrgId(),
'virtualhost_id' => $oVirtualHost->GetKey(),
@@ -509,13 +607,15 @@ class ItopDataTestCase extends ItopTestCase
}
}
/**
* Reload a Ticket from the database.
*
* @param DBObject $oObject
* @throws ArchivedObjectException
* @throws Exception
*/
/**
* Reload a Ticket from the database.
*
* @param DBObject $oObject
*
* @return \DBObject|null
* @throws ArchivedObjectException
* @throws Exception
*/
protected function ReloadObject(&$oObject)
{
$oObject = MetaModel::GetObject(get_class($oObject), $oObject->GetKey());
@@ -573,5 +673,12 @@ class ItopDataTestCase extends ItopTestCase
}
}
protected function CreateTestOrganization()
{
// Create a specific organization for the tests
$oOrg = $this->CreateOrganization('UnitTestOrganization');
$this->iTestOrgId = $oOrg->GetKey();
}
}

View File

@@ -173,6 +173,7 @@ class CriterionConversionTest extends ItopDataTestCase
* @param $aCriterion
* @param $sExpectedOperator
*
* @throws \CoreException
* @throws \OQLException
*/
function testToSearchForm($aCriterion, $sExpectedOperator)
@@ -311,22 +312,27 @@ class CriterionConversionTest extends ItopDataTestCase
);
}
/**
* @dataProvider OqlProvider
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
*/
function testOqlToForSearchToOql($sOQL, $sExpectedOQL, $aExpectedCriterion)
/**
* @dataProvider OqlProvider
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
* @throws \CoreException
*/
function testOqlToSearchToOql($sOQL, $sExpectedOQL, $aExpectedCriterion)
{
$this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US");
// For tests on tags
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second');
$this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US");
}
function OqlProvider()
@@ -452,46 +458,67 @@ class CriterionConversionTest extends ItopDataTestCase
'ExpectedOQL' => "SELECT `dev` FROM DatacenterDevice AS `dev` WHERE ((INET_ATON(`dev`.`managementip`) < INET_ATON('10.22.32.255')) AND (INET_ATON(`dev`.`managementip`) > INET_ATON('10.22.32.224')))",
'ExpectedCriterion' => array(array('widget' => 'raw')),
),
'TagSet Matches' => array(
'OQL' => "SELECT UserRequest WHERE tagfield MATCHES 'tag1'",
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.`tagfield` MATCHES 'tag1'",
'ExpectedCriterion' => array(array('widget' => 'tag_set')),
),
'TagSet Matches2' => array(
'OQL' => "SELECT UserRequest WHERE tagfield MATCHES 'tag1 tag2'",
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE `UserRequest`.`tagfield` MATCHES 'tag1 tag2'",
'ExpectedCriterion' => array(array('widget' => 'tag_set')),
),
'TagSet Undefined' => array(
'OQL' => "SELECT UserRequest WHERE tagfield = ''",
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE (`UserRequest`.`tagfield` = '')",
'ExpectedCriterion' => array(array('widget' => 'tag_set')),
),
'TagSet Undefined and tag' => array(
'OQL' => "SELECT UserRequest WHERE (((tagfield MATCHES 'tag1 tag2') OR (tagfield = '')) AND 1)",
'ExpectedOQL' => "SELECT `UserRequest` FROM UserRequest AS `UserRequest` WHERE ((`UserRequest`.`tagfield` MATCHES 'tag1 tag2' OR (`UserRequest`.`tagfield` = '')) AND 1)",
'ExpectedCriterion' => array(array('widget' => 'tag_set')),
),
);
}
/**
* @dataProvider OqlProviderDates
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
*/
/**
* @dataProvider OqlProviderDates
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
* @throws \CoreException
*/
function testOqlToForSearchToOqlAltLanguageFR($sOQL, $sExpectedOQL, $aExpectedCriterion)
{
$this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "FR FR");
$this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "FR FR");
}
/**
* @dataProvider OqlProviderDates
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
*/
/**
* @dataProvider OqlProviderDates
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
* @throws \CoreException
*/
function testOqlToForSearchToOqlAltLanguageEN($sOQL, $sExpectedOQL, $aExpectedCriterion)
{
$this->OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US");
$this->OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, "EN US");
}
@@ -572,21 +599,22 @@ class CriterionConversionTest extends ItopDataTestCase
);
}
/**
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @param $sLanguageCode
*
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
*/
function OqlToForSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, $sLanguageCode )
/**
*
* @param $sOQL
*
* @param $sExpectedOQL
*
* @param $aExpectedCriterion
*
* @param $sLanguageCode
*
* @throws \CoreException
* @throws \DictExceptionUnknownLanguage
* @throws \MissingQueryArgument
* @throws \OQLException
*/
function OqlToSearchToOqlAltLanguage($sOQL, $sExpectedOQL, $aExpectedCriterion, $sLanguageCode )
{
$this->debug($sOQL);

View File

@@ -43,6 +43,7 @@ class SearchFormTest extends ItopDataTestCase
/**
* @dataProvider GetFieldsProvider
* @throws \OQLException
* @throws \CoreException
*/
public function testGetFields($sOQL)
{
@@ -74,6 +75,8 @@ class SearchFormTest extends ItopDataTestCase
* @param $sOQL
* @param $iOrCount
*
* @throws \CoreException
* @throws \MissingQueryArgument
*/
public function testGetCriterion($sOQL, $iOrCount)
{

View File

@@ -0,0 +1,109 @@
<!--
~ Copyright (c) 2010-2018 Combodo SARL
~
~ This file is part of iTop.
~
~ iTop is free software; you can redistribute it and/or modify
~ it under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ iTop is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU Affero General Public License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with iTop. If not, see <http://www.gnu.org/licenses/>
~
-->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>AttributeSet fields widget test</title>
<script>
var aDictEntries = {"Core:AttributeSet:placeholder": "click to add"};
</script>
<script src="../js/utils.js"></script>
<script src="../js/jquery-3.3.1.min.js"></script>
<script src="../js/jquery-ui-1.11.4.custom.min.js"></script>
<script src="../js/selectize.min.js"></script>
<script src="../js/jquery.itop-set-widget.js"></script>
<link rel="stylesheet" type="text/css" href="../css/light-grey.css">
<link rel="stylesheet" type="text/css" href="../css/selectize.default.css">
</head>
<body>
<h1>POC Set widget (itop.set_widget) et CSS</h1>
<h2>Edition : widget</h2>
<form>
<div class="field_container">
<div>
<div class="field_value">
<div class="attribute-edit">
<div class="field_input_zone field_input_set">
<textarea id="tagset-field" rows="30" cols="60">
{
"possible_values": [
{
"code": "critical",
"label": "Critical ticket"
},
{
"code": "high",
"label": "don't forget it !"
},
{
"code": "normal",
"label": "when time available"
},
{
"code": "low",
"label": "don't worry ;)"
}
],
"max_items_allowed": 3,
"partial_values": [
"low"
],
"orig_value": [
"critical",
"low"
],
"added": "",
"removed": ["toto"]
}
</textarea>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
$("#tagset-field").set_widget({isDebug: true});
</script>
<h2>Visualisation</h2>
<p>
<span class="attribute-set">
<a href="" class="attribute-set-item attribute-set-item-city" data-code="city" data-label="Ville 🏢" data-description="">Ville 🏢</a>
<a href="" class="attribute-set-item attribute-set-item-ocean" data-code="ocean" data-label="Mer 🌊" data-description="">Mer 🌊</a>
<a href="" class="attribute-set-item attribute-set-item-sunny" data-code="sunny" data-label="Soleil 🌞" data-description="">Soleil 🌞</a>
</span>
</p>
</body>
</html>

View File

@@ -0,0 +1,93 @@
<?php
/**
* Created by PhpStorm.
* User: Eric
* Date: 17/09/2018
* Time: 12:31
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBSearch;
/**
* Tests of the DBSearch class.
* <ul>
* <li>MakeGroupByQuery</li>
* </ul>
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class DBSearchCommitTest extends ItopDataTestCase
{
// Need database COMMIT in order to create the FULLTEXT INDEX of MySQL
const USE_TRANSACTION = false;
/**
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \Exception
*/
public function testAttributeTagSet()
{
// Create a tag
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'UNIT First');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'UNIT Second');
//Use it
$oTicket = $this->CreateTicket(1);
$oTicket->Set(TAG_ATTCODE, 'tag1');
$oTicket->DBWrite();
$oSearch = DBSearch::FromOQL("SELECT UserRequest");
$oSearch->AddCondition(TAG_ATTCODE, 'tag1', 'MATCHES');
$oSet = new \DBObjectSet($oSearch);
static::assertEquals(1, $oSet->Count());
$oTicket->Set(TAG_ATTCODE, 'tag1 tag2');
$oTicket->DBWrite();
$oSet = new \DBObjectSet($oSearch);
static::assertEquals(1, $oSet->Count());
}
/**
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @throws \Exception
*/
public function testAttributeTagSet2()
{
// Create a tag
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'UNIT First');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'UNIT Second');
//Use it
$oTicket = $this->CreateTicket(1);
$oTicket->Set(TAG_ATTCODE, 'tag1');
$oTicket->DBWrite();
$oSearch = DBSearch::FromOQL("SELECT UserRequest");
$oSearch->AddCondition(TAG_ATTCODE, 'tag1');
$oSet = new \DBObjectSet($oSearch);
static::assertEquals(1, $oSet->Count());
$oTicket->Set(TAG_ATTCODE, 'tag1 tag2');
$oTicket->DBWrite();
$oSet = new \DBObjectSet($oSearch);
static::assertEquals(0, $oSet->Count());
}
}

269
test/core/OQLTest.php Normal file
View File

@@ -0,0 +1,269 @@
<?php
/**
* Created by PhpStorm.
* User: Eric
* Date: 31/08/2018
* Time: 17:03
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class OQLTest extends ItopDataTestCase
{
/**
* @dataProvider GoodQueryProvider
*
* @param $sQuery
*
* @throws \OQLException
*/
public function testGoodQueryParser($sQuery)
{
$this->debug($sQuery);
$oOql = new \OqlInterpreter($sQuery);
$oQuery = $oOql->ParseQuery();
static::assertInstanceOf('OqlQuery', $oQuery);
}
public function GoodQueryProvider()
{
return array(
array('SELECT toto'),
array('SELECT toto WHERE toto.a = 1'),
array('SELECT toto WHERE toto.a = -1'),
array('SELECT toto WHERE toto.a = (1-1)'),
array('SELECT toto WHERE toto.a = (-1+3)'),
array('SELECT toto WHERE toto.a = (3+-1)'),
array('SELECT toto WHERE toto.a = (3--1)'),
array('SELECT toto WHERE toto.a = 0xC'),
array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''),
array('SELECT toto WHERE toto.a = :myparameter'),
array('SELECT toto WHERE toto.a IN (:param1)'),
array('SELECT toto WHERE toto.a IN (:param1, :param2)'),
array('SELECT toto WHERE toto.a=1'),
array('SELECT toto WHERE toto.a = "1"'),
array('SELECT toto WHERE toto.a & 1'),
array('SELECT toto WHERE toto.a | 1'),
array('SELECT toto WHERE toto.a ^ 1'),
array('SELECT toto WHERE toto.a << 1'),
array('SELECT toto WHERE toto.a >> 1'),
array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'),
array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'),
array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''),
array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''),
array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'),
array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''),
array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'),
array('SELECT toto WHERE toto.a NOT LIKE "\\""'),
array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'),
array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'),
array('SELECT toto WHERE toto.a NOT LIKE ""'),
array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'),
array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'),
array('SELECT Device JOIN Site ON Device.site = Site.id'),
array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'),
array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''),
array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''),
array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'),
array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'),
array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'),
array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'),
array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'),
array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'),
array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'),
array('SELECT A FROM A'),
array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'),
array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'),
array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'),
array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'),
array('SELECT A UNION SELECT B'),
array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'),
array('SELECT A UNION SELECT B UNION SELECT C'),
array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'),
array('SELECT Person AS B WHERE B.name LIKE \'%A%\''),
array('SELECT Server WHERE name REGEXP \'dbserver[0-9]+\''),
array('SELECT Server WHERE name REGEXP \'^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$\''),
array('SELECT Change AS ch WHERE ch.start_date >= \'2009-12-31\' AND ch.end_date <= \'2010-01-01\''),
array('SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON(\'10.22.32.224\') AND INET_ATON(dev.managementip) < INET_ATON(\'10.22.32.255\')'),
array('SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1'),
array('SELECT PhysicalInterface AS if JOIN DatacenterDevice AS dev ON if.connectableci_id = dev.id WHERE dev.status = \'production\' AND dev.organization_name = \'Demo\''),
array('SELECT Ticket AS t WHERE t.agent_id = :current_contact_id'),
array('SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != \'closed\''),
array('SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)'),
array('SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = \'new\''),
array('SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)'),
array('SELECT Ticket WHERE tagfield MATCHES \'salad\''),
);
}
/**
* @dataProvider BadQueryProvider
*
* @param $sQuery
* @param $sExpectedExceptionClass
*
*/
public function testBadQueryParser($sQuery, $sExpectedExceptionClass)
{
$this->debug($sQuery);
$oOql = new \OqlInterpreter($sQuery);
$sExceptionClass = '';
try
{
$oOql->ParseQuery();
}
catch (\Exception $e)
{
$sExceptionClass = get_class($e);
}
static::assertEquals($sExpectedExceptionClass, $sExceptionClass);
}
public function BadQueryProvider()
{
return array(
array('SELECT toto WHERE toto.a = (3++1)', 'OQLParserException'),
array('SELECT toto WHHHERE toto.a = "1"', 'OQLParserException'),
array('SELECT toto WHERE toto.a == "1"', 'OQLParserException'),
array('SELECT toto WHERE toto.a % 1', 'Exception'),
array('SELECT toto WHERE toto.a like \'arg\'', 'OQLParserException'),
array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""', 'OQLParserException'),
array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'', 'OQLParserException'),
array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"', 'Exception'),
array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'', 'Exception'),
array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3', 'OQLParserException'),
);
}
/**
* @dataProvider TypeErrorQueryProvider
*
* @param $sQuery
*
* @expectedException \TypeError
*
* @throws \OQLException
*/
public function testTypeErrorQueryParser($sQuery)
{
$this->debug($sQuery);
$oOql = new \OqlInterpreter($sQuery);
$oOql->ParseQuery();
}
public function TypeErrorQueryProvider()
{
return array(
array('SELECT A WHERE A.a MATCHES toto'),
);
}
/**
* Needs actual datamodel
*
* @dataProvider QueryNormalizationProvider
*
* @param $sQuery
* @param $sExpectedExceptionClass
*
*/
public function testQueryNormalization($sQuery, $sExpectedExceptionClass)
{
$this->debug($sQuery);
$sExceptionClass = '';
try
{
$oSearch = \DBObjectSearch::FromOQL($sQuery);
static::assertInstanceOf('DBObjectSearch', $oSearch);
}
catch (\Exception $e)
{
$sExceptionClass = get_class($e);
}
static::assertEquals($sExpectedExceptionClass, $sExceptionClass);
}
public function QueryNormalizationProvider()
{
return array(
array('SELECT Contact', ''),
array('SELECT Contact WHERE nom_de_famille = "foo"', 'OqlNormalizeException'),
array('SELECT Contact AS c WHERE name = "foo"', ''),
array('SELECT Contact AS c WHERE nom_de_famille = "foo"', 'OqlNormalizeException'),
array('SELECT Contact AS c WHERE c.name = "foo"', ''),
array('SELECT Contact AS c WHERE Contact.name = "foo"', 'OqlNormalizeException'),
array('SELECT Contact AS c WHERE x.name = "foo"', 'OqlNormalizeException'),
array('SELECT Organization AS child JOIN Organization AS root ON child.parent_id BELOW root.id', ''),
array('SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id', ''),
array('SELECT RelationProfessionnelle', 'UnknownClassOqlException'),
array('SELECT RelationProfessionnelle AS c WHERE name = "foo"', 'UnknownClassOqlException'),
// The first query is the base query altered only in one place in the subsequent queries
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', ''),
array('SELECT Person AS p JOIN lnkXXXXXXXXXXXX AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', 'UnknownClassOqlException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.role = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.team_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id BELOW p.id WHERE p.name LIKE "bar"', ''),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.org_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), // inverted the JOIN spec
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE name LIKE "foo"', ''),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE x.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.eman LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE eman LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE id = 1', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', ''),
array('SELECT Person AS p JOIN Organization AS o ON p.location_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN Organization AS o ON p.name = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id JOIN Person AS p ON p.org_id = o.id', 'OqlNormalizeException'),
array('SELECT Person JOIN Organization AS o ON Person.org_id = o.id JOIN Person ON Person.org_id = o.id', 'OqlNormalizeException'),
array('SELECT Person AS p JOIN Location AS l ON p.location_id = l.id', ''),
array('SELECT Person AS p JOIN Location AS l ON p.location_id BELOW l.id', 'OqlNormalizeException'),
array('SELECT Person FROM Person JOIN Location ON Person.location_id = Location.id', ''),
array('SELECT p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''),
array('SELECT l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''),
array('SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''),
array('SELECT p, l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''),
array('SELECT foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'),
array('SELECT p, foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'),
// Joins based on AttributeObjectKey
//
array('SELECT Attachment AS a JOIN UserRequest AS r ON a.item_id = r.id', ''),
array('SELECT UserRequest AS r JOIN Attachment AS a ON a.item_id = r.id', ''),
);
}
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* Created by PhpStorm.
* User: Eric
* Date: 10/09/2018
* Time: 11:02
*/
namespace Combodo\iTop\Test\UnitTest\Core;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use TagSetFieldData;
class TagSetFieldDataTest extends ItopDataTestCase
{
// Need database COMMIT in order to create the FULLTEXT INDEX of MySQL
const USE_TRANSACTION = false;
/**
* @throws \CoreException
*/
public function testGetAllowedValues()
{
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iInitialCount = count($aAllowedValues);
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(1, $iCurrCount - $iInitialCount);
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second');
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(2, $iCurrCount - $iInitialCount);
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag3', 'Third');
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(3, $iCurrCount - $iInitialCount);
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth');
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(4, $iCurrCount - $iInitialCount);
}
/**
* @throws \CoreException
*/
public function testDoCheckToWrite()
{
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iInitialCount = count($aAllowedValues);
$this->debug("Currently $iInitialCount tags defined");
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag2', 'Second');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag3', 'Third');
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth');
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(4, $iCurrCount - $iInitialCount);
try
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'Fourth');
} catch (\CoreException $e)
{
$this->debug($e->getMessage());
}
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(4, $iCurrCount - $iInitialCount);
try
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag4', 'zembrek');
} catch (\CoreException $e)
{
$this->debug($e->getMessage());
}
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(4, $iCurrCount - $iInitialCount);
try
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'zembrek', 'Fourth');
} catch (\CoreException $e)
{
$this->debug($e->getMessage());
}
$aAllowedValues = TagSetFieldData::GetAllowedValues(TAG_CLASS, TAG_ATTCODE);
$iCurrCount = count($aAllowedValues);
static::assertEquals(4, $iCurrCount - $iInitialCount);
}
/**
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function testDoCheckToDelete()
{
$oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$oTagData->DBDelete();
// Create a tag
$oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
//Use it
$oTicket = $this->CreateTicket(1);
$oTicket->Set(TAG_ATTCODE, 'tag1');
$oTicket->DBWrite();
// Try to delete the tag, must complain !
try
{
$oTagData->DBDelete();
} catch (\DeleteException $e)
{
static::assertTrue(true);
return;
}
// Should not pass here
static::assertFalse(true);
}
/**
* @throws \CoreException
* @throws \Exception
*/
public function testComputeValues()
{
$sTagClass = TagSetFieldData::GetTagDataClassName(TAG_CLASS, TAG_ATTCODE);
$oTagData = $this->createObject($sTagClass, array(
'code' => 'tag1',
'label' => 'First',
));
$this->debug("Created {$oTagData->Get('obj_class')}::{$oTagData->Get('obj_attcode')}");
static::assertEquals(TAG_CLASS, $oTagData->Get('obj_class'));
static::assertEquals(TAG_ATTCODE, $oTagData->Get('obj_attcode'));
}
/**
* Test invalid tag codes
* @dataProvider InvalidTagCodeProvider
*
* @expectedException \CoreException
*
* @param string $sTagCode
*
* @throws \CoreException
*/
public function testInvalidTagCode($sTagCode)
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, 'First');
// Should not pass here
static::assertFalse(true);
}
public function InvalidTagCodeProvider()
{
return array(
'No space' => array('tag1 1'),
'No _' => array('tag_1'),
'No -' => array('tag-1'),
'No %' => array('tag%1'),
'At least 3 chars' => array(''),
'At least 3 chars 1' => array('a'),
'At least 3 chars 2' => array('ab'),
'No #' => array('#tag'),
'No !' => array('tag!'),
);
}
/**
* Test invalid tag labels
* @expectedException \CoreException
* @throws \CoreException
*/
public function testInvalidTagLabel()
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First|Second');
// Should not pass here
static::assertFalse(true);
}
/**
* Test that tag code cannot be modified if used
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function testUpdateCode()
{
$oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$oTagData->Set('code', 'tag2');
$oTagData->DBWrite();
//Use it
$oTicket = $this->CreateTicket(1);
$oTicket->Set(TAG_ATTCODE, 'tag2');
$oTicket->DBWrite();
// Try to change the code of the tag, must complain !
try
{
$oTagData->Set('code', 'tag1');
$oTagData->DBWrite();
} catch (\CoreException $e)
{
static::assertTrue(true);
return;
}
// Should not pass here
static::assertFalse(true);
}
/**
* Check that the code length is correctly checked
*
* @throws \CoreException
*/
public function testMaxTagCodeLength()
{
/** @var \AttributeTagSet $oAttdef */
$oAttdef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE);
$iMaxLength = $oAttdef->GetTagCodeMaxLength();
$sTagCode = str_repeat('a', $iMaxLength);
// Should work
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode);
// Too long
$sTagCode = str_repeat('a', $iMaxLength + 1);
try
{
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode);
} catch (\CoreException $e)
{
$this->debug('Awaited: '.$e->getMessage());
static::assertTrue(true);
return;
}
// Failed
static::assertFalse(true);
}
public function testMaxTagsAllowed()
{
/** @var \AttributeTagSet $oAttDef */
$oAttDef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE);
$iMaxTags = $oAttDef->GetMaxItems();
for ($i = 0; $i < $iMaxTags; $i++)
{
$sTagCode = 'MaxTag'.$i;
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode);
}
$oTicket = $this->CreateTicket(1);
$this->debug("Max number of tags is $iMaxTags");
$sValue = '';
for ($i = 0; $i < ($iMaxTags + 1); $i++)
{
try
{
$sTagCode = 'MaxTag'.$i;
$sValue .= "$sTagCode ";
$oTicket->Set(TAG_ATTCODE, $sValue);
$oTicket->DBWrite();
} catch (\Exception $e)
{
// Should fail on the last iteration
static::assertEquals($iMaxTags, $i);
$this->debug("Setting (".($i+1).") tag(s) failed");
return;
}
$this->debug("Setting (".($i+1).") tag(s) worked");
}
static::assertFalse(true);
}
}

Some files were not shown because too many files have changed in this diff Show More