\n";
foreach($aFields as $aAttrib)
{
$sDataAttCode = isset($aAttrib['attcode']) ? "data-attcode=\"{$aAttrib['attcode']}\"" : '';
@@ -501,6 +532,9 @@ class WebPage implements Page
echo "
";
echo "
".htmlentities($this->s_title, ENT_QUOTES, 'UTF-8')."\n";
echo $this->get_base_tag();
+
+ $this->output_dict_entries();
+
foreach($this->a_linked_scripts as $s_script)
{
// Make sure that the URL to the script contains the application's version number
@@ -524,7 +558,6 @@ class WebPage implements Page
}
echo "\n";
}
- $this->output_dict_entries();
foreach($this->a_linked_stylesheets as $a_stylesheet)
{
if (strpos($a_stylesheet['link'], '?') === false)
@@ -778,36 +811,21 @@ class WebPage implements Page
protected function output_dict_entries($bReturnOutput = false)
{
- $sHtml = '';
- if (count($this->a_dict_entries)>0)
+ if ((count($this->a_dict_entries) > 0) || (count($this->a_dict_entries_prefixes) > 0))
{
- $sHtml .= "\n";
- }
-
- if ($bReturnOutput)
- {
- return $sHtml;
- }
- else
- {
- echo $sHtml;
}
}
}
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 3218e4e9d..5b84748eb 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -111,6 +111,18 @@ define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/re
*/
abstract class AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE_RAW = 'raw';
+ const SEARCH_WIDGET_TYPE_STRING = 'string';
+ const SEARCH_WIDGET_TYPE_NUMERIC = 'numeric';
+ const SEARCH_WIDGET_TYPE_ENUM = 'enum';
+ const SEARCH_WIDGET_TYPE_EXTERNAL_KEY = 'external_key';
+ const SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY = 'hierarchical_key';
+ const SEARCH_WIDGET_TYPE_EXTERNAL_FIELD = 'external_field';
+ const SEARCH_WIDGET_TYPE_DATE_TIME = 'date_time';
+ const SEARCH_WIDGET_TYPE_DATE = 'date';
+
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function GetType()
{
return Dict::S('Core:'.get_class($this));
@@ -122,6 +134,24 @@ abstract class AttributeDefinition
abstract public function GetEditClass();
+ /**
+ * Return the search widget type corresponding to this attribute
+ *
+ * @return string
+ */
+ public function GetSearchType()
+ {
+ return static::SEARCH_WIDGET_TYPE;
+ }
+
+ /**
+ * @return bool
+ */
+ public function IsSearchable()
+ {
+ return static::SEARCH_WIDGET_TYPE != static::SEARCH_WIDGET_TYPE_RAW;
+ }
+
protected $m_sCode;
private $m_aParams = array();
protected $m_sHostClass = '!undefined!';
@@ -1617,6 +1647,8 @@ class AttributeDBField extends AttributeDBFieldVoid
*/
class AttributeInteger extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -1710,6 +1742,8 @@ class AttributeInteger extends AttributeDBField
*/
class AttributeObjectKey extends AttributeDBFieldVoid
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('class_attcode', 'is_null_allowed'));
@@ -1765,6 +1799,8 @@ class AttributeObjectKey extends AttributeDBFieldVoid
*/
class AttributePercentage extends AttributeInteger
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
+
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
$iWidth = 5; // Total width of the percentage bar graph, in em...
@@ -1804,6 +1840,8 @@ class AttributePercentage extends AttributeInteger
*/
class AttributeDecimal extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('digits', 'decimals' /* including precision */));
@@ -1902,6 +1940,8 @@ class AttributeDecimal extends AttributeDBField
*/
class AttributeBoolean extends AttributeInteger
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -2098,6 +2138,8 @@ class AttributeBoolean extends AttributeInteger
*/
class AttributeString extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -2248,6 +2290,8 @@ class AttributeString extends AttributeDBField
*/
class AttributeClass extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("class_category", "more_values"));
@@ -2300,6 +2344,8 @@ class AttributeClass extends AttributeString
*/
class AttributeApplicationLanguage extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -2336,6 +2382,8 @@ class AttributeApplicationLanguage extends AttributeString
*/
class AttributeFinalClass extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function __construct($sCode, $aParams)
{
$this->m_sCode = $sCode;
@@ -2490,6 +2538,8 @@ class AttributeFinalClass extends AttributeString
*/
class AttributePassword extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -2542,6 +2592,8 @@ class AttributePassword extends AttributeString
*/
class AttributeEncryptedString extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static $sKey = null; // Encryption key used for all encrypted fields
public function __construct($sCode, $aParams)
@@ -2984,6 +3036,8 @@ class AttributeLongText extends AttributeText
*/
class AttributeCaseLog extends AttributeLongText
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function GetNullValue()
{
return '';
@@ -3403,6 +3457,8 @@ class AttributeIPAddress extends AttributeString
*/
class AttributeOQL extends AttributeText
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function GetEditClass() {return "OQLExpression";}
}
@@ -3413,6 +3469,7 @@ class AttributeOQL extends AttributeText
*/
class AttributeTemplateString extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
}
/**
@@ -3422,6 +3479,7 @@ class AttributeTemplateString extends AttributeString
*/
class AttributeTemplateText extends AttributeText
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
}
/**
@@ -3431,6 +3489,8 @@ class AttributeTemplateText extends AttributeText
*/
class AttributeTemplateHTML extends AttributeText
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function GetSQLColumns($bFullSpec = false)
{
$aColumns = array();
@@ -3465,6 +3525,8 @@ class AttributeTemplateHTML extends AttributeText
*/
class AttributeEnum extends AttributeString
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM;
+
static public function ListExpectedParams()
{
return parent::ListExpectedParams();
@@ -3892,6 +3954,8 @@ class AttributeMetaEnum extends AttributeEnum
*/
class AttributeDateTime extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE_TIME;
+
static $oFormat = null;
static public function GetFormat()
@@ -4418,6 +4482,8 @@ class AttributeDuration extends AttributeInteger
*/
class AttributeDate extends AttributeDateTime
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE;
+
static $oDateFormat = null;
static public function GetFormat()
@@ -4562,6 +4628,8 @@ class AttributeDeadline extends AttributeDateTime
*/
class AttributeExternalKey extends AttributeDBFieldVoid
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete"));
@@ -4767,6 +4835,8 @@ class AttributeExternalKey extends AttributeDBFieldVoid
*/
class AttributeHierarchicalKey extends AttributeExternalKey
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
+
protected $m_sTargetClass;
static public function ListExpectedParams()
@@ -4914,6 +4984,35 @@ class AttributeHierarchicalKey extends AttributeExternalKey
*/
class AttributeExternalField extends AttributeDefinition
{
+ /**
+ * Return the search widget type corresponding to this attribute
+ *
+ * @return string
+ */
+ public function GetSearchType()
+ {
+ // Not necessary the external key is already present
+ if ($this->IsFriendlyName())
+ {
+ return self::SEARCH_WIDGET_TYPE_RAW;
+ }
+
+ try
+ {
+ $oRemoteAtt = $this->GetExtAttDef();
+ if ($oRemoteAtt instanceof AttributeString)
+ {
+ return self::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD;
+ }
+ }
+ catch (CoreException $e)
+ {
+ }
+
+ return self::SEARCH_WIDGET_TYPE_RAW;
+ }
+
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
@@ -4965,6 +5064,23 @@ class AttributeExternalField extends AttributeDefinition
}
return $sLabel;
}
+
+ public function GetLabelForSearchField()
+ {
+ $sLabel = parent::GetLabel('');
+ if (strlen($sLabel) == 0)
+ {
+ $sKeyAttCode = $this->Get("extkey_attcode");
+ $oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode);
+ $sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode);
+
+ $oRemoteAtt = $this->GetExtAttDef();
+ $sLabel .= '->'.$oRemoteAtt->GetLabel($this->m_sCode);
+ }
+
+ return $sLabel;
+ }
+
public function GetDescription($sDefault = null)
{
$sLabel = parent::GetDescription('');
@@ -5285,6 +5401,8 @@ class AttributeURL extends AttributeString
*/
class AttributeBlob extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
@@ -5612,6 +5730,8 @@ class AttributeImage extends AttributeBlob
*/
class AttributeStopWatch extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
// The list of thresholds must be an array of iPercent => array of 'option' => value
@@ -6280,6 +6400,8 @@ class AttributeStopWatch extends AttributeDefinition
*/
class AttributeSubItem extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array('target_attcode', 'item_code'));
@@ -6446,6 +6568,8 @@ class AttributeSubItem extends AttributeDefinition
*/
class AttributeOneWayPassword extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("depends_on"));
@@ -6609,6 +6733,8 @@ class AttributeOneWayPassword extends AttributeDefinition
// Indexed array having two dimensions
class AttributeTable extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
public function GetEditClass() {return "Table";}
protected function GetSQLCol($bFullSpec = false)
@@ -6838,6 +6964,8 @@ class AttributePropertySet extends AttributeTable
*/
class AttributeFriendlyName extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
+
public function __construct($sCode)
{
$this->m_sCode = $sCode;
@@ -7004,6 +7132,8 @@ class AttributeFriendlyName extends AttributeDefinition
*/
class AttributeRedundancySettings extends AttributeDBField
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW;
+
static public function ListExpectedParams()
{
return array('sql', 'relation_code', 'from_class', 'neighbour_id', 'enabled', 'enabled_mode', 'min_up', 'min_up_type', 'min_up_mode');
@@ -7394,6 +7524,8 @@ class AttributeRedundancySettings extends AttributeDBField
*/
class AttributeCustomFields extends AttributeDefinition
{
+ const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING;
+
static public function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("handler_class"));
diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php
index a7f8d5e00..2d85f41e2 100644
--- a/core/cmdbsource.class.inc.php
+++ b/core/cmdbsource.class.inc.php
@@ -241,8 +241,7 @@ class CMDBSource
}
catch(mysqli_sql_exception $e)
{
- throw new MySQLException('Could not connect to the DB server',
- array('host' => $sServer, 'user' => $sUser), $e);
+ throw new MySQLException('Could not connect to the DB server', array('host' => $sServer, 'user' => $sUser), $e);
}
if ($bCheckTlsAfterConnection
diff --git a/core/dict.class.inc.php b/core/dict.class.inc.php
index 6d68757a5..b792660e8 100644
--- a/core/dict.class.inc.php
+++ b/core/dict.class.inc.php
@@ -20,7 +20,7 @@
* Class Dict
* Management of localizable strings
*
- * @copyright Copyright (C) 2010-2012 Combodo SARL
+ * @copyright Copyright (C) 2010-2018 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -65,6 +65,11 @@ class Dict
protected static $m_aData = array();
protected static $m_sApplicationPrefix = null;
+ /**
+ * @param $sLanguageCode
+ *
+ * @throws \DictExceptionUnknownLanguage
+ */
public static function SetDefaultLanguage($sLanguageCode)
{
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
@@ -74,6 +79,11 @@ class Dict
self::$m_sDefaultLanguage = $sLanguageCode;
}
+ /**
+ * @param $sLanguageCode
+ *
+ * @throws \DictExceptionUnknownLanguage
+ */
public static function SetUserLanguage($sLanguageCode)
{
if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
@@ -113,7 +123,6 @@ class Dict
* @param string $sDefault Default value if there is no match in the dictionary
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
*
- * @throws DictExceptionMissingString
* @return string
*/
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
@@ -124,7 +133,7 @@ class Dict
if (!array_key_exists(self::GetUserLanguage(), self::$m_aData))
{
- // It may happen, when something happens before the dictionnaries get loaded
+ // It may happen, when something happens before the dictionaries get loaded
return $sStringCode;
}
$aCurrentDictionary = self::$m_aData[self::GetUserLanguage()];
@@ -155,25 +164,12 @@ class Dict
}
// Could not find the string...
//
- switch (self::$m_iErrorMode)
+ if (is_null($sDefault))
{
- case DICT_ERR_STRING:
- if (is_null($sDefault))
- {
- return $sStringCode;
- }
- else
- {
- return $sDefault;
- }
- break;
-
- case DICT_ERR_EXCEPTION:
- default:
- throw new DictExceptionMissingString(self::$m_sCurrentLanguage, $sStringCode);
- break;
+ return $sStringCode;
}
- return 'bug!';
+
+ return $sDefault;
}
@@ -285,6 +281,9 @@ class Dict
/**
* Clone a string in every language (if it exists in that language)
+ *
+ * @param $sSourceCode
+ * @param $sDestCode
*/
public static function CloneString($sSourceCode, $sDestCode)
{
@@ -357,5 +356,38 @@ class Dict
// No need to actually load the strings since it's only used to know the list of languages
// at setup time !!
}
+
+ /**
+ * Export all the dictionary entries - of the given language - whose code matches the given prefix
+ * missing entries in the current language will be replaced by entries in the default language
+ * @param string $sStartingWith
+ * @return string[]
+ */
+ public static function ExportEntries($sStartingWith)
+ {
+ self::InitLangIfNeeded(self::GetUserLanguage());
+ self::InitLangIfNeeded(self::$m_sDefaultLanguage);
+ $aEntries = array();
+ $iLength = strlen($sStartingWith);
+
+ // First prefill the array with entries from the default language
+ foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
+ {
+ if (substr($sCode, 0, $iLength) == $sStartingWith)
+ {
+ $aEntries[$sCode] = $sEntry;
+ }
+ }
+
+ // Now put (overwrite) the entries for the user language
+ foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
+ {
+ if (substr($sCode, 0, $iLength) == $sStartingWith)
+ {
+ $aEntries[$sCode] = $sEntry;
+ }
+ }
+ return $aEntries;
+ }
}
?>
diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php
index 1d09070f6..ab1a31e6e 100644
--- a/core/displayablegraph.class.inc.php
+++ b/core/displayablegraph.class.inc.php
@@ -1439,7 +1439,7 @@ class DisplayableGraph extends SimpleGraph
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
}
$oP->add("
\n");
- $oP->add("
\n");
+ $oP->add("
\n");
if (!$oP->IsPrintableVersion())
{
$oP->add_ready_script(
diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php
index 80db27c07..0a36c106a 100644
--- a/core/oql/expression.class.inc.php
+++ b/core/oql/expression.class.inc.php
@@ -1,5 +1,5 @@
Render($aArgs);
+ }
+
+ public function GetAttDef($aClasses = array())
+ {
+ return null;
+ }
+
/**
* Recursively browse the expression tree
* @param Closure $callback
@@ -46,7 +72,7 @@ abstract class Expression
abstract public function Browse(Closure $callback);
abstract public function ApplyParameters($aArgs);
-
+
// recursively builds an array of class => fieldname
abstract public function ListRequiredFields();
@@ -54,10 +80,10 @@ abstract class Expression
abstract public function CollectUsedParents(&$aTable);
abstract public function IsTrue();
-
+
// recursively builds an array of [classAlias][fieldName] => value
abstract public function ListConstantFields();
-
+
public function RequiresField($sClass, $sFieldName)
{
// #@# todo - optimize : this is called quite often when building a single query !
@@ -71,6 +97,12 @@ abstract class Expression
return base64_encode($this->Render());
}
+ /**
+ * @param $sValue
+ *
+ * @return Expression
+ * @throws OQLException
+ */
static public function unserialize($sValue)
{
return self::FromOQL(base64_decode($sValue));
@@ -119,22 +151,57 @@ abstract class Expression
{
return new BinaryExpression($this, 'OR', $oExpr);
}
-
+
abstract public function RenameParam($sOldName, $sNewName);
abstract public function RenameAlias($sOldName, $sNewName);
/**
* Make the most relevant label, given the value of the expression
- *
- * @param DBSearch oFilter The context in which this expression has been used
- * @param string sValue The value returned by the query, for this expression
- * @param string sDefault The default value if no relevant label could be computed
+ *
+ * @param DBSearch oFilter The context in which this expression has been used
+ * @param string sValue The value returned by the query, for this expression
+ * @param string sDefault The default value if no relevant label could be computed
+ *
* @return The label
- */
+ */
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
return $sDefault;
}
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ return array(
+ 'widget' => AttributeDefinition::SEARCH_WIDGET_TYPE_RAW,
+ 'oql' => $this->Render($aArgs, $bRetrofitParams),
+ 'label' => $this->Display($oSearch, $aArgs, $oAttDef),
+ 'source' => get_class($this),
+ );
+ }
+
+ /**
+ * Split binary expression on given operator
+ *
+ * @param Expression $oExpr
+ * @param string $sOperator
+ * @param array $aAndExpr
+ *
+ * @return array of expressions
+ */
+ public static function Split($oExpr, $sOperator = 'AND', &$aAndExpr = array())
+ {
+ if (($oExpr instanceof BinaryExpression) && ($oExpr->GetOperator() == $sOperator))
+ {
+ static::Split($oExpr->GetLeftExpr(), $sOperator, $aAndExpr);
+ static::Split($oExpr->GetRightExpr(), $sOperator, $aAndExpr);
+ }
+ else
+ {
+ $aAndExpr[] = $oExpr;
+ }
+
+ return $aAndExpr;
+ }
}
class SQLExpression extends Expression
@@ -165,7 +232,7 @@ class SQLExpression extends Expression
public function ApplyParameters($aArgs)
{
}
-
+
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -183,12 +250,12 @@ class SQLExpression extends Expression
public function CollectUsedParents(&$aTable)
{
}
-
+
public function ListConstantFields()
{
return array();
}
-
+
public function RenameParam($sOldName, $sNewName)
{
// Do nothing, since there is nothing to rename
@@ -244,7 +311,7 @@ class BinaryExpression extends Expression
}
return false;
}
-
+
public function GetLeftExpr()
{
return $this->m_oLeftExpr;
@@ -295,7 +362,7 @@ class BinaryExpression extends Expression
$this->m_oRightExpr->ApplyParameters($aArgs);
}
}
-
+
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->GetLeftExpr()->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -321,7 +388,15 @@ class BinaryExpression extends Expression
$this->GetLeftExpr()->CollectUsedParents($aTable);
$this->GetRightExpr()->CollectUsedParents($aTable);
}
-
+
+ public function GetAttDef($aClasses = array())
+ {
+ $oAttDef = $this->GetLeftExpr()->GetAttDef($aClasses);
+ if (!is_null($oAttDef)) return $oAttDef;
+
+ return $this->GetRightExpr()->GetAttDef($aClasses);
+ }
+
/**
* List all constant expression of the form
= or = :
* Could be extended to support =
@@ -355,7 +430,7 @@ class BinaryExpression extends Expression
}
return $aResult;
}
-
+
public function RenameParam($sOldName, $sNewName)
{
$this->GetLeftExpr()->RenameParam($sOldName, $sNewName);
@@ -367,6 +442,131 @@ class BinaryExpression extends Expression
$this->GetLeftExpr()->RenameAlias($sOldName, $sNewName);
$this->GetRightExpr()->RenameAlias($sOldName, $sNewName);
}
+
+ // recursive rendering
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ $bReverseOperator = false;
+ $oLeftExpr = $this->GetLeftExpr();
+ if ($oLeftExpr instanceof FieldExpression)
+ {
+ $oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
+ }
+ $oRightExpr = $this->GetRightExpr();
+ if ($oRightExpr instanceof FieldExpression)
+ {
+ $oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
+ $bReverseOperator = true;
+ }
+
+ $sLeft = $oLeftExpr->Display($oSearch, $aArgs, $oAttDef);
+ $sRight = $oRightExpr->Display($oSearch, $aArgs, $oAttDef);
+
+ if ($bReverseOperator)
+ {
+ // switch left and right expressions so reverse the operator
+ // Note that the operation is the same so < becomes > and not >=
+ switch ($this->GetOperator())
+ {
+ case '>':
+ $sOperator = '<';
+ break;
+ case '<':
+ $sOperator = '>';
+ break;
+ case '>=':
+ $sOperator = '<=';
+ break;
+ case '<=':
+ $sOperator = '>=';
+ break;
+ default:
+ $sOperator = $this->GetOperator();
+ break;
+ }
+ $sOperator = $this->OperatorToNaturalLanguage($sOperator, $oAttDef);
+
+ return "({$sRight}{$sOperator}{$sLeft})";
+ }
+
+ $sOperator = $this->GetOperator();
+ $sOperator = $this->OperatorToNaturalLanguage($sOperator, $oAttDef);
+
+ return "({$sLeft}{$sOperator}{$sRight})";
+ }
+
+ private function OperatorToNaturalLanguage($sOperator, $oAttDef)
+ {
+ if ($oAttDef instanceof AttributeDateTime)
+ {
+ return Dict::S('Expression:Operator:Date:'.$sOperator, " $sOperator ");
+ }
+
+ return Dict::S('Expression:Operator:'.$sOperator, " $sOperator ");
+ }
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $bReverseOperator = false;
+ $oLeftExpr = $this->GetLeftExpr();
+ $oRightExpr = $this->GetRightExpr();
+
+ $oAttDef = $oLeftExpr->GetAttDef($oSearch->GetJoinedClasses());
+
+ if (is_null($oAttDef))
+ {
+ $oAttDef = $oRightExpr->GetAttDef($oSearch->GetJoinedClasses());
+ $bReverseOperator = true;
+ }
+
+ if (is_null($oAttDef))
+ {
+ return parent::GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+ }
+
+ $aCriteriaLeft = $oLeftExpr->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+ $aCriteriaRight = $oRightExpr->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+
+ if ($bReverseOperator)
+ {
+ $aCriteria = array_merge($aCriteriaRight, $aCriteriaLeft);
+ // switch left and right expressions so reverse the operator
+ // Note that the operation is the same so < becomes > and not >=
+ switch ($this->GetOperator())
+ {
+ case '>':
+ $aCriteria['operator'] = '<';
+ break;
+ case '<':
+ $aCriteria['operator'] = '>';
+ break;
+ case '>=':
+ $aCriteria['operator'] = '<=';
+ break;
+ case '<=':
+ $aCriteria['operator'] = '>=';
+ break;
+ default:
+ $aCriteria['operator'] = $this->GetOperator();
+ break;
+ }
+ }
+ else
+ {
+ $aCriteria = array_merge($aCriteriaLeft, $aCriteriaRight);
+ $aCriteria['operator'] = $this->GetOperator();
+ }
+ $aCriteria['oql'] = $this->Render($aArgs, $bRetrofitParams);
+ $aCriteria['label'] = $this->Display($oSearch, $aArgs, $oAttDef);
+
+ if (isset($aCriteriaLeft['ref']) && isset($aCriteriaRight['ref']) && ($aCriteriaLeft['ref'] != $aCriteriaRight['ref']))
+ {
+ // Only one Field is supported in the expressions
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+ }
+
+ return $aCriteria;
+ }
}
@@ -404,7 +604,7 @@ class UnaryExpression extends Expression
public function ApplyParameters($aArgs)
{
}
-
+
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}
@@ -427,7 +627,7 @@ class UnaryExpression extends Expression
{
return array();
}
-
+
public function RenameParam($sOldName, $sNewName)
{
// Do nothing
@@ -451,6 +651,46 @@ class ScalarExpression extends UnaryExpression
parent::__construct($value);
}
+ /**
+ * @param array $oSearch
+ * @param array $aArgs
+ * @param AttributeDefinition $oAttDef
+ *
+ * @return array|string
+ * @throws \Exception
+ */
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ if (!is_null($oAttDef))
+ {
+ if ($oAttDef->IsExternalKey())
+ {
+ try
+ {
+ /** @var AttributeExternalKey $oAttDef */
+ $sTarget = $oAttDef->GetTargetClass();
+ $oObj = MetaModel::GetObject($sTarget, $this->m_value);
+
+ return $oObj->Get("friendlyname");
+ } catch (CoreException $e)
+ {
+ }
+ }
+
+ if (!($oAttDef instanceof AttributeDateTime))
+ {
+ return $oAttDef->GetAsPlainText($this->m_value);
+ }
+ }
+
+ if (strpos($this->m_value, '%') === 0)
+ {
+ return '';
+ }
+
+ return $this->Render($aArgs);
+ }
+
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -469,6 +709,54 @@ class ScalarExpression extends UnaryExpression
{
return clone $this;
}
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $aCriteria = array();
+ switch ((string)($this->m_value))
+ {
+ case '%Y-%m-%d':
+ $aCriteria['date_type'] = 'd';
+ break;
+ case '%Y-%m':
+ $aCriteria['date_type'] = 'm';
+ break;
+ case '%w':
+ $aCriteria['date_type'] = 'w';
+ break;
+ default:
+ $aValue = array('value' => $this->GetValue());
+ if (!is_null($oAttDef))
+ {
+ switch (true)
+ {
+ case $oAttDef->IsExternalKey():
+ try
+ {
+ /** @var AttributeExternalKey $oAttDef */
+ $sTarget = $oAttDef->GetTargetClass();
+ $oObj = MetaModel::GetObject($sTarget, $this->GetValue());
+
+ $aValue['label'] = $oObj->Get("friendlyname");
+
+ }
+ catch (Exception $e)
+ {
+ IssueLog::Error($e->getMessage());
+ }
+ break;
+ default:
+ $aValue['label'] = $oAttDef->GetAsPlainText($this->GetValue());
+ break;
+ }
+ }
+ $aCriteria['values'] = array($aValue);
+ break;
+ }
+
+ return $aCriteria;
+ }
+
}
class TrueExpression extends ScalarExpression
@@ -525,6 +813,44 @@ class FieldExpression extends UnaryExpression
$this->m_value = $sParent.'.'.$this->m_sName;
}
+ private function GetClassName($aClasses = array())
+ {
+ if (isset($aClasses[$this->m_sParent]))
+ {
+ return $aClasses[$this->m_sParent];
+ }
+ else
+ {
+ return $this->m_sParent;
+ }
+ }
+
+ /**
+ * @param DBObjectSearch $oSearch
+ * @param array $aArgs
+ * @param AttributeDefinition $oAttDef
+ *
+ * @return array|string
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ * @throws \Exception
+ */
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ if (empty($this->m_sParent))
+ {
+ return "`{$this->m_sName}`";
+ }
+ $sClass = $this->GetClassName($oSearch->GetJoinedClasses());
+ $sAttName = MetaModel::GetLabel($sClass, $this->m_sName);
+ if ($sClass != $oSearch->GetClass())
+ {
+ $sAttName = MetaModel::GetName($sClass).':'.$sAttName;
+ }
+
+ return $sAttName;
+ }
+
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
{
@@ -535,6 +861,37 @@ class FieldExpression extends UnaryExpression
return "`{$this->m_sParent}`.`{$this->m_sName}`";
}
+ public function GetAttDef($aClasses = array())
+ {
+ if (!empty($this->m_sParent))
+ {
+ $sClass = $this->GetClassName($aClasses);
+ $aAttDefs = MetaModel::ListAttributeDefs($sClass);
+ if (isset($aAttDefs[$this->m_sName]))
+ {
+ return $aAttDefs[$this->m_sName];
+ }
+ else
+ {
+ if ($this->m_sName == 'id')
+ {
+ $aParams = array(
+ 'default_value' => 0,
+ 'is_null_allowed' => false,
+ 'allowed_values' => null,
+ 'depends_on' => null,
+ 'sql' => 'id',
+ );
+
+ return new AttributeInteger($this->m_sName, $aParams);
+ }
+ }
+ }
+
+ return null;
+ }
+
+
public function ListRequiredFields()
{
return array($this->m_sParent.'.'.$this->m_sName);
@@ -565,8 +922,9 @@ class FieldExpression extends UnaryExpression
if (!array_key_exists($this->m_sParent, $aTranslationData))
{
if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData)));
+
return clone $this;
- }
+ }
if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent]))
{
if (!array_key_exists('*', $aTranslationData[$this->m_sParent]))
@@ -595,12 +953,13 @@ class FieldExpression extends UnaryExpression
/**
* Make the most relevant label, given the value of the expression
- *
- * @param DBSearch oFilter The context in which this expression has been used
- * @param string sValue The value returned by the query, for this expression
- * @param string sDefault The default value if no relevant label could be computed
+ *
+ * @param DBSearch oFilter The context in which this expression has been used
+ * @param string sValue The value returned by the query, for this expression
+ * @param string sDefault The default value if no relevant label could be computed
+ *
* @return The label
- */
+ */
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
$sAttCode = $this->GetName();
@@ -646,6 +1005,47 @@ class FieldExpression extends UnaryExpression
$this->m_sParent = $sNewName;
}
}
+
+
+ /**
+ * @param $oSearch
+ * @param null $aArgs
+ * @param bool $bRetrofitParams
+ * @param AttributeDefinition $oAttDef
+ *
+ * @return array
+ */
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $oAttDef = $this->GetAttDef($oSearch->GetJoinedClasses());
+ if (!is_null($oAttDef))
+ {
+ $sSearchType = $oAttDef->GetSearchType();
+ try
+ {
+ if ($sSearchType == AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY)
+ {
+ // TODO Check the type of external key ? (EXTKEY_ABSOLUTE or EXTKEY_RELATIVE)
+ if (MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass()))
+ {
+ $sSearchType = AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY;
+ }
+ }
+ }
+ catch (CoreException $e)
+ {
+ }
+ }
+ else
+ {
+ $sSearchType = AttributeDefinition::SEARCH_WIDGET_TYPE;
+ }
+ return array(
+ 'widget' => $sSearchType,
+ 'ref' => $this->GetParent().'.'.$this->GetName(),
+ 'class_alias' => $this->GetParent(),
+ );
+ }
}
// Has been resolved into an SQL expression
@@ -678,7 +1078,68 @@ class VariableExpression extends UnaryExpression
return false;
}
- public function GetName() {return $this->m_sName;}
+ public function GetName()
+ {
+ return $this->m_sName;
+ }
+
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ $sValue = $this->m_value;
+ if (!is_null($aArgs) && (array_key_exists($this->m_sName, $aArgs)))
+ {
+ $sValue = $aArgs[$this->m_sName];
+ }
+ elseif (($iPos = strpos($this->m_sName, '->')) !== false)
+ {
+ $sParamName = substr($this->m_sName, 0, $iPos);
+ $oObj = null;
+ $sAttCode = 'id';
+ if (array_key_exists($sParamName.'->object()', $aArgs))
+ {
+ $sAttCode = substr($this->m_sName, $iPos + 2);
+ $oObj = $aArgs[$sParamName.'->object()'];
+ }
+ elseif (array_key_exists($sParamName, $aArgs))
+ {
+ $sAttCode = substr($this->m_sName, $iPos + 2);
+ $oObj = $aArgs[$sParamName];
+ }
+ if (!is_null($oObj))
+ {
+ if ($sAttCode == 'id')
+ {
+ $sValue = $oObj->Get("friendlyname");
+ }
+ else
+ {
+ $sValue = $oObj->Get($sAttCode);
+ }
+
+ return $sValue;
+ }
+ }
+ if (!is_null($oAttDef))
+ {
+ if ($oAttDef->IsExternalKey())
+ {
+ try
+ {
+ /** @var AttributeExternalKey $oAttDef */
+ $sTarget = $oAttDef->GetTargetClass();
+ $oObj = MetaModel::GetObject($sTarget, $sValue);
+
+ return $oObj->Get("friendlyname");
+ } catch (CoreException $e)
+ {
+ }
+ }
+
+ return $oAttDef->GetAsPlainText($sValue);
+ }
+
+ return $this->Render($aArgs);
+ }
// recursive rendering
public function Render(&$aArgs = null, $bRetrofitParams = false)
@@ -729,7 +1190,7 @@ class VariableExpression extends UnaryExpression
$this->m_sName = $sNewName;
}
}
-
+
public function GetAsScalar($aArgs)
{
$oRet = null;
@@ -833,7 +1294,7 @@ class ListExpression extends Expression
}
}
}
-
+
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -861,7 +1322,7 @@ class ListExpression extends Expression
}
return $aRes;
}
-
+
public function CollectUsedParents(&$aTable)
{
foreach ($this->m_aExpressions as $oExpr)
@@ -879,7 +1340,7 @@ class ListExpression extends Expression
}
return $aRes;
}
-
+
public function RenameParam($sOldName, $sNewName)
{
$aRes = array();
@@ -887,7 +1348,7 @@ class ListExpression extends Expression
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
- }
+ }
public function RenameAlias($sOldName, $sNewName)
{
@@ -896,7 +1357,34 @@ class ListExpression extends Expression
{
$oExpr->RenameAlias($sOldName, $sNewName);
}
- }
+ }
+
+ public function GetAttDef($aClasses = array())
+ {
+ foreach($this->m_aExpressions as $oExpression)
+ {
+ $oAttDef = $oExpression->GetAttDef($aClasses);
+ if (!is_null($oAttDef)) return $oAttDef;
+ }
+
+ return null;
+ }
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $aValues = array();
+
+ foreach($this->m_aExpressions as $oExpression)
+ {
+ $aCrit = $oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+ if (array_key_exists('values', $aCrit))
+ {
+ $aValues = array_merge($aValues, $aCrit['values']);
+ }
+ }
+
+ return array('values' => $aValues);
+ }
}
@@ -990,7 +1478,7 @@ class FunctionExpression extends Expression
}
return $aRes;
}
-
+
public function CollectUsedParents(&$aTable)
{
foreach ($this->m_aArgs as $oExpr)
@@ -1008,7 +1496,7 @@ class FunctionExpression extends Expression
}
return $aRes;
}
-
+
public function RenameParam($sOldName, $sNewName)
{
foreach ($this->m_aArgs as $key => $oExpr)
@@ -1025,14 +1513,26 @@ class FunctionExpression extends Expression
}
}
+ public function GetAttDef($aClasses = array())
+ {
+ foreach($this->m_aArgs as $oExpression)
+ {
+ $oAttDef = $oExpression->GetAttDef($aClasses);
+ if (!is_null($oAttDef)) return $oAttDef;
+ }
+
+ return null;
+ }
+
/**
* Make the most relevant label, given the value of the expression
- *
- * @param DBSearch oFilter The context in which this expression has been used
- * @param string sValue The value returned by the query, for this expression
- * @param string sDefault The default value if no relevant label could be computed
+ *
+ * @param DBSearch oFilter The context in which this expression has been used
+ * @param string sValue The value returned by the query, for this expression
+ * @param string sDefault The default value if no relevant label could be computed
+ *
* @return The label
- */
+ */
public function MakeValueLabel($oFilter, $sValue, $sDefault)
{
static $aWeekDayToString = null;
@@ -1095,6 +1595,87 @@ class FunctionExpression extends Expression
}
return $sRes;
}
+
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ $sOperation = '';
+ $sVerb = '';
+ switch ($this->m_sVerb)
+ {
+ case 'NOW':
+ $sVerb = $this->VerbToNaturalLanguage();
+ break;
+ case 'DATE_SUB':
+ $sVerb = ' -';
+ break;
+ case 'DATE_ADD':
+ $sVerb = ' +';
+ break;
+ case 'DATE_FORMAT':
+ break;
+ default:
+ return $this->Render($aArgs);
+ }
+
+ foreach($this->m_aArgs as $oExpression)
+ {
+ if ($oExpression instanceof IntervalExpression)
+ {
+ $sOperation .= $sVerb;
+ $sVerb = '';
+ }
+ $sOperation .= $oExpression->Display($oSearch, $aArgs, $oAttDef);
+ }
+
+ if (!empty($sVerb))
+ {
+ $sOperation .= $sVerb;
+ }
+ return $sOperation;
+ }
+
+ private function VerbToNaturalLanguage()
+ {
+ return Dict::S('Expression:Verb:'.$this->m_sVerb, " {$this->m_sVerb} ");
+ }
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $aCriteria = array();
+ switch ($this->m_sVerb)
+ {
+ case 'ISNULL':
+ $aCriteria['operator'] = $this->m_sVerb;
+ foreach($this->m_aArgs as $oExpression)
+ {
+ $aCriteria = array_merge($oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef), $aCriteria);
+ }
+ $aCriteria['has_undefined'] = true;
+ break;
+
+ case 'NOW':
+ $aCriteria = array('widget' => 'date_time');
+ $aCriteria['is_relative'] = true;
+ $aCriteria['verb'] = $this->m_sVerb;
+ break;
+
+ case 'DATE_ADD':
+ case 'DATE_SUB':
+ case 'DATE_FORMAT':
+ $aCriteria = array('widget' => 'date_time');
+ foreach($this->m_aArgs as $oExpression)
+ {
+ $aCriteria = array_merge($oExpression->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef), $aCriteria);
+ }
+ $aCriteria['verb'] = $this->m_sVerb;
+ break;
+
+ default:
+ return parent::GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+ }
+
+ return $aCriteria;
+ }
}
class IntervalExpression extends Expression
@@ -1147,7 +1728,7 @@ class IntervalExpression extends Expression
$this->m_oValue->ApplyParameters($aArgs);
}
}
-
+
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
$this->m_oValue->GetUnresolvedFields($sAlias, $aUnresolved);
@@ -1171,16 +1752,29 @@ class IntervalExpression extends Expression
{
return array();
}
-
+
public function RenameParam($sOldName, $sNewName)
{
$this->m_oValue->RenameParam($sOldName, $sNewName);
- }
+ }
public function RenameAlias($sOldName, $sNewName)
{
$this->m_oValue->RenameAlias($sOldName, $sNewName);
- }
+ }
+
+ public function GetCriterion($oSearch, &$aArgs = null, $bRetrofitParams = false, $oAttDef = null)
+ {
+ $aCriteria = $this->m_oValue->GetCriterion($oSearch, $aArgs, $bRetrofitParams, $oAttDef);
+ $aCriteria['unit'] = $this->m_sUnit;
+
+ return $aCriteria;
+ }
+
+ public function Display($oSearch, &$aArgs = null, $oAttDef = null)
+ {
+ return $this->m_oValue->Render($aArgs).' '.Dict::S('Expression:Unit:Long:'.$this->m_sUnit, $this->m_sUnit);
+ }
}
class CharConcatExpression extends Expression
@@ -1210,7 +1804,7 @@ class CharConcatExpression extends Expression
foreach ($this->m_aExpressions as $oExpr)
{
$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
- // Concat will be globally NULL if one single argument is null !
+ // Concat will be globally NULL if one single argument is null !
$aRes[] = "COALESCE($sCol, '')";
}
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
@@ -1293,7 +1887,7 @@ class CharConcatExpression extends Expression
{
$this->m_aExpressions[$key] = $oExpr->RenameParam($sOldName, $sNewName);
}
- }
+ }
public function RenameAlias($sOldName, $sNewName)
{
@@ -1301,7 +1895,7 @@ class CharConcatExpression extends Expression
{
$oExpr->RenameAlias($sOldName, $sNewName);
}
- }
+ }
}
@@ -1322,7 +1916,7 @@ class CharConcatWSExpression extends CharConcatExpression
foreach ($this->m_aExpressions as $oExpr)
{
$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
- // Concat will be globally NULL if one single argument is null !
+ // Concat will be globally NULL if one single argument is null !
$aRes[] = "COALESCE($sCol, '')";
}
$sSep = CMDBSource::Quote($this->m_separator);
@@ -1502,6 +2096,7 @@ class QueryBuilderExpressions
{
$this->m_aJoinFields[$index] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
+
foreach($this->m_aClassIds as $sClass => $oExpression)
{
$this->m_aClassIds[$sClass] = $oExpression->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
diff --git a/css/css-variables.scss b/css/css-variables.scss
index 6e61874f8..975555e61 100644
--- a/css/css-variables.scss
+++ b/css/css-variables.scss
@@ -1,8 +1,49 @@
-$highlight-color: #E87C1E;
+// Base colors
+$gray-base: #000 !default;
+$gray-darker: lighten($gray-base, 13.5%) !default; // #222
+$gray-dark: #444 !default;
+$gray: #777 !default;
+$gray-light: #808080 !default;
+$gray-lighter: #ddd !default;
+$gray-extra-light: #F1F1F1 !default;
+
+$white: #FFFFFF !default;
+
+$combodo-orange: #EA7D1E !default;
+$combodo-dark-gray: #585653 !default;
+
+$combodo-orange-dark: darken($combodo-orange, 13.8%) !default;
+$combodo-orange-darker: darken($combodo-orange, 18%) !default;
+$combodo-dark-gray-dark: darken($combodo-dark-gray, 13.5%) !default;
+$combodo-dark-gray-darker: darken($combodo-dark-gray, 18%) !default;
+
+// Vars
+$highlight-color: $combodo-orange;
$grey-color: #555555;
$complement-color: #1c94c4;
$complement-light: #d6e8ef;
-$frame-background-color: #F1F1F1;
+$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;
+//
+$search-criteria-box-color: #2D2D2D;
+$search-criteria-box-picto-color: #E87C1E;
+$search-criteria-box-bg-color: #EEEEEE;
+$search-criteria-box-hover-color: $white;
+$search-criteria-box-border-color: #CCCCCC;
+$search-criteria-box-border: 1px solid $search-criteria-box-border-color;
+$search-criteria-box-radius: 1px;
+//
+$search-add-criteria-box-color: $search-criteria-box-color;
+$search-add-criteria-box-bg-color: $white;
+$search-add-criteria-box-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.4.0";
\ No newline at end of file
+$version: "v2.4.0";
diff --git a/css/font-combodo/combodo-webfont.ttf b/css/font-combodo/combodo-webfont.ttf
index 5be2cbdd4..495c822c0 100644
Binary files a/css/font-combodo/combodo-webfont.ttf and b/css/font-combodo/combodo-webfont.ttf differ
diff --git a/css/font-combodo/combodo-webfont.woff b/css/font-combodo/combodo-webfont.woff
index cf7212013..f8588d085 100644
Binary files a/css/font-combodo/combodo-webfont.woff and b/css/font-combodo/combodo-webfont.woff differ
diff --git a/css/font-combodo/combodo-webfont.woff2 b/css/font-combodo/combodo-webfont.woff2
index 812157161..0bddf7d18 100644
Binary files a/css/font-combodo/combodo-webfont.woff2 and b/css/font-combodo/combodo-webfont.woff2 differ
diff --git a/css/font-combodo/combodo.sfd b/css/font-combodo/combodo.sfd
index 102234437..ee4bb1e16 100644
--- a/css/font-combodo/combodo.sfd
+++ b/css/font-combodo/combodo.sfd
@@ -21,7 +21,7 @@ OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 1
CreationTime: 1463745065
-ModificationTime: 1506001058
+ModificationTime: 1522325525
OS2TypoAscent: 0
OS2TypoAOffset: 1
OS2TypoDescent: 0
@@ -35,6 +35,7 @@ HheadAscent: 0
HheadAOffset: 1
HheadDescent: 0
HheadDOffset: 1
+OS2Vendor: 'PfEd'
MarkAttachClasses: 1
DEI: 91125
Encoding: ISO8859-1
@@ -46,7 +47,7 @@ FitToEm: 0
WinInfo: 0 31 10
BeginPrivate: 0
EndPrivate
-BeginChars: 256 11
+BeginChars: 256 13
StartChar: zero
Encoding: 48 48 0
@@ -940,7 +941,135 @@ SplineSet
815.574 32.7998 814.774 33.5996 813.175 33.5996 c 2
810.774 33.5996 l 1
EndSplineSet
-Validated: 524321
+Validated: 33
+EndChar
+
+StartChar: B
+Encoding: 66 66 11
+Width: 1024
+VWidth: 0
+HStem: 0 59<210.859 353.111 670.997 813.141> 103 28<483.475 540.525> 326 280<245 398 626 779>
+VStem: 87 62<121.402 265.716> 415 60<134.336 199.804> 549 60<134.336 199.804> 875 62<121.402 265.716>
+LayerCount: 3
+Fore
+SplineSet
+376 606 m 0
+ 420 606 456 571 456 527 c 0
+ 456 514 l 1
+ 458 444 l 1
+ 566 444 l 1
+ 568 514 l 1
+ 568 514 568 524 568 527 c 0
+ 568 571 604 606 648 606 c 0
+ 677 606 704 590 718 564 c 1
+ 719 564 l 1
+ 754 502 l 1
+ 781 491 803 472 817 446 c 1
+ 818 446 l 1
+ 914 283 l 1
+ 929 255 937 224 937 192 c 0
+ 937 87 852 0 747 0 c 0
+ 666 0 594 54 568 131 c 1
+ 555 114 534 103 512 103 c 0
+ 490 103 469 114 456 132 c 1
+ 431 55 358 0 277 0 c 0
+ 172 0 87 87 87 192 c 0
+ 87 224 95 255 110 283 c 1
+ 206 446 l 1
+ 207 446 l 1
+ 221 472 243 491 270 502 c 1
+ 306 564 l 1
+ 320 590 347 606 376 606 c 0
+282 326 m 0
+ 208 326 149 266 149 193 c 0
+ 149 119 208 59 282 59 c 0
+ 355 59 415 119 415 193 c 0
+ 415 266 355 326 282 326 c 0
+742 326 m 0
+ 669 326 609 266 609 193 c 0
+ 609 119 669 59 742 59 c 0
+ 816 59 875 119 875 193 c 0
+ 875 266 816 326 742 326 c 0
+512 204 m 0
+ 492 204 475 188 475 168 c 0
+ 475 148 492 131 512 131 c 0
+ 532 131 549 148 549 168 c 0
+ 549 188 532 204 512 204 c 0
+EndSplineSet
+EndChar
+
+StartChar: b
+Encoding: 98 98 12
+Width: 1024
+VWidth: 0
+Flags: H
+HStem: -1 35<183.649 347.721 676.937 838.822> 198 25<478.803 548.794> 294 234<470.756 555.897>
+VStem: 75 44<83.375 145> 414 42<83.6665 369.146> 432 24<255 473> 569 42<83.6665 369.146> 569 24<255 473> 906 43<84.3258 145>
+LayerCount: 3
+Fore
+SplineSet
+352 616 m 0xf080
+ 390 616 423 593 420 563 c 1
+ 422 488 l 1
+ 452 468 454 441 454 441 c 1
+ 455 305 456 159 456 12 c 1
+ 453 -62 369 -121 266 -121 c 0
+ 161 -121 74 -59 74 17 c 0
+ 74 20 76 22 76 25 c 1
+ 74 25 l 1
+ 106 196 l 1
+ 106 196 l 1
+ 113 235 143 270 188 292 c 1
+ 186 292 l 1
+ 206 408 l 1
+ 206 408 228 457 274 487 c 1
+ 282 570 l 1
+ 284 570 l 1
+ 287 596 316 616 352 616 c 0xf080
+266 114 m 0
+ 185 114 118 69 118 14 c 0
+ 118 -41 185 -86 266 -86 c 0
+ 347 -86 414 -41 414 14 c 0
+ 414 69 347 114 266 114 c 0
+514 408 m 0
+ 554 408 589 384 592 353 c 1
+ 592 353 l 1
+ 592 135 l 1
+ 592 135 l 1
+ 590 103 555 78 514 78 c 0
+ 473 78 436 103 434 135 c 1
+ 432 135 l 1
+ 432 353 l 1
+ 434 353 l 1
+ 437 384 474 408 514 408 c 0
+466 138 m 0
+ 466 118 489 103 514 103 c 2
+ 539 103 560 118 560 138 c 0
+ 560 158 539 174 514 174 c 0
+ 489 174 466 158 466 138 c 0
+672 616 m 0
+ 708 616 737 596 740 570 c 1
+ 742 570 l 1
+ 750 487 l 1
+ 796 457 818 408 818 408 c 1
+ 838 292 l 1
+ 836 292 l 1
+ 881 270 911 235 918 196 c 1
+ 950 25 l 1
+ 948 25 l 1
+ 948 22 948 20 948 17 c 0
+ 948 -59 863 -121 758 -121 c 0
+ 655 -121 571 -62 568 12 c 1
+ 568 159 569 305 570 441 c 1
+ 570 441 572 468 602 488 c 1
+ 604 563 l 1
+ 601 593 634 616 672 616 c 0
+758 114 m 0
+ 677 114 610 69 610 14 c 0
+ 610 -41 677 -86 758 -86 c 0
+ 839 -86 906 -41 906 14 c 0
+ 906 69 839 114 758 114 c 0
+EndSplineSet
EndChar
EndChars
EndSplineFont
diff --git a/css/font-combodo/font-combodo.css b/css/font-combodo/font-combodo.css
index 07a583cda..bf60a2766 100644
--- a/css/font-combodo/font-combodo.css
+++ b/css/font-combodo/font-combodo.css
@@ -1,8 +1,8 @@
@font-face {
font-family: 'CombodoRegular';
- src: url('combodo-webfont.woff2?v=2.0') format('woff2'),
- url('combodo-webfont.woff?v=2.0') format('woff'),
- url('combodo-webfont.ttf?v=2.0') format('truetype');
+ src: url('combodo-webfont.woff2?v=2.1') format('woff2'),
+ url('combodo-webfont.woff?v=2.1') format('woff'),
+ url('combodo-webfont.ttf?v=2.1') format('truetype');
font-weight: normal;
font-style: normal;
@@ -187,6 +187,12 @@
.fc-closed-request:before {
content: "3";
}
+.fc-binoculars:before {
+ content: "B";
+}
+.fc-binoculars-alt:before {
+ content: "b";
+}
.fc-combodo-icon-o:before {
content: "C";
}
diff --git a/css/font-combodo/glyphs/B.svg b/css/font-combodo/glyphs/B.svg
new file mode 100755
index 000000000..d1a20a22c
--- /dev/null
+++ b/css/font-combodo/glyphs/B.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/css/font-combodo/glyphs/b-lowercase.svg b/css/font-combodo/glyphs/b-lowercase.svg
new file mode 100755
index 000000000..08dac675b
--- /dev/null
+++ b/css/font-combodo/glyphs/b-lowercase.svg
@@ -0,0 +1,280 @@
+
+
diff --git a/css/light-grey.css b/css/light-grey.css
index 1c3555356..001c43ffb 100644
--- a/css/light-grey.css
+++ b/css/light-grey.css
@@ -58,7 +58,7 @@ label {
cursor: pointer;
}
.hilite, .hilite a, .hilite a:visited {
- color: #e87c1e;
+ color: #ea7d1e;
text-decoration: none;
}
table.datatable {
@@ -114,7 +114,7 @@ table.listResults td .view-image img {
cursor: pointer;
margin-bottom: 3px;
padding: 2px;
- background-color: #e87c1e;
+ background-color: #ea7d1e;
}
.edit-image .edit-buttons .button.disabled {
cursor: default;
@@ -275,7 +275,7 @@ legend.transparent {
}
.ui-widget-content td a:hover, p a:hover, td a:hover {
text-decoration: underline;
- color: #e87c1e;
+ color: #ea7d1e;
}
.cke_reset_all *:hover {
text-decoration: none;
@@ -283,8 +283,8 @@ legend.transparent {
}
table.cke_dialog_contents a.cke_dialog_ui_button_ok {
color: #000;
- border-color: #e87c1e;
- background: #e87c1e;
+ border-color: #ea7d1e;
+ background: #ea7d1e;
}
.cke_notifications_area {
display: none;
@@ -309,7 +309,7 @@ td a.mailto, td a.mailto:visited {
}
td a.mailto:hover {
text-decoration: underline;
- color: #e87c1e;
+ color: #ea7d1e;
padding-left: 20px;
background: url(../images/mail.png?v=v2.4.0) no-repeat left;
}
@@ -329,7 +329,7 @@ a.small_action {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
- background: #e87c1e url(../images/actions_left.png?v=v2.4.0) no-repeat left;
+ background: #ea7d1e url(../images/actions_left.png?v=v2.4.0) no-repeat left;
}
.actions_details span {
background: url(../images/actions_right.png?v=v2.4.0) no-repeat right;
@@ -424,7 +424,7 @@ div.ui-accordion-content {
text-decoration: none;
}
.ui-accordion-content a:hover {
- color: #e87c1e;
+ color: #ea7d1e;
text-decoration: none;
}
.ui-accordion-content ul {
@@ -455,7 +455,7 @@ a.CollapsibleLabel.open, td a.CollapsibleLabel.open {
padding: 0px 0pt 0px 16px;
font-size: 8pt;
text-decoration: none;
- color: #e87c1e;
+ color: #ea7d1e;
background: url(../images/mini-arrow-orange-open.gif) no-repeat left;
}
.page_header {
@@ -506,7 +506,7 @@ div.actions_menu > ul {
nowidth: 70px;
padding-left: 5px;
/* Nasty work-around for IE... en attendant mieux */
- background: #e87c1e url(../images/actions_left.png?v=v2.4.0) no-repeat top left;
+ background: #ea7d1e url(../images/actions_left.png?v=v2.4.0) no-repeat top left;
cursor: pointer;
margin: 0;
}
@@ -577,7 +577,7 @@ div.actions_menu > ul > li {
position: absolute;
display: none;
border-top: 1px solid white;
- z-index: 999;
+ z-index: 1500;
}
.itop_popup li ul li, #logOffBtn li ul li {
float: none;
@@ -588,7 +588,7 @@ div.actions_menu > ul > li {
text-align: left;
}
.itop_popup li ul li a:hover, #logOffBtn li ul li a:hover {
- background: #e87c1e;
+ background: #ea7d1e;
color: #fff;
font-weight: bold;
}
@@ -675,60 +675,6 @@ input.dp-applied {
float: left;
}
/* For search forms */
-.SearchDrawer {
- border-top: 5px solid #1c94c4;
- border-left: 5px solid #1c94c4;
- border-right: 5px solid #1c94c4;
- border-bottom: 0;
- background: #d6e8ef;
- color: #000;
- padding: 10px;
- margin: 0;
- font-size: 12px;
-}
-.SearchDrawer label {
- background: #d6e8ef;
- color: #000;
- text-align: right;
-}
-.SearchDrawer h1 {
- color: #000;
-}
-.SearchDrawer .SearchAttribute > .field_input_zone {
- display: inline-block;
-}
-.SearchDrawer .SearchAttribute > .field_input_zone > .field_select_wrapper {
- display: inline-block;
-}
-.DrawerClosed {
- display: none;
-}
-.DrawerHandle {
- margin: 0;
- padding: 5px;
- background: url(../images/drawer-handle.gif) bottom no-repeat transparent;
- color: #fff;
- cursor: pointer;
- text-align: center;
- /* center the block */
- width: 100px;
- margin-left: auto;
- margin-right: auto;
- margin-top: 0;
- margin-bottom: 0;
- display: block;
- font-size: 12px;
-}
-div.HRDrawer {
- height: 5px;
- width: 100%;
- margin: 0;
- background-color: #1c94c4;
- margin: 0;
- padding: 0;
- border: 0;
- display: block;
-}
.mini_tabs a {
text-decoration: none;
font-weight: bold;
@@ -753,6 +699,506 @@ div.HRDrawer {
nopadding-right: 1em;
margin-top: 0;
}
+/* Search forms v2 */
+.search_box {
+ box-sizing: border-box;
+ position: relative;
+ z-index: 1100;
+ /* To be over the table block/unblock UI. Not very sure about this. */
+ /* Sizing reset */
+}
+.search_box * {
+ box-sizing: border-box;
+}
+.search_form_handler {
+ position: relative;
+ z-index: 10;
+ font-size: 12px;
+ border: 1px solid #3f7294;
+ /* Sizing reset */
+ /* Hyperlink reset */
+ /* Input reset */
+ /* List helpers */
+}
+.search_form_handler * {
+ box-sizing: border-box;
+}
+.search_form_handler a {
+ color: inherit;
+ text-decoration: none;
+}
+.search_form_handler input[type="text"], .search_form_handler select {
+ padding: 1px 2px;
+}
+.search_form_handler.opened .sf_title .sft_toggler {
+ transform: rotateX(180deg);
+}
+.search_form_handler.opened .sf_criterion_area {
+ /*display: inherit;*/
+}
+.search_form_handler .sf_title {
+ padding: 8px 10px;
+ margin: 0;
+ color: #fff;
+ background-color: #3f7294;
+ cursor: pointer;
+ /* Pictogram */
+}
+.search_form_handler .sf_title .sft_picto {
+ display: none;
+ /* TODO: Remove this class and the correspondig DOM element if this option is kept. */
+ margin-right: 10px;
+}
+.search_form_handler .sf_title .sft_refresh, .search_form_handler .sf_title .sft_toggler {
+ transition: color 0.2s ease-in-out, transform 0.4s ease-in-out;
+}
+.search_form_handler .sf_title .sft_refresh:hover, .search_form_handler .sf_title .sft_toggler:hover {
+ color: #f1f1f1;
+}
+.search_form_handler .sf_title .sft_refresh {
+ font-size: 10pt;
+ line-height: 13pt;
+}
+.search_form_handler .sf_title .sft_toggler {
+ margin-left: 0.7em;
+}
+.search_form_handler .sf_message {
+ display: none;
+ margin: 8px 8px 0px 8px;
+ border-radius: 0px;
+}
+.search_form_handler .sf_criterion_area {
+ /*display: none;*/
+ padding: 8px 8px 3px 8px;
+ /* padding-bottom must equals to padding-top - .search_form_criteria:margin-bottom */
+ background-color: #fff;
+ /* Common style between criterion and more criterion */
+ /* Criteria tags */
+ /* More criterion */
+}
+.search_form_handler .sf_criterion_area .search_form_criteria, .search_form_handler .sf_criterion_area .sf_more_criterion {
+ position: relative;
+ display: inline-block;
+ margin-bottom: 5px;
+ vertical-align: top;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened, .search_form_handler .sf_criterion_area .sf_more_criterion.opened {
+ margin-bottom: 0px;
+ /* To compensate the .sfc/.sfm_header:padding-bottom: 13px */
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_header, .search_form_handler .sf_criterion_area .sf_more_criterion.opened .sfc_header, .search_form_handler .sf_criterion_area .search_form_criteria.opened .sfm_header, .search_form_handler .sf_criterion_area .sf_more_criterion.opened .sfm_header {
+ border-bottom: none !important;
+ box-shadow: none !important;
+ padding-bottom: 13px;
+ /* Must be equal to .search_form_criteria:margin-bottom + this:padding-bottom */
+}
+.search_form_handler .sf_criterion_area .search_form_criteria > *, .search_form_handler .sf_criterion_area .sf_more_criterion > * {
+ padding: 7px 8px;
+ vertical-align: top;
+ border: 1px solid #ccc;
+ border-radius: 1px;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
+}
+.search_form_handler .sf_criterion_area .search_form_criteria .sfc_form_group, .search_form_handler .sf_criterion_area .sf_more_criterion .sfc_form_group, .search_form_handler .sf_criterion_area .search_form_criteria .sfm_content, .search_form_handler .sf_criterion_area .sf_more_criterion .sfm_content {
+ position: absolute;
+ z-index: -1;
+ min-width: 100%;
+ left: 0px;
+ margin-top: -1px;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria {
+ margin-right: 30px;
+ /* Non editable criteria */
+ /* Draft criteria (modifications not applied) */
+ /* Opened criteria (form group displayed) */
+ /* Top left corner icons */
+ /* Special criterion processing */
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.locked {
+ background-color: #f1f1f1;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.locked .sfc_title {
+ user-select: none;
+ cursor: initial;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_header, .search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_form_group {
+ border-style: dashed;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.draft .sfc_title {
+ font-style: italic;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened {
+ z-index: 1;
+ /* To be over other criterion */
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_toggle {
+ transform: rotateX(-180deg);
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened .sfc_form_group {
+ display: block;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria.opened_left .sfc_form_group {
+ left: auto;
+ right: 0px;
+}
+.search_form_handler .sf_criterion_area .search_form_criteria:not(:last-of-type)::after {
+ /* TODO: Find an elegant way to do this, without hardcoding the content (could be a
EOF
- );
- $oPage->add('');
- $oPage->add('
');
- $oPage->add('
');
- $oPage->add('
'.Dict::S('UI:CSVImport:AdvancedMode+').'
');
- $oPage->add('
');
- $oPage->add('
');
- $oPage->add('
'.Dict::S('ExcelExport:PreparingExport').'
');
- $oPage->add('
'.Dict::S('ExcelExport:Statistics').'
');
- $oPage->add('
');
- $aLabels = array(
- 'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
- 'cancel_button' => Dict::S('UI:Button:Cancel'),
- 'export_button' => Dict::S('ExcelExporter:ExportButton'),
- 'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
- );
- $sJSLabels = json_encode($aLabels);
- $sFilter = addslashes($sFilter);
- $sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php');
- $oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});");
- break;
-
+ );
+ $oPage->add('');
+ $oPage->add('
');
+ $oPage->add('
');
+ $oPage->add('
'.Dict::S('UI:CSVImport:AdvancedMode+').'
');
+ $oPage->add('
');
+ $oPage->add('
');
+ $oPage->add('
'.Dict::S('ExcelExport:PreparingExport').'
');
+ $oPage->add('
'.Dict::S('ExcelExport:Statistics').'
');
+ $oPage->add('
');
+ $aLabels = array(
+ 'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
+ 'cancel_button' => Dict::S('UI:Button:Cancel'),
+ 'export_button' => Dict::S('ExcelExporter:ExportButton'),
+ 'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
+ );
+ $sJSLabels = json_encode($aLabels);
+ $sFilter = addslashes($sFilter);
+ $sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php');
+ $oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});");
+ break;
+
case 'xlsx_start':
- $sFilter = utils::ReadParam('filter', '', false, 'raw_data');
- $bAdvanced = (utils::ReadParam('advanced', 'false') == 'true');
- $oSearch = DBObjectSearch::unserialize($sFilter);
-
- $oExcelExporter = new ExcelExporter();
- $oExcelExporter->SetObjectList($oSearch);
- //$oExcelExporter->SetChunkSize(10); //Only for testing
- $oExcelExporter->SetAdvancedMode($bAdvanced);
- $sToken = $oExcelExporter->SaveState();
- $oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken)));
- break;
-
+ $sFilter = utils::ReadParam('filter', '', false, 'raw_data');
+ $bAdvanced = (utils::ReadParam('advanced', 'false') == 'true');
+ $oSearch = DBObjectSearch::unserialize($sFilter);
+
+ $oExcelExporter = new ExcelExporter();
+ $oExcelExporter->SetObjectList($oSearch);
+ //$oExcelExporter->SetChunkSize(10); //Only for testing
+ $oExcelExporter->SetAdvancedMode($bAdvanced);
+ $sToken = $oExcelExporter->SaveState();
+ $oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken)));
+ break;
+
case 'xlsx_run':
- $sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit');
- ini_set('memory_limit', $sMemoryLimit);
- ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes
-
- $sToken = utils::ReadParam('token', '', false, 'raw_data');
- $oExcelExporter = new ExcelExporter($sToken);
- $aStatus = $oExcelExporter->Run();
- $aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']);
- if ($aStatus['code'] == 'done')
- {
- $aResults['statistics'] = $oExcelExporter->GetStatistics('html');
- }
- $oPage->add(json_encode($aResults));
- break;
-
+ $sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit');
+ ini_set('memory_limit', $sMemoryLimit);
+ ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes
+
+ $sToken = utils::ReadParam('token', '', false, 'raw_data');
+ $oExcelExporter = new ExcelExporter($sToken);
+ $aStatus = $oExcelExporter->Run();
+ $aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']);
+ if ($aStatus['code'] == 'done')
+ {
+ $aResults['statistics'] = $oExcelExporter->GetStatistics('html');
+ }
+ $oPage->add(json_encode($aResults));
+ break;
+
case 'xlsx_download':
- $sToken = utils::ReadParam('token', '', false, 'raw_data');
- $oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
- $oPage->SetContentDisposition('attachment', 'export.xlsx');
- $sFileContent = ExcelExporter::GetExcelFileFromToken($sToken);
- $oPage->add($sFileContent);
- ExcelExporter::CleanupFromToken($sToken);
- break;
-
+ $sToken = utils::ReadParam('token', '', false, 'raw_data');
+ $oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
+ $oPage->SetContentDisposition('attachment', 'export.xlsx');
+ $sFileContent = ExcelExporter::GetExcelFileFromToken($sToken);
+ $oPage->add($sFileContent);
+ ExcelExporter::CleanupFromToken($sToken);
+ break;
+
case 'xlsx_abort':
- // Stop & cleanup an export...
- $sToken = utils::ReadParam('token', '', false, 'raw_data');
- ExcelExporter::CleanupFromToken($sToken);
- break;
+ // Stop & cleanup an export...
+ $sToken = utils::ReadParam('token', '', false, 'raw_data');
+ ExcelExporter::CleanupFromToken($sToken);
+ break;
case 'relation_pdf':
case 'relation_attachment':
- require_once(APPROOT.'core/simplegraph.class.inc.php');
- require_once(APPROOT.'core/relationgraph.class.inc.php');
- require_once(APPROOT.'core/displayablegraph.class.inc.php');
- $sRelation = utils::ReadParam('relation', 'impacts');
- $sDirection = utils::ReadParam('direction', 'down');
-
- $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer');
- $sPageFormat = utils::ReadParam('p', 'A4');
- $sPageOrientation = utils::ReadParam('o', 'L');
- $sTitle = utils::ReadParam('title', '', false, 'raw_data');
- $sPositions = utils::ReadParam('positions', null, false, 'raw_data');
- $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
- $bIncludeList = (bool)utils::ReadParam('include_list', false);
- $sComments = utils::ReadParam('comments', '', false, 'raw_data');
- $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
- $sContextKey = utils::ReadParam('context_key', '', false, 'raw_data');
- $aPositions = null;
- if ($sPositions != null)
- {
- $aPositions = json_decode($sPositions, true);
- }
-
- // Get the list of source objects
- $aSources = utils::ReadParam('sources', array(), false, 'raw_data');
- $aSourceObjects = array();
- foreach($aSources as $sClass => $aIDs)
- {
- $oSearch = new DBObjectSearch($sClass);
- $oSearch->AddCondition('id', $aIDs, 'IN');
- $oSet = new DBObjectSet($oSearch);
- while($oObj = $oSet->Fetch())
- {
- $aSourceObjects[] = $oObj;
- }
- }
- $sSourceClass = '*';
- if (count($aSourceObjects) == 1)
- {
- $sSourceClass = get_class($aSourceObjects[0]);
- }
-
- // Get the list of excluded objects
- $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
- $aExcludedObjects = array();
- foreach($aExcluded as $sClass => $aIDs)
- {
- $oSearch = new DBObjectSearch($sClass);
- $oSearch->AddCondition('id', $aIDs, 'IN');
- $oSet = new DBObjectSet($oSearch);
- while($oObj = $oSet->Fetch())
- {
- $aExcludedObjects[] = $oObj;
- }
- }
-
- $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
- if ($sDirection == 'up')
- {
- $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
- }
- else
- {
- $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
- }
-
- // Remove excluded classes from the graph
- if (count($aExcludedClasses) > 0)
- {
- $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
- foreach($oIterator as $oNode)
- {
- $oObj = $oNode->GetProperty('object');
- if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
- {
- $oRelGraph->FilterNode($oNode);
- }
- }
- }
-
- $oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation);
- $oPage->SetContentDisposition('attachment', $sTitle.'.pdf');
-
- $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
- $oGraph->InitFromGraphviz();
- if ($aPositions != null)
- {
- $oGraph->UpdatePositions($aPositions);
- }
+ require_once(APPROOT.'core/simplegraph.class.inc.php');
+ require_once(APPROOT.'core/relationgraph.class.inc.php');
+ require_once(APPROOT.'core/displayablegraph.class.inc.php');
+ $sRelation = utils::ReadParam('relation', 'impacts');
+ $sDirection = utils::ReadParam('direction', 'down');
- $aGroups = array();
- $oIterator = new RelationTypeIterator($oGraph, 'Node');
- foreach($oIterator as $oNode)
- {
- if ($oNode instanceof DisplayableGroupNode)
+ $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer');
+ $sPageFormat = utils::ReadParam('p', 'A4');
+ $sPageOrientation = utils::ReadParam('o', 'L');
+ $sTitle = utils::ReadParam('title', '', false, 'raw_data');
+ $sPositions = utils::ReadParam('positions', null, false, 'raw_data');
+ $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
+ $bIncludeList = (bool)utils::ReadParam('include_list', false);
+ $sComments = utils::ReadParam('comments', '', false, 'raw_data');
+ $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
+ $sContextKey = utils::ReadParam('context_key', '', false, 'raw_data');
+ $aPositions = null;
+ if ($sPositions != null)
{
- $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects();
+ $aPositions = json_decode($sPositions, true);
}
- }
- // First page is the graph
- $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey);
- if ($bIncludeList)
- {
- // Then the lists of objects (one table per finalclass)
- $aResults = array();
- $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
- foreach($oIterator as $oNode)
- {
- $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object
- if ($oObj)
- {
- $sObjClass = get_class($oObj);
- if (!array_key_exists($sObjClass, $aResults))
- {
- $aResults[$sObjClass] = array();
- }
- $aResults[$sObjClass][] = $oObj;
- }
- }
-
- $oPage->get_tcpdf()->AddPage();
- $oPage->get_tcpdf()->SetFont('dejavusans', '', 10, '', true); // Reset the font size to its default
- $oPage->add('');
- $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
- foreach($aResults as $sListClass => $aObjects)
- {
- set_time_limit($iLoopTimeLimit*count($aObjects));
- $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
- $oSet->SetShowObsoleteData(utils::ShowObsoleteData());
- $sHtml = "\n";
- $oPage->add($sHtml);
- cmdbAbstractObject::DisplaySet($oPage, $oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass));
- $oPage->p(''); // Some space
- }
-
- // Then the content of the groups (one table per group)
- if (count($aGroups) > 0)
- {
- $oPage->get_tcpdf()->AddPage();
- $oPage->add('');
- foreach($aGroups as $idx => $aObjects)
- {
- set_time_limit($iLoopTimeLimit*count($aObjects));
- $sListClass = get_class(current($aObjects));
- $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
- $sHtml = "\n";
- $oPage->add($sHtml);
- cmdbAbstractObject::DisplaySet($oPage, $oSet);
- $oPage->p(''); // Some space
- }
- }
- }
- if ($operation == 'relation_attachment')
- {
- $sObjClass = utils::ReadParam('obj_class', '', false, 'class');
- $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
-
- // Save the generated PDF as an attachment
- $sPDF = $oPage->get_pdf();
- $oPage = new ajax_page('');
- $oAttachment = new Attachment();
- $oAttachment->Set('item_class', $sObjClass);
- $oAttachment->Set('item_id', $iObjKey);
- $oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf');
- $oAttachment->Set('contents', $oDoc);
- $iAttachmentId = $oAttachment->DBInsert();
- $aRet = array(
- 'status' => 'ok',
- 'att_id' => $iAttachmentId,
- );
- $oPage->add(json_encode($aRet));
- }
- break;
-
- case 'relation_json':
- require_once(APPROOT.'core/simplegraph.class.inc.php');
- require_once(APPROOT.'core/relationgraph.class.inc.php');
- require_once(APPROOT.'core/displayablegraph.class.inc.php');
- $sRelation = utils::ReadParam('relation', 'impacts');
- $sDirection = utils::ReadParam('direction', 'down');
- $iGroupingThreshold = utils::ReadParam('g', 5);
- $sPositions = utils::ReadParam('positions', null, false, 'raw_data');
- $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
- $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
- $sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data');
- $aPositions = null;
- if ($sPositions != null)
- {
- $aPositions = json_decode($sPositions, true);
- }
-
// Get the list of source objects
- $aSources = utils::ReadParam('sources', array(), false, 'raw_data');
- $aSourceObjects = array();
- foreach($aSources as $sClass => $aIDs)
- {
- $oSearch = new DBObjectSearch($sClass);
- $oSearch->AddCondition('id', $aIDs, 'IN');
- $oSet = new DBObjectSet($oSearch);
- while($oObj = $oSet->Fetch())
+ $aSources = utils::ReadParam('sources', array(), false, 'raw_data');
+ $aSourceObjects = array();
+ foreach($aSources as $sClass => $aIDs)
{
- $aSourceObjects[] = $oObj;
- }
- }
-
- // Get the list of excluded objects
- $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
- $aExcludedObjects = array();
- foreach($aExcluded as $sClass => $aIDs)
- {
- $oSearch = new DBObjectSearch($sClass);
- $oSearch->AddCondition('id', $aIDs, 'IN');
- $oSet = new DBObjectSet($oSearch);
- while($oObj = $oSet->Fetch())
- {
- $aExcludedObjects[] = $oObj;
- }
- }
-
- // Compute the graph
- $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
- if ($sDirection == 'up')
- {
- $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
- }
- else
- {
- $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
- }
-
- // Remove excluded classes from the graph
- if (count($aExcludedClasses) > 0)
- {
- $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
- foreach($oIterator as $oNode)
- {
- $oObj = $oNode->GetProperty('object');
- if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
+ $oSearch = new DBObjectSearch($sClass);
+ $oSearch->AddCondition('id', $aIDs, 'IN');
+ $oSet = new DBObjectSet($oSearch);
+ while ($oObj = $oSet->Fetch())
{
- $oRelGraph->FilterNode($oNode);
+ $aSourceObjects[] = $oObj;
}
}
- }
-
- $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
- $oGraph->InitFromGraphviz();
- if ($aPositions != null)
- {
- $oGraph->UpdatePositions($aPositions);
- }
- $oPage->add($oGraph->GetAsJSON($sContextKey));
- $oPage->SetContentType('application/json');
- break;
-
- case 'relation_groups':
- $aGroups = utils::ReadParam('groups');
- $iBlock = 1; // Zero is not a valid blockid
- foreach($aGroups as $idx => $aDefinition)
- {
- $sListClass = $aDefinition['class'];
- $oSearch = new DBObjectSearch($sListClass);
- $oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
- $oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
- $oPage->add("".Dict::Format('UI:RelationGroupNumber_N', (1+$idx))."
\n");
- $oPage->add("\n");
- $oBlock = new DisplayBlock($oSearch, 'list');
- $oBlock->Display($oPage, 'group_'.$iBlock++);
- $oPage->p(' '); // Some space ?
- }
- break;
+ $sSourceClass = '*';
+ if (count($aSourceObjects) == 1)
+ {
+ $sSourceClass = get_class($aSourceObjects[0]);
+ }
- case 'relation_lists':
- $aLists = utils::ReadParam('lists');
- $iBlock = 1; // Zero is not a valid blockid
- foreach($aLists as $sListClass => $aKeys)
- {
- $oSearch = new DBObjectSearch($sListClass);
- $oSearch->AddCondition('id', $aKeys, 'IN');
- $oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
- $oPage->add("\n");
- $oBlock = new DisplayBlock($oSearch, 'list');
- $oBlock->Display($oPage, 'list_'.$iBlock++, array('table_id' => 'ImpactAnalysis_'.$sListClass));
- $oPage->p(' '); // Some space ?
- }
- break;
-
- case 'ticket_impact':
- require_once(APPROOT.'core/simplegraph.class.inc.php');
- require_once(APPROOT.'core/relationgraph.class.inc.php');
- require_once(APPROOT.'core/displayablegraph.class.inc.php');
- $sRelation = utils::ReadParam('relation', 'impacts');
- $sDirection = utils::ReadParam('direction', 'down');
- $iGroupingThreshold = utils::ReadParam('g', 5);
- $sClass = utils::ReadParam('class', '', false, 'class');
- $sAttCode = utils::ReadParam('attcode', 'functionalcis_list');
- $sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code');
- $sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual');
- $iId = (int)utils::ReadParam('id', 0, false, 'integer');
-
- // Get the list of source objects
- $oTicket = MetaModel::GetObject($sClass, $iId);
- $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
- $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
- $oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote);
- $sRemoteClass = $oExtKeyToRemote->GetTargetClass();
- $oSet = $oTicket->Get($sAttCode);
- $aSourceObjects = array();
- $aExcludedObjects = array();
- while($oLnk = $oSet->Fetch())
- {
- if ($oLnk->Get($sImpactAttCode) == 'manual')
+ // Get the list of excluded objects
+ $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
+ $aExcludedObjects = array();
+ foreach($aExcluded as $sClass => $aIDs)
{
- $aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
- }
- if ($oLnk->Get($sImpactAttCode) == 'not_impacted')
- {
- $aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
- }
- }
-
- // Compute the graph
- $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
- if ($sDirection == 'up')
- {
- $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
- }
- else
- {
- $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects);
- }
-
- $aResults = $oRelGraph->GetObjectsByClass();
- $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
-
- $sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
- $oAppContext = new ApplicationContext();
- $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket));
- break;
-
- case 'export_build':
- register_shutdown_function(function()
- {
- $aErr = error_get_last();
- if (($aErr !== null) && ($aErr['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)))
- {
- ob_end_clean();
- echo json_encode(array('code' => 'error', 'percentage' => 100, 'message' => Dict::Format('UI:Error_Details', $aErr['message'])));
- }
- });
- try
- {
- $token = utils::ReadParam('token', null);
- $aResult = array('code' => 'error', 'percentage' => 100, 'message' => "Export not found for token: '$token'"); // Fallback error, just in case
- $data = '';
- if ($token === null)
- {
- $sFormat = utils::ReadParam('format', '');
- $sExpression = utils::ReadParam('expression', null, false, 'raw_data');
- $iQueryId = utils::ReadParam('query', null);
- if ($sExpression === null)
+ $oSearch = new DBObjectSearch($sClass);
+ $oSearch->AddCondition('id', $aIDs, 'IN');
+ $oSet = new DBObjectSet($oSearch);
+ while ($oObj = $oSet->Fetch())
{
- $oQuerySearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $iQueryId));
- $oQuerySearch->UpdateContextFromUser();
- $oQueries = new DBObjectSet($oQuerySearch);
- if ($oQueries->Count() > 0)
- {
- $oQuery = $oQueries->Fetch();
- $sExpression = $oQuery->Get('oql');
- }
- else
- {
- $aResult = array('code' => 'error', 'percentage' => 100, 'message' => "Invalid query phrasebook identifier: '$iQueryId'");
- }
+ $aExcludedObjects[] = $oObj;
}
- if($sExpression !== null)
- {
- $oSearch = DBObjectSearch::FromOQL($sExpression);
- $oSearch->UpdateContextFromUser();
- $oExporter = BulkExport::FindExporter($sFormat, $oSearch);
- $oExporter->SetObjectList($oSearch);
- $oExporter->SetFormat($sFormat);
- $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
- $oExporter->ReadParameters();
- }
-
- // First pass, generate the headers
- $data .= $oExporter->GetHeader();
+ }
+
+ $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
+ if ($sDirection == 'up')
+ {
+ $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
}
else
{
- $oExporter = BulkExport::FindExporterFromToken($token);
+ $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
}
-
- if ($oExporter)
+
+ // Remove excluded classes from the graph
+ if (count($aExcludedClasses) > 0)
{
- $data .= $oExporter->GetNextChunk($aResult);
- if ($aResult['code'] != 'done')
+ $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
+ foreach($oIterator as $oNode)
{
- $oExporter->AppendToTmpFile($data);
- $aResult['token'] = $oExporter->SaveState();
- }
- else
- {
- // Last pass
- $data .= $oExporter->GetFooter();
- $oExporter->AppendToTmpFile($data);
- $aResult['token'] = $oExporter->SaveState();
- if (substr($oExporter->GetMimeType(), 0, 5) == 'text/')
+ $oObj = $oNode->GetProperty('object');
+ if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
{
- // Result must be encoded in UTF-8 to be passed as part of a JSON structure
- $sCharset = $oExporter->GetCharacterSet();
- if (strtoupper($sCharset) != 'UTF-8')
+ $oRelGraph->FilterNode($oNode);
+ }
+ }
+ }
+
+ $oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation);
+ $oPage->SetContentDisposition('attachment', $sTitle.'.pdf');
+
+ $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
+ $oGraph->InitFromGraphviz();
+ if ($aPositions != null)
+ {
+ $oGraph->UpdatePositions($aPositions);
+ }
+
+ $aGroups = array();
+ $oIterator = new RelationTypeIterator($oGraph, 'Node');
+ foreach($oIterator as $oNode)
+ {
+ if ($oNode instanceof DisplayableGroupNode)
+ {
+ $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects();
+ }
+ }
+ // First page is the graph
+ $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey);
+
+ if ($bIncludeList)
+ {
+ // Then the lists of objects (one table per finalclass)
+ $aResults = array();
+ $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
+ foreach($oIterator as $oNode)
+ {
+ $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object
+ if ($oObj)
+ {
+ $sObjClass = get_class($oObj);
+ if (!array_key_exists($sObjClass, $aResults))
{
- $aResult['text_result'] = iconv($sCharset, 'UTF-8', file_get_contents($oExporter->GetTmpFilePath()));
+ $aResults[$sObjClass] = array();
+ }
+ $aResults[$sObjClass][] = $oObj;
+ }
+ }
+
+ $oPage->get_tcpdf()->AddPage();
+ $oPage->get_tcpdf()->SetFont('dejavusans', '', 10, '', true); // Reset the font size to its default
+ $oPage->add('');
+ $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
+ foreach($aResults as $sListClass => $aObjects)
+ {
+ set_time_limit($iLoopTimeLimit * count($aObjects));
+ $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
+ $oSet->SetShowObsoleteData(utils::ShowObsoleteData());
+ $sHtml = "\n";
+ $oPage->add($sHtml);
+ cmdbAbstractObject::DisplaySet($oPage, $oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass));
+ $oPage->p(''); // Some space
+ }
+
+ // Then the content of the groups (one table per group)
+ if (count($aGroups) > 0)
+ {
+ $oPage->get_tcpdf()->AddPage();
+ $oPage->add('');
+ foreach($aGroups as $idx => $aObjects)
+ {
+ set_time_limit($iLoopTimeLimit * count($aObjects));
+ $sListClass = get_class(current($aObjects));
+ $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
+ $sHtml = "\n";
+ $oPage->add($sHtml);
+ cmdbAbstractObject::DisplaySet($oPage, $oSet);
+ $oPage->p(''); // Some space
+ }
+ }
+ }
+ if ($operation == 'relation_attachment')
+ {
+ $sObjClass = utils::ReadParam('obj_class', '', false, 'class');
+ $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
+
+ // Save the generated PDF as an attachment
+ $sPDF = $oPage->get_pdf();
+ $oPage = new ajax_page('');
+ $oAttachment = new Attachment();
+ $oAttachment->Set('item_class', $sObjClass);
+ $oAttachment->Set('item_id', $iObjKey);
+ $oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf');
+ $oAttachment->Set('contents', $oDoc);
+ $iAttachmentId = $oAttachment->DBInsert();
+ $aRet = array(
+ 'status' => 'ok',
+ 'att_id' => $iAttachmentId,
+ );
+ $oPage->add(json_encode($aRet));
+ }
+ break;
+
+ case 'relation_json':
+ require_once(APPROOT.'core/simplegraph.class.inc.php');
+ require_once(APPROOT.'core/relationgraph.class.inc.php');
+ require_once(APPROOT.'core/displayablegraph.class.inc.php');
+ $sRelation = utils::ReadParam('relation', 'impacts');
+ $sDirection = utils::ReadParam('direction', 'down');
+ $iGroupingThreshold = utils::ReadParam('g', 5);
+ $sPositions = utils::ReadParam('positions', null, false, 'raw_data');
+ $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data');
+ $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data');
+ $sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data');
+ $aPositions = null;
+ if ($sPositions != null)
+ {
+ $aPositions = json_decode($sPositions, true);
+ }
+
+ // Get the list of source objects
+ $aSources = utils::ReadParam('sources', array(), false, 'raw_data');
+ $aSourceObjects = array();
+ foreach($aSources as $sClass => $aIDs)
+ {
+ $oSearch = new DBObjectSearch($sClass);
+ $oSearch->AddCondition('id', $aIDs, 'IN');
+ $oSet = new DBObjectSet($oSearch);
+ while ($oObj = $oSet->Fetch())
+ {
+ $aSourceObjects[] = $oObj;
+ }
+ }
+
+ // Get the list of excluded objects
+ $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
+ $aExcludedObjects = array();
+ foreach($aExcluded as $sClass => $aIDs)
+ {
+ $oSearch = new DBObjectSearch($sClass);
+ $oSearch->AddCondition('id', $aIDs, 'IN');
+ $oSet = new DBObjectSet($oSearch);
+ while ($oObj = $oSet->Fetch())
+ {
+ $aExcludedObjects[] = $oObj;
+ }
+ }
+
+ // Compute the graph
+ $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
+ if ($sDirection == 'up')
+ {
+ $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
+ }
+ else
+ {
+ $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
+ }
+
+ // Remove excluded classes from the graph
+ if (count($aExcludedClasses) > 0)
+ {
+ $oIterator = new RelationTypeIterator($oRelGraph, 'Node');
+ foreach($oIterator as $oNode)
+ {
+ $oObj = $oNode->GetProperty('object');
+ if ($oObj && in_array(get_class($oObj), $aExcludedClasses))
+ {
+ $oRelGraph->FilterNode($oNode);
+ }
+ }
+ }
+
+ $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
+ $oGraph->InitFromGraphviz();
+ if ($aPositions != null)
+ {
+ $oGraph->UpdatePositions($aPositions);
+ }
+ $oPage->add($oGraph->GetAsJSON($sContextKey));
+ $oPage->SetContentType('application/json');
+ break;
+
+ case 'relation_groups':
+ $aGroups = utils::ReadParam('groups');
+ $iBlock = 1; // Zero is not a valid blockid
+ foreach($aGroups as $idx => $aDefinition)
+ {
+ $sListClass = $aDefinition['class'];
+ $oSearch = new DBObjectSearch($sListClass);
+ $oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
+ $oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
+ $oPage->add("".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx))."
\n");
+ $oPage->add("\n");
+ $oBlock = new DisplayBlock($oSearch, 'list');
+ $oBlock->Display($oPage, 'group_'.$iBlock++);
+ $oPage->p(' '); // Some space ?
+ }
+ break;
+
+ case 'relation_lists':
+ $aLists = utils::ReadParam('lists');
+ $iBlock = 1; // Zero is not a valid blockid
+ foreach($aLists as $sListClass => $aKeys)
+ {
+ $oSearch = new DBObjectSearch($sListClass);
+ $oSearch->AddCondition('id', $aKeys, 'IN');
+ $oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
+ $oPage->add("\n");
+ $oBlock = new DisplayBlock($oSearch, 'list');
+ $oBlock->Display($oPage, 'list_'.$iBlock++, array('table_id' => 'ImpactAnalysis_'.$sListClass));
+ $oPage->p(' '); // Some space ?
+ }
+ break;
+
+ case 'ticket_impact':
+ require_once(APPROOT.'core/simplegraph.class.inc.php');
+ require_once(APPROOT.'core/relationgraph.class.inc.php');
+ require_once(APPROOT.'core/displayablegraph.class.inc.php');
+ $sRelation = utils::ReadParam('relation', 'impacts');
+ $sDirection = utils::ReadParam('direction', 'down');
+ $iGroupingThreshold = utils::ReadParam('g', 5);
+ $sClass = utils::ReadParam('class', '', false, 'class');
+ $sAttCode = utils::ReadParam('attcode', 'functionalcis_list');
+ $sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code');
+ $sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual');
+ $iId = (int)utils::ReadParam('id', 0, false, 'integer');
+
+ // Get the list of source objects
+ $oTicket = MetaModel::GetObject($sClass, $iId);
+ $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+ $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+ $oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote);
+ $sRemoteClass = $oExtKeyToRemote->GetTargetClass();
+ $oSet = $oTicket->Get($sAttCode);
+ $aSourceObjects = array();
+ $aExcludedObjects = array();
+ while ($oLnk = $oSet->Fetch())
+ {
+ if ($oLnk->Get($sImpactAttCode) == 'manual')
+ {
+ $aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
+ }
+ if ($oLnk->Get($sImpactAttCode) == 'not_impacted')
+ {
+ $aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote));
+ }
+ }
+
+ // Compute the graph
+ $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth', 20);
+ if ($sDirection == 'up')
+ {
+ $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth);
+ }
+ else
+ {
+ $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects);
+ }
+
+ $aResults = $oRelGraph->GetObjectsByClass();
+ $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
+
+ $sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
+ $oAppContext = new ApplicationContext();
+ $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket));
+ break;
+
+ case 'export_build':
+ register_shutdown_function(function () {
+ $aErr = error_get_last();
+ if (($aErr !== null) && ($aErr['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)))
+ {
+ ob_end_clean();
+ echo json_encode(array('code' => 'error', 'percentage' => 100, 'message' => Dict::Format('UI:Error_Details', $aErr['message'])));
+ }
+ });
+ try
+ {
+ $token = utils::ReadParam('token', null);
+ $aResult = array('code' => 'error', 'percentage' => 100, 'message' => "Export not found for token: '$token'"); // Fallback error, just in case
+ $data = '';
+ if ($token === null)
+ {
+ $sFormat = utils::ReadParam('format', '');
+ $sExpression = utils::ReadParam('expression', null, false, 'raw_data');
+ $iQueryId = utils::ReadParam('query', null);
+ if ($sExpression === null)
+ {
+ $oQuerySearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', array('query_id' => $iQueryId));
+ $oQuerySearch->UpdateContextFromUser();
+ $oQueries = new DBObjectSet($oQuerySearch);
+ if ($oQueries->Count() > 0)
+ {
+ $oQuery = $oQueries->Fetch();
+ $sExpression = $oQuery->Get('oql');
}
else
{
- $aResult['text_result'] = file_get_contents($oExporter->GetTmpFilePath());
+ $aResult = array('code' => 'error', 'percentage' => 100, 'message' => "Invalid query phrasebook identifier: '$iQueryId'");
}
- $aResult['mime_type'] = $oExporter->GetMimeType();
}
- $aResult['message'] = Dict::Format('Core:BulkExport:ClickHereToDownload_FileName', $oExporter->GetDownloadFileName());
+ if ($sExpression !== null)
+ {
+ $oSearch = DBObjectSearch::FromOQL($sExpression);
+ $oSearch->UpdateContextFromUser();
+ $oExporter = BulkExport::FindExporter($sFormat, $oSearch);
+ $oExporter->SetObjectList($oSearch);
+ $oExporter->SetFormat($sFormat);
+ $oExporter->SetChunkSize(EXPORTER_DEFAULT_CHUNK_SIZE);
+ $oExporter->ReadParameters();
+ }
+
+ // First pass, generate the headers
+ $data .= $oExporter->GetHeader();
}
+ else
+ {
+ $oExporter = BulkExport::FindExporterFromToken($token);
+ }
+
+ if ($oExporter)
+ {
+ $data .= $oExporter->GetNextChunk($aResult);
+ if ($aResult['code'] != 'done')
+ {
+ $oExporter->AppendToTmpFile($data);
+ $aResult['token'] = $oExporter->SaveState();
+ }
+ else
+ {
+ // Last pass
+ $data .= $oExporter->GetFooter();
+ $oExporter->AppendToTmpFile($data);
+ $aResult['token'] = $oExporter->SaveState();
+ if (substr($oExporter->GetMimeType(), 0, 5) == 'text/')
+ {
+ // Result must be encoded in UTF-8 to be passed as part of a JSON structure
+ $sCharset = $oExporter->GetCharacterSet();
+ if (strtoupper($sCharset) != 'UTF-8')
+ {
+ $aResult['text_result'] = iconv($sCharset, 'UTF-8', file_get_contents($oExporter->GetTmpFilePath()));
+ }
+ else
+ {
+ $aResult['text_result'] = file_get_contents($oExporter->GetTmpFilePath());
+ }
+ $aResult['mime_type'] = $oExporter->GetMimeType();
+ }
+ $aResult['message'] = Dict::Format('Core:BulkExport:ClickHereToDownload_FileName', $oExporter->GetDownloadFileName());
+ }
+ }
+ $oPage->add(json_encode($aResult));
+ } catch (BulkExportException $e)
+ {
+ $aResult = array('code' => 'error', 'percentage' => 100, 'message' => $e->GetLocalizedMessage());
+ $oPage->add(json_encode($aResult));
+ } catch (Exception $e)
+ {
+ $aResult = array('code' => 'error', 'percentage' => 100, 'message' => $e->getMessage());
+ $oPage->add(json_encode($aResult));
}
- $oPage->add(json_encode($aResult));
- }
- catch(BulkExportException $e)
- {
- $aResult = array('code' => 'error', 'percentage' => 100, 'message' => $e->GetLocalizedMessage());
- $oPage->add(json_encode($aResult));
- }
- catch(Exception $e)
- {
- $aResult = array('code' => 'error', 'percentage' => 100, 'message' => $e->getMessage());
- $oPage->add(json_encode($aResult));
- }
- break;
-
+ break;
+
case 'export_download':
- $token = utils::ReadParam('token', null);
- if ($token !== null)
- {
- $oExporter = BulkExport::FindExporterFromToken($token);
- if ($oExporter)
+ $token = utils::ReadParam('token', null);
+ if ($token !== null)
{
- $sMimeType = $oExporter->GetMimeType();
- if (substr($sMimeType, 0, 5) == 'text/')
+ $oExporter = BulkExport::FindExporterFromToken($token);
+ if ($oExporter)
{
- $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet());
+ $sMimeType = $oExporter->GetMimeType();
+ if (substr($sMimeType, 0, 5) == 'text/')
+ {
+ $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet());
+ }
+ $oPage->SetContentType($sMimeType);
+ $oPage->SetContentDisposition('attachment', $oExporter->GetDownloadFileName());
+ $oPage->add(file_get_contents($oExporter->GetTmpFilePath()));
}
- $oPage->SetContentType($sMimeType);
- $oPage->SetContentDisposition('attachment', $oExporter->GetDownloadFileName());
- $oPage->add(file_get_contents($oExporter->GetTmpFilePath()));
}
- }
- break;
-
+ break;
+
case 'export_cancel':
- $token = utils::ReadParam('token', null);
- if ($token !== null)
- {
- $oExporter = BulkExport::FindExporterFromToken($token);
- if ($oExporter)
+ $token = utils::ReadParam('token', null);
+ if ($token !== null)
{
- $oExporter->Cleanup();
- }
- }
- $aResult = array('code' => 'error', 'percentage' => 100, 'message' => Dict::S('Core:BulkExport:ExportCancelledByUser'));
- $oPage->add(json_encode($aResult));
- break;
-
- case 'extend_lock':
- $sObjClass = utils::ReadParam('obj_class', '', false, 'class');
- $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
- $sToken = utils::ReadParam('token', 0, false, 'raw_data');
- $aResult = iTopOwnershipLock::ExtendLock($sObjClass, $iObjKey, $sToken);
- if (!$aResult['status'])
- {
- if ($aResult['operation'] == 'lost')
- {
- $sName = $aResult['owner']->GetName();
- if ($aResult['owner']->Get('contactid') != 0)
+ $oExporter = BulkExport::FindExporterFromToken($token);
+ if ($oExporter)
{
- $sName .= ' ('.$aResult['owner']->Get('contactid_friendlyname').')';
+ $oExporter->Cleanup();
}
- $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName);
- $aResult['popup_message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User_Explanation', $sName);
}
- else if ($aResult['operation'] == 'expired')
+ $aResult = array('code' => 'error', 'percentage' => 100, 'message' => Dict::S('Core:BulkExport:ExportCancelledByUser'));
+ $oPage->add(json_encode($aResult));
+ break;
+
+ case 'extend_lock':
+ $sObjClass = utils::ReadParam('obj_class', '', false, 'class');
+ $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer');
+ $sToken = utils::ReadParam('token', 0, false, 'raw_data');
+ $aResult = iTopOwnershipLock::ExtendLock($sObjClass, $iObjKey, $sToken);
+ if (!$aResult['status'])
{
- $aResult['message'] = Dict::S('UI:CurrentObjectLockExpired');
- $aResult['popup_message'] = Dict::S('UI:CurrentObjectLockExpired_Explanation');
+ if ($aResult['operation'] == 'lost')
+ {
+ $sName = $aResult['owner']->GetName();
+ if ($aResult['owner']->Get('contactid') != 0)
+ {
+ $sName .= ' ('.$aResult['owner']->Get('contactid_friendlyname').')';
+ }
+ $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName);
+ $aResult['popup_message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User_Explanation', $sName);
+ }
+ else
+ {
+ if ($aResult['operation'] == 'expired')
+ {
+ $aResult['message'] = Dict::S('UI:CurrentObjectLockExpired');
+ $aResult['popup_message'] = Dict::S('UI:CurrentObjectLockExpired_Explanation');
+ }
+ }
}
- }
- $oPage->add(json_encode($aResult));
- break;
-
+ $oPage->add(json_encode($aResult));
+ break;
+
case 'watchdog':
- $oPage->add('ok'); // Better for debugging...
- break;
-
+ $oPage->add('ok'); // Better for debugging...
+ break;
+
case 'cke_img_upload':
// Image uploaded via CKEditor
$aResult = array(
- 'uploaded' => 0,
- 'fileName' => '',
- 'url' => '',
- 'icon' => '',
- 'msg' => '',
- 'att_id' => 0,
- 'preview' => 'false',
+ 'uploaded' => 0,
+ 'fileName' => '',
+ 'url' => '',
+ 'icon' => '',
+ 'msg' => '',
+ 'att_id' => 0,
+ 'preview' => 'false',
);
-
+
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
$sTempId = utils::ReadParam('temp_id', '');
if (empty($sObjClass))
@@ -2333,9 +2369,9 @@ EOF
$oAttachment->Set('item_class', $sObjClass);
$oAttachment->SetDefaultOrgId();
$oAttachment->Set('contents', $oDoc);
- $oAttachment->Set('secret', sprintf ('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
+ $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
$iAttId = $oAttachment->DBInsert();
-
+
$aResult['uploaded'] = 1;
$aResult['msg'] = htmlentities($oDoc->GetFileName(), ENT_QUOTES, 'UTF-8');
$aResult['fileName'] = $oDoc->GetFileName();
@@ -2350,15 +2386,14 @@ EOF
{
$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
}
- }
- catch (FileUploadException $e)
+ } catch (FileUploadException $e)
{
$aResult['error'] = $e->GetMessage();
}
}
$oPage->add(json_encode($aResult));
break;
-
+
case 'cke_upload_and_browse':
$sTempId = utils::ReadParam('temp_id');
$sObjClass = utils::ReadParam('obj_class', '', false, 'class');
@@ -2375,32 +2410,31 @@ EOF
$oAttachment->Set('item_class', $sObjClass);
$oAttachment->SetDefaultOrgId();
$oAttachment->Set('contents', $oDoc);
- $oAttachment->Set('secret', sprintf ('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
+ $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess
$iAttId = $oAttachment->DBInsert();
-
+
}
- }
- catch (FileUploadException $e)
+ } catch (FileUploadException $e)
{
// fail silently ??
- }
+ }
// Fall though !! => browse
-
+
case 'cke_browse':
$oPage = new NiceWebPage(Dict::S('UI:BrowseInlineImages'));
$oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/magnific-popup.css');
$oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.magnific-popup.min.js');
$sImgUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL;
-
+
$sTempId = utils::ReadParam('temp_id');
$sClass = utils::ReadParam('obj_class', '', false, 'class');
$iObjectId = utils::ReadParam('obj_key', 0, false, 'integer');
$sCKEditorFuncNum = utils::ReadParam('CKEditorFuncNum', '');
-
+
$sPostUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum;
-
+
$oPage->add_style(
-<<add(
-<<
EOF
);
-
+
$oPage->add_script(
-<<
add_ready_script(
-<<');
$('#upload_form').submit();
@@ -2475,14 +2509,14 @@ EOF
$sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
$oPage->add("");
- break;
-
+ break;
+
case 'custom_fields_update':
$oPage->SetContentType('application/json');
$sAttCode = utils::ReadParam('attcode', '');
@@ -2518,21 +2552,28 @@ EOF
$aRenderRes = $oRenderer->Render($aRequestedFields);
$aResult['form']['updated_fields'] = $aRenderRes;
- }
- catch (Exception $e)
+ } catch (Exception $e)
{
$aResult['error'] = $e->getMessage();
}
$oPage->add(json_encode($aResult));
break;
+ case 'dict':
+ $sSignature = Utils::ReadParam('s', ''); // Sanitization prevents / and ..
+ $oPage = new ajax_page(""); // New page to cleanup the no_cache done above
+ $oPage->SetContentType('text/javascript');
+ $oPage->add_header('Cache-control: public, max-age=86400'); // Cache for 24 hours
+ $oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
+ $oPage->add(file_get_contents(Utils::GetCachePath().$sSignature.'.js'));
+ break;
+
default:
- $oPage->p("Invalid query.");
+ $oPage->p("Invalid query.");
}
$oPage->output();
-}
-catch (Exception $e)
+} catch (Exception $e)
{
// note: transform to cope with XSS attacks
echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
diff --git a/pages/ajax.searchform.php b/pages/ajax.searchform.php
new file mode 100644
index 000000000..faa25a183
--- /dev/null
+++ b/pages/ajax.searchform.php
@@ -0,0 +1,139 @@
+
+ *
+ */
+
+use Combodo\iTop\Application\Search\AjaxSearchException;
+use Combodo\iTop\Application\Search\CriterionParser;
+
+require_once('../approot.inc.php');
+require_once(APPROOT.'/application/application.inc.php');
+require_once(APPROOT.'/application/webpage.class.inc.php');
+require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
+require_once(APPROOT.'/application/startup.inc.php');
+require_once(APPROOT.'/application/user.preferences.class.inc.php');
+require_once(APPROOT.'/application/loginwebpage.class.inc.php');
+require_once(APPROOT.'/sources/application/search/ajaxsearchexception.class.inc.php');
+require_once(APPROOT.'/sources/application/search/criterionparser.class.inc.php');
+require_once(APPROOT.'/application/wizardhelper.class.inc.php');
+
+try
+{
+ if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx(null /* any portal */, false, LoginWebPage::EXIT_RETURN))
+ {
+ throw new SecurityException('You must be logged in');
+ }
+
+ $sParams = utils::ReadParam('params', '', false, 'raw_data');
+ if (!$sParams)
+ {
+ throw new AjaxSearchException("Invalid query (empty filter)", 400);
+ }
+
+ $oPage = new ajax_page("");
+ $oPage->no_cache();
+ $oPage->SetContentType('text/html');
+
+ $sListParams = utils::ReadParam('list_params', '{}', false, 'raw_data');
+ $aListParams = (array)json_decode($sListParams, true);
+
+ $aParams = json_decode($sParams, true);
+ if (array_key_exists('hidden_criteria', $aListParams))
+ {
+ $sHiddenCriteria = $aListParams['hidden_criteria'];
+ }
+ else
+ {
+ $sHiddenCriteria = '';
+ }
+ $oFilter = CriterionParser::Parse($aParams['base_oql'], $aParams['criterion'], $sHiddenCriteria);
+
+ if (isset($aListParams['debug']))
+ {
+ $sOQL = $oFilter->ToOQL();
+ $oPage->add("\n");
+ }
+
+ //IssueLog::Info('Search OQL: "'.$oFilter->ToOQL().'"');
+ $oDisplayBlock = new DisplayBlock($oFilter, 'list', false);
+
+ foreach($aListParams as $key => $value)
+ {
+ $aExtraParams[$key] = $value;
+ }
+
+ if (array_key_exists('table_inner_id', $aListParams))
+ {
+ $sListId = $aListParams['table_inner_id'];
+ }
+
+ if (array_key_exists('json', $aListParams))
+ {
+ $aJson = $aListParams['json'];
+ $sJson = json_encode($aJson);
+ $oWizardHelper = WizardHelper::FromJSON($sJson);
+ $oObj = $oWizardHelper->GetTargetObject();
+ if (array_key_exists('query_params', $aExtraParams))
+ {
+ $aExtraParams['query_params']['this'] = $oObj;
+ }
+ else
+ {
+ $aExtraParams['query_params'] = array('this' => $oObj);
+ }
+
+// // Current extkey value, so we can display event if it is not available anymore (eg. archived).
+// $iCurrentExtKeyId = (is_null($oObj)) ? 0 : $oObj->Get($this->sAttCode);
+// $aExtraParams['current_extkey_id'] = $iCurrentExtKeyId;
+
+ }
+
+ $aExtraParams['display_limit'] = true;
+ $aExtraParams['truncated'] = true;
+ if (isset($sListId))
+ {
+ $oDisplayBlock->Display($oPage, $sListId, $aExtraParams);
+ }
+ else
+ {
+ $oDisplayBlock->RenderContent($oPage, $aExtraParams);
+ }
+
+
+ $oPage->output();
+
+} catch (AjaxSearchException $e)
+{
+ http_response_code($e->getCode());
+ // note: transform to cope with XSS attacks
+ echo '' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
';
+ IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
+} catch (SecurityException $e)
+{
+ http_response_code(403);
+ // note: transform to cope with XSS attacks
+ echo '' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
';
+ IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
+} catch (Exception $e)
+{
+ http_response_code(500);
+ // note: transform to cope with XSS attacks
+ echo '' . htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8') . '
';
+ IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
+}
\ No newline at end of file
diff --git a/sources/application/search/ajaxsearchexception.class.inc.php b/sources/application/search/ajaxsearchexception.class.inc.php
new file mode 100644
index 000000000..a3d259674
--- /dev/null
+++ b/sources/application/search/ajaxsearchexception.class.inc.php
@@ -0,0 +1,35 @@
+
+ *
+ */
+
+/**
+ * Created by PhpStorm.
+ * User: Eric
+ * Date: 08/03/2018
+ * Time: 11:18
+ */
+
+namespace Combodo\iTop\Application\Search;
+
+
+class AjaxSearchException extends \Exception
+{
+
+}
\ No newline at end of file
diff --git a/sources/application/search/criterionconversion/criteriontooql.class.inc.php b/sources/application/search/criterionconversion/criteriontooql.class.inc.php
new file mode 100644
index 000000000..140e2cf2c
--- /dev/null
+++ b/sources/application/search/criterionconversion/criteriontooql.class.inc.php
@@ -0,0 +1,356 @@
+
+ *
+ */
+
+
+namespace Combodo\iTop\Application\Search\CriterionConversion;
+
+
+use AttributeDate;
+use AttributeDateTime;
+use AttributeDefinition;
+use AttributeEnum;
+use Combodo\iTop\Application\Search\AjaxSearchException;
+use Combodo\iTop\Application\Search\CriterionConversionAbstract;
+use Combodo\iTop\Application\Search\SearchForm;
+
+class CriterionToOQL extends CriterionConversionAbstract
+{
+
+ public static function Convert($aCriteria)
+ {
+ if (!empty($aCriteria['oql']))
+ {
+ return $aCriteria['oql'];
+ }
+
+ $aRef = explode('.', $aCriteria['ref']);
+ for($i = 0; $i < count($aRef); $i++)
+ {
+ $aRef[$i] = '`'.$aRef[$i].'`';
+ }
+ $sRef = implode('.', $aRef);
+
+ $sOperator = $aCriteria['operator'];
+
+ $aMappedOperators = array(
+ self::OP_CONTAINS => 'ContainsToOql',
+ self::OP_STARTS_WITH => 'StartsWithToOql',
+ self::OP_ENDS_WITH => 'EndsWithToOql',
+ self::OP_EMPTY => 'EmptyToOql',
+ self::OP_NOT_EMPTY => 'NotEmptyToOql',
+ self::OP_BETWEEN_DATES => 'BetweenDatesToOql',
+ self::OP_BETWEEN => 'BetweenToOql',
+ self::OP_IN => 'InToOql',
+ self::OP_ALL => 'AllToOql',
+ );
+
+ if (array_key_exists($sOperator, $aMappedOperators))
+ {
+ $sFct = $aMappedOperators[$sOperator];
+
+ return self::$sFct($sRef, $aCriteria);
+ }
+
+ $sValue = self::GetValue(self::GetValues($aCriteria), 0);
+
+ return "({$sRef} {$sOperator} '{$sValue}')";
+ }
+
+ private static function GetValues($aCriteria)
+ {
+ if (!array_key_exists('values', $aCriteria))
+ {
+ return array();
+ }
+
+ return $aCriteria['values'];
+ }
+
+ private static function GetValue($aValues, $iIndex)
+ {
+ if (!array_key_exists($iIndex, $aValues))
+ {
+ return null;
+ }
+ if (!array_key_exists('value', $aValues[$iIndex]))
+ {
+ return null;
+ }
+
+ return $aValues[$iIndex]['value'];
+ }
+
+ protected static function ContainsToOql($sRef, $aCriteria)
+ {
+ $aValues = self::GetValues($aCriteria);
+ $sValue = self::GetValue($aValues, 0);
+
+ return "({$sRef} LIKE '%{$sValue}%')";
+ }
+
+ protected static function StartsWithToOql($sRef, $aCriteria)
+ {
+ $aValues = self::GetValues($aCriteria);
+ $sValue = self::GetValue($aValues, 0);
+
+ return "({$sRef} LIKE '{$sValue}%')";
+ }
+
+ protected static function EndsWithToOql($sRef, $aCriteria)
+ {
+ $aValues = self::GetValues($aCriteria);
+ $sValue = self::GetValue($aValues, 0);
+
+ return "({$sRef} LIKE '%{$sValue}')";
+ }
+
+ protected static function EmptyToOql($sRef, $aCriteria)
+ {
+ if (isset($aCriteria['widget']))
+ {
+ switch ($aCriteria['widget'])
+ {
+ case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC:
+ case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD:
+ return "ISNULL({$sRef})";
+ }
+ }
+
+ return "({$sRef} = '')";
+ }
+
+ protected static function NotEmptyToOql($sRef, $aCriteria)
+ {
+ return "({$sRef} != '')";
+ }
+
+ protected static function InToOql($sRef, $aCriteria)
+ {
+ $sAttCode = $aCriteria['code'];
+ $sClass = $aCriteria['class'];
+ $aValues = self::GetValues($aCriteria);
+
+ if (count($aValues) == 0)
+ {
+ // Ignore when nothing is selected
+ return "1";
+ }
+
+ $bFilterOnUndefined = false;
+ try
+ {
+ $aAttributeDefs = \MetaModel::ListAttributeDefs($sClass);
+ if (array_key_exists($sAttCode, $aAttributeDefs))
+ {
+ $oAttDef = $aAttributeDefs[$sAttCode];
+ if ($oAttDef instanceof AttributeEnum)
+ {
+ $aAllowedValues = SearchForm::GetFieldAllowedValues($oAttDef);
+ if (array_key_exists('values', $aAllowedValues))
+ {
+ // Can't invert the test if NULL is allowed
+ if (!$oAttDef->IsNullAllowed())
+ {
+ $aAllowedValues = $aAllowedValues['values'];
+ if (count($aValues) == count($aAllowedValues))
+ {
+ // All entries are selected
+ return "1";
+ }
+ // more selected values than remaining so use NOT IN
+ else
+ {
+ if (count($aValues) > (count($aAllowedValues) / 2))
+ {
+ foreach($aValues as $aValue)
+ {
+ unset($aAllowedValues[$aValue['value']]);
+ }
+ $sInList = implode("','", array_keys($aAllowedValues));
+
+ return "({$sRef} NOT IN ('$sInList'))";
+ }
+ }
+ }
+ // search for "undefined"
+ for($i = 0; $i < count($aValues); $i++)
+ {
+ $aValue = $aValues[$i];
+ if (isset($aValue['value']) && ($aValue['value'] === 'null'))
+ {
+ $bFilterOnUndefined = true;
+ unset($aValues[$i]);
+ break;
+ }
+ }
+ }
+ }
+ }
+ } catch (\CoreException $e)
+ {
+ }
+
+ $aInValues = array();
+ foreach($aValues as $aValue)
+ {
+ $aInValues[] = $aValue['value'];
+ }
+ $sInList = implode("','", $aInValues);
+
+ if ($bFilterOnUndefined)
+ {
+ $sFilterOnUndefined = "ISNULL({$sRef})";
+ if (count($aValues) === 0)
+ {
+ return $sFilterOnUndefined;
+ }
+
+ if (count($aInValues) == 1)
+ {
+ // Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing
+ return "((({$sRef} = '$sInList') OR {$sFilterOnUndefined}) AND 1)";
+ }
+
+ // Add 'AND 1' to group the 'OR' inside an AND list for OQL parsing
+ return "(({$sRef} IN ('$sInList') OR {$sFilterOnUndefined}) AND 1)";
+ }
+
+ if (count($aInValues) == 1)
+ {
+ return "({$sRef} = '$sInList')";
+ }
+
+ return "({$sRef} IN ('$sInList'))";
+ }
+
+ protected static function BetweenDatesToOql($sRef, $aCriteria)
+ {
+ $aOQL = array();
+
+ $aValues = self::GetValues($aCriteria);
+ if (count($aValues) != 2)
+ {
+ return "1";
+ }
+
+ $sWidget = $aCriteria['widget'];
+ if ($sWidget == AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME)
+ {
+ $sAttributeClass = AttributeDateTime::class;
+ }
+ else
+ {
+ $sAttributeClass = AttributeDate::class;
+ }
+ $oFormat = $sAttributeClass::GetFormat();
+
+ $sStartDate = $aValues[0]['value'];
+ if (!empty($sStartDate))
+ {
+ $oDate = $oFormat->parse($sStartDate);
+ $sStartDate = $oDate->format($sAttributeClass::GetSQLFormat());
+ $aOQL[] = "({$sRef} >= '$sStartDate')";
+ }
+
+ $sEndDate = $aValues[1]['value'];
+ if (!empty($sEndDate))
+ {
+ $oDate = $oFormat->parse($sEndDate);
+ $sEndDate = $oDate->format($sAttributeClass::GetSQLFormat());
+ $aOQL[] = "({$sRef} <= '$sEndDate')";
+ }
+
+ $sOQL = implode(' AND ', $aOQL);
+
+ if (empty($sOQL))
+ {
+ $sOQL = "1";
+ }
+
+ return $sOQL;
+ }
+
+ /**
+ * @param $sRef
+ * @param $aCriteria
+ *
+ * @return string
+ * @throws \Combodo\iTop\Application\Search\AjaxSearchException
+ */
+ protected static function BetweenToOql($sRef, $aCriteria)
+ {
+ $aOQL = array();
+
+ $aValues = self::GetValues($aCriteria);
+ if (count($aValues) != 2)
+ {
+ return "1";
+ }
+
+ if (isset($aValues[0]['value']))
+ {
+ $sStartNum = trim($aValues[0]['value']);
+ if (is_numeric($sStartNum))
+ {
+ $aOQL[] = "({$sRef} >= '$sStartNum')";
+ }
+ else
+ {
+ if (!empty($sStartNum))
+ {
+ throw new AjaxSearchException("'$sStartNum' is not a numeric value", 400);
+ }
+ }
+ }
+
+ if (isset($aValues[1]['value']))
+ {
+ $sEndNum = trim($aValues[1]['value']);
+ if (is_numeric($sEndNum))
+ {
+ $aOQL[] = "({$sRef} <= '$sEndNum')";
+ }
+ else
+ {
+ if (!empty($sEndNum))
+ {
+ throw new AjaxSearchException("'$sEndNum' is not a numeric value", 400);
+ }
+ }
+ }
+
+ $sOQL = implode(' AND ', $aOQL);
+
+ if (empty($sOQL))
+ {
+ $sOQL = "1";
+ }
+
+ return $sOQL;
+ }
+
+
+ protected static function AllToOql($sRef, $aCriteria)
+ {
+ return "1";
+ }
+
+}
\ No newline at end of file
diff --git a/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php
new file mode 100644
index 000000000..f85dcf9c3
--- /dev/null
+++ b/sources/application/search/criterionconversion/criteriontosearchform.class.inc.php
@@ -0,0 +1,644 @@
+
+ *
+ */
+
+/**
+ * Convert structures from OQL expressions into structure for the search form
+ */
+namespace Combodo\iTop\Application\Search\CriterionConversion;
+
+
+use AttributeDate;
+use AttributeDateTime;
+use AttributeDefinition;
+use Combodo\iTop\Application\Search\CriterionConversionAbstract;
+use DateInterval;
+use DateTime;
+use Dict;
+use Exception;
+use MetaModel;
+
+class CriterionToSearchForm extends CriterionConversionAbstract
+{
+
+ /**
+ * @param array $aAndCriterionRaw
+ * @param array $aFieldsByCategory
+ *
+ * @param array $aClasses all the classes of the filter
+ *
+ * @param bool $bIsRemovable
+ *
+ * @return array
+ */
+ public static function Convert($aAndCriterionRaw, $aFieldsByCategory, $aClasses, $bIsRemovable = true)
+ {
+ $aAllFields = array();
+ foreach($aFieldsByCategory as $aFields)
+ {
+ foreach($aFields as $aField)
+ {
+ $sAlias = $aField['class_alias'];
+ $sCode = $aField['code'];
+ $aAllFields["$sAlias.$sCode"] = $aField;
+ }
+ }
+ $aAndCriterion = array();
+ $aMappingOperatorToFunction = array(
+ AttributeDefinition::SEARCH_WIDGET_TYPE_STRING => 'TextToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD => 'ExternalFieldToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_DATE => 'DateTimeToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME => 'DateTimeToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC => 'NumericToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'ExternalKeyToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY => 'ExternalKeyToSearchForm',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'EnumToSearchForm',
+ );
+
+ foreach($aAndCriterionRaw as $aCriteria)
+ {
+ if (isset($aCriteria['label']))
+ {
+ $aCriteria['label'] = preg_replace("@\)$@", '', $aCriteria['label']);
+ $aCriteria['label'] = preg_replace("@^\(@", '', $aCriteria['label']);
+ }
+ $aCriteria['is_removable'] = $bIsRemovable;
+
+ $sClass = '';
+ if (isset($aCriteria['ref']))
+ {
+ $aRef = explode('.', $aCriteria['ref']);
+ if (isset($aClasses[$aRef[0]]))
+ {
+ $sClass = $aClasses[$aRef[0]];
+ $aCriteria['class'] = $sClass;
+ }
+ }
+
+ // Check criteria validity
+ if (!isset($aCriteria['ref']) || !isset($aAllFields[$aCriteria['ref']]))
+ {
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+ $aCriteria['label'] = Dict::S('UI:Search:Criteria:Raw:Filtered');
+ if (isset($aCriteria['ref']))
+ {
+ try
+ {
+ $aCriteria['label'] = Dict::Format('UI:Search:Criteria:Raw:FilteredOn', MetaModel::GetName($sClass));
+ }
+ catch (Exception $e)
+ {
+ }
+ }
+ }
+ if (array_key_exists('widget', $aCriteria))
+ {
+ if (array_key_exists($aCriteria['widget'], $aMappingOperatorToFunction))
+ {
+ $sFct = $aMappingOperatorToFunction[$aCriteria['widget']];
+ $aAndCriterion[] = self::$sFct($aCriteria, $aAllFields);
+ }
+ else
+ {
+ $aAndCriterion[] = $aCriteria;
+ }
+ }
+ }
+
+ // Regroup criterion by variable name (no ref first)
+ usort($aAndCriterion, function ($a, $b) {
+ if (array_key_exists('ref', $a) || array_key_exists('ref', $b))
+ {
+ if (array_key_exists('ref', $a) && array_key_exists('ref', $b))
+ {
+ $iRefCmp = strcmp($a['ref'], $b['ref']);
+ if ($iRefCmp != 0) return $iRefCmp;
+
+ return strcmp($a['operator'], $b['operator']);
+ }
+ if (array_key_exists('ref', $a))
+ {
+ return 1;
+ }
+
+ return -1;
+ }
+ if (array_key_exists('oql', $a) && array_key_exists('oql', $b))
+ {
+ return strcmp($a['oql'], $b['oql']);
+ }
+
+ return 0;
+ });
+
+ $aMergeFctByWidget = array(
+ AttributeDefinition::SEARCH_WIDGET_TYPE_DATE => 'MergeDate',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME => 'MergeDateTime',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC => 'MergeNumeric',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_ENUM => 'MergeEnumExtKeys',
+ AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_KEY => 'MergeEnumExtKeys',
+ );
+
+ $aPrevCriterion = null;
+ $aMergedCriterion = array();
+ foreach($aAndCriterion as $aCurrCriterion)
+ {
+ if (!is_null($aPrevCriterion))
+ {
+ if (array_key_exists('ref', $aPrevCriterion))
+ {
+ // If previous has ref, the current has ref as the array is sorted with all without ref first
+ if (strcmp($aPrevCriterion['ref'], $aCurrCriterion['ref']) == 0)
+ {
+ // Same attribute, try to merge
+ if (array_key_exists('widget', $aCurrCriterion))
+ {
+ if (array_key_exists($aCurrCriterion['widget'], $aMergeFctByWidget))
+ {
+ $sFct = $aMergeFctByWidget[$aCurrCriterion['widget']];
+ $aPrevCriterion = self::$sFct($aPrevCriterion, $aCurrCriterion, $aMergedCriterion);
+ continue;
+ }
+ }
+ }
+ }
+ $aMergedCriterion[] = $aPrevCriterion;
+ }
+
+ $aPrevCriterion = $aCurrCriterion;
+ }
+ if (!is_null($aPrevCriterion))
+ {
+ $aMergedCriterion[] = $aPrevCriterion;
+ }
+
+ // Sort by label criterion by variable name (no ref first)
+ usort($aMergedCriterion, function ($a, $b) {
+ if (($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW) ||
+ ($b['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
+ {
+ if (($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW) &&
+ ($b['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
+ {
+ return strcmp($a['label'], $b['label']);
+ }
+ if ($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)
+ {
+ return -1;
+ }
+
+ return 1;
+ }
+
+ return strcmp($a['label'], $b['label']);
+ });
+
+ return $aMergedCriterion;
+ }
+
+ /**
+ * @param $aPrevCriterion
+ * @param $aCurrCriterion
+ * @param $aMergedCriterion
+ *
+ * @return Current criteria or null if merged
+ * @throws \Exception
+ */
+ protected static function MergeDate($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
+ {
+ $sPrevOperator = $aPrevCriterion['operator'];
+ $sCurrOperator = $aCurrCriterion['operator'];
+ if ((($sPrevOperator != '<') && ($sPrevOperator != '<=')) || (($sCurrOperator != '>') && ($sCurrOperator != '>=')))
+ {
+ $aMergedCriterion[] = $aPrevCriterion;
+
+ return $aCurrCriterion;
+ }
+
+ // Merge into 'between' operation.
+ // The ends of the interval are included
+ $aCurrCriterion['operator'] = 'between_dates';
+ $oFormat = AttributeDate::GetFormat();
+ $sLastDate = $aPrevCriterion['values'][0]['value'];
+ $oDate = new DateTime($sLastDate);
+ if ($sPrevOperator == '<')
+ {
+ // previous day to include ends
+ $oDate->sub(DateInterval::createFromDateString('1 day'));
+ }
+ $sLastDateValue = $oDate->format(AttributeDate::GetSQLFormat());
+ $sLastDateLabel = $oFormat->format($oDate);
+
+ $sFirstDate = $aCurrCriterion['values'][0]['value'];
+ $oDate = new DateTime($sFirstDate);
+ if ($sCurrOperator == '>')
+ {
+ // next day to include ends
+ $oDate->add(DateInterval::createFromDateString('1 day'));
+ }
+ $sFirstDateValue = $oDate->format(AttributeDate::GetSQLFormat());
+ $sFirstDateLabel = $oFormat->format($oDate);
+
+ $aCurrCriterion['values'] = array();
+ $aCurrCriterion['values'][] = array('value' => $sFirstDateValue, 'label' => $sFirstDateLabel);
+ $aCurrCriterion['values'][] = array('value' => $sLastDateValue, 'label' => $sLastDateLabel);
+
+ $aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
+ $aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND', 'AND').' '.$aCurrCriterion['label'];
+
+ $aMergedCriterion[] = $aCurrCriterion;
+
+ return null;
+ }
+
+ /**
+ * @param $aPrevCriterion
+ * @param $aCurrCriterion
+ * @param $aMergedCriterion
+ *
+ * @return Current criteria or null if merged
+ * @throws \Exception
+ */
+ protected static function MergeDateTime($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
+ {
+ $sPrevOperator = $aPrevCriterion['operator'];
+ $sCurrOperator = $aCurrCriterion['operator'];
+ if ((($sPrevOperator != '<') && ($sPrevOperator != '<=')) || (($sCurrOperator != '>') && ($sCurrOperator != '>=')))
+ {
+ $aMergedCriterion[] = $aPrevCriterion;
+
+ return $aCurrCriterion;
+ }
+
+ // Merge into 'between' operation.
+ // The ends of the interval are included
+ $sLastDate = $aPrevCriterion['values'][0]['value'];
+ $sFirstDate = $aCurrCriterion['values'][0]['value'];
+ $oDate = new DateTime($sLastDate);
+ $aCurrCriterion['operator'] = 'between_dates';
+ $sInterval = '1 second';
+
+ if ($sPrevOperator == '<')
+ {
+ // previous day/second to include ends
+ $oDate->sub(DateInterval::createFromDateString($sInterval));
+ }
+ $sLastDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
+ $sLastDateLabel = AttributeDateTime::GetFormat()->Format($sLastDateValue);
+
+ $oDate = new DateTime($sFirstDate);
+ if ($sCurrOperator == '>')
+ {
+ // next day/second to include ends
+ $oDate->add(DateInterval::createFromDateString($sInterval));
+ }
+ $sFirstDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
+ $sFirstDateLabel = AttributeDateTime::GetFormat()->Format($sFirstDateValue);
+
+ $aCurrCriterion['values'] = array();
+ $aCurrCriterion['values'][] = array('value' => $sFirstDateValue, 'label' => $sFirstDateLabel);
+ $aCurrCriterion['values'][] = array('value' => $sLastDateValue, 'label' => $sLastDateLabel);
+
+ $aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
+ $aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND',
+ 'AND').' '.$aCurrCriterion['label'];
+
+ $aMergedCriterion[] = $aCurrCriterion;
+
+ return null;
+ }
+
+ /**
+ * @param $aPrevCriterion
+ * @param $aCurrCriterion
+ * @param $aMergedCriterion
+ *
+ * @return Current criteria or null if merged
+ * @throws \Exception
+ */
+ protected static function MergeNumeric($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
+ {
+ $sPrevOperator = $aPrevCriterion['operator'];
+ $sCurrOperator = $aCurrCriterion['operator'];
+ if (($sPrevOperator != '<=') || ($sCurrOperator != '>='))
+ {
+ $aMergedCriterion[] = $aPrevCriterion;
+
+ return $aCurrCriterion;
+ }
+
+ // Merge into 'between' operation.
+ $sLastNum = $aPrevCriterion['values'][0]['value'];
+ $sFirstNum = $aCurrCriterion['values'][0]['value'];
+ $aCurrCriterion['values'] = array();
+ $aCurrCriterion['values'][] = array('value' => $sFirstNum, 'label' => "$sFirstNum");
+ $aCurrCriterion['values'][] = array('value' => $sLastNum, 'label' => "$sLastNum");
+
+ $aCurrCriterion['oql'] = "({$aPrevCriterion['oql']} AND {$aCurrCriterion['oql']})";
+ $aCurrCriterion['label'] = $aPrevCriterion['label'].' '.Dict::S('Expression:Operator:AND', 'AND').' '.$aCurrCriterion['label'];
+ $aCurrCriterion['operator'] = 'between';
+
+ $aMergedCriterion[] = $aCurrCriterion;
+
+ return null;
+ }
+
+ private static function SerializeValues($aValues)
+ {
+ $aSerializedValues = array();
+ foreach($aValues as $aValue)
+ {
+ $aSerializedValues[] = serialize($aValue);
+ }
+
+ return $aSerializedValues;
+ }
+
+ protected static function MergeEnumExtKeys($aPrevCriterion, $aCurrCriterion, &$aMergedCriterion)
+ {
+ $aFirstValues = self::SerializeValues($aPrevCriterion['values']);
+ $aNextValues = self::SerializeValues($aCurrCriterion['values']);
+
+ // Keep only the common values
+ $aCurrCriterion['values'] = array_map("unserialize", array_intersect($aFirstValues, $aNextValues));
+
+ $aMergedCriterion[] = $aCurrCriterion;
+ return null;
+ }
+
+ protected static function TextToSearchForm($aCriteria, $aFields)
+ {
+ $sOperator = $aCriteria['operator'];
+ $sValue = $aCriteria['values'][0]['value'];
+
+ $bStartWithPercent = substr($sValue, 0, 1) == '%' ? true : false;
+ $bEndWithPercent = substr($sValue, -1) == '%' ? true : false;
+
+ switch (true)
+ {
+ case ('' == $sValue and ($sOperator == '=' or $sOperator == 'LIKE')):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
+ break;
+ case ('' == $sValue and $sOperator == '!='):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_NOT_EMPTY;
+ break;
+ case ($sOperator == 'LIKE' && $bStartWithPercent && $bEndWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_CONTAINS;
+ $sValue = substr($sValue, 1, -1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ case ($sOperator == 'LIKE' && $bStartWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_ENDS_WITH;
+ $sValue = substr($sValue, 1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ case ($sOperator == 'LIKE' && $bEndWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_STARTS_WITH;
+ $sValue = substr($sValue, 0, -1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ }
+
+ return $aCriteria;
+ }
+
+ protected static function ExternalFieldToSearchForm($aCriteria, $aFields)
+ {
+ $sOperator = $aCriteria['operator'];
+ $sValue = $aCriteria['values'][0]['value'];
+
+ $bStartWithPercent = substr($sValue, 0, 1) == '%' ? true : false;
+ $bEndWithPercent = substr($sValue, -1) == '%' ? true : false;
+
+ switch (true)
+ {
+ case ($sOperator == 'ISNULL'):
+ case ('' == $sValue and ($sOperator == 'LIKE')):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
+ break;
+ case ('' == $sValue and $sOperator == '!='):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_NOT_EMPTY;
+ break;
+ case ($sOperator == 'LIKE' && $bStartWithPercent && $bEndWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_CONTAINS;
+ $sValue = substr($sValue, 1, -1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ case ($sOperator == 'LIKE' && $bStartWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_ENDS_WITH;
+ $sValue = substr($sValue, 1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ case ($sOperator == 'LIKE' && $bEndWithPercent):
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_STARTS_WITH;
+ $sValue = substr($sValue, 0, -1);
+ $aCriteria['values'][0]['value'] = $sValue;
+ $aCriteria['values'][0]['label'] = "$sValue";
+ break;
+ }
+
+ return $aCriteria;
+ }
+
+ protected static function DateTimeToSearchForm($aCriteria, $aFields)
+ {
+ if (!array_key_exists('is_relative', $aCriteria) || !$aCriteria['is_relative'])
+ {
+ // Convert '=' in 'between'
+ if (isset($aCriteria['operator']) && ($aCriteria['operator'] === '='))
+ {
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_BETWEEN_DATES;
+ $sWidget = $aCriteria['widget'];
+ if ($sWidget == AttributeDefinition::SEARCH_WIDGET_TYPE_DATE)
+ {
+ $aCriteria['values'][1] = $aCriteria['values'][0];
+ }
+ else
+ {
+ $sDate = $aCriteria['values'][0]['value'];
+ $oDate = new DateTime($sDate);
+
+ $sFirstDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
+ $sFirstDateLabel = AttributeDateTime::GetFormat()->Format($sFirstDateValue);
+ $aCriteria['values'][0] = array('value' => $sFirstDateValue, 'label' => "$sFirstDateLabel");
+
+
+ $oDate->add(DateInterval::createFromDateString('1 day'));
+ $oDate->sub(DateInterval::createFromDateString('1 second'));
+
+ $sLastDateValue = $oDate->format(AttributeDateTime::GetSQLFormat());
+ $sLastDateLabel = AttributeDateTime::GetFormat()->Format($sLastDateValue);
+ $aCriteria['values'][1] = array('value' => $sLastDateValue, 'label' => "$sLastDateLabel");
+ }
+ }
+
+ return $aCriteria;
+ }
+
+ if (isset($aCriteria['values'][0]['value']))
+ {
+ $sLabel = $aCriteria['values'][0]['value'];
+ if (isset($aCriteria['verb']))
+ {
+ switch ($aCriteria['verb'])
+ {
+ case 'DATE_SUB':
+ $sLabel = '-'.$sLabel;
+ break;
+ case 'DATE_ADD':
+ $sLabel = '+'.$sLabel;
+ break;
+ }
+ }
+ if (isset($aCriteria['unit']))
+ {
+ $sLabel .= Dict::S('Expression:Unit:Short:'.$aCriteria['unit'], $aCriteria['unit']);
+ }
+ $aCriteria['values'][0]['label'] = "$sLabel";
+ }
+
+ // Temporary until the JS widget support relative dates
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+
+ return $aCriteria;
+ }
+
+ protected static function NumericToSearchForm($aCriteria, $aFields)
+ {
+ if ($aCriteria['operator'] == 'ISNULL')
+ {
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_EMPTY;
+ }
+
+ return $aCriteria;
+ }
+
+ protected static function EnumToSearchForm($aCriteria, $aFields)
+ {
+ $sOperator = $aCriteria['operator'];
+ switch ($sOperator)
+ {
+ case '=':
+ // Same as IN
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
+ break;
+ case 'NOT IN':
+ case 'NOTIN':
+ case '!=':
+ // Same as NOT IN
+ $aCriteria = self::RevertValues($aCriteria, $aFields);
+ break;
+ case 'IN':
+ // Nothing special to do
+ break;
+ case 'OR':
+ case 'ISNULL':
+ // Special case when undefined and/or other values are selected
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
+ if (isset($aCriteria['has_undefined']) && $aCriteria['has_undefined'])
+ {
+ if (!isset($aCriteria['values']))
+ {
+ $aCriteria['values'] = array();
+ }
+ // Convention for 'undefined' enums
+ $aCriteria['values'][] = array('value' => 'null', 'label' => 'null');
+ }
+ break;
+ default:
+ // Unknown operator
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+ break;
+ }
+
+ return $aCriteria;
+ }
+
+ protected static function ExternalKeyToSearchForm($aCriteria, $aFields)
+ {
+ $sOperator = $aCriteria['operator'];
+ switch ($sOperator)
+ {
+ case '=':
+ // Same as IN
+ $aCriteria['operator'] = CriterionConversionAbstract::OP_IN;
+ break;
+ case 'IN':
+ // Nothing special to do
+ break;
+ default:
+ // Unknown operator
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+ break;
+ }
+
+ return $aCriteria;
+ }
+
+ /**
+ * @param $aCriteria
+ * @param $aFields
+ *
+ * @return mixed
+ */
+ protected static function RevertValues($aCriteria, $aFields)
+ {
+ $sRef = $aCriteria['ref'];
+ $aValues = $aCriteria['values'];
+ if (array_key_exists($sRef, $aFields))
+ {
+ $aField = $aFields[$sRef];
+ if (array_key_exists('allowed_values', $aField) && array_key_exists('values', $aField['allowed_values']))
+ {
+ $aAllowedValues = $aField['allowed_values']['values'];
+ }
+ else
+ {
+ // Can't obtain the list of allowed values, just set as unknown
+ $aCriteria['widget'] = AttributeDefinition::SEARCH_WIDGET_TYPE_RAW;
+ }
+ }
+
+ if (isset($aAllowedValues))
+ {
+ foreach($aValues as $aValue)
+ {
+ $sValue = $aValue['value'];
+ unset($aAllowedValues[$sValue]);
+ }
+ $aCriteria['values'] = array();
+
+ foreach($aAllowedValues as $sValue => $sLabel)
+ {
+ $aValue = array('value' => $sValue, 'label' => "$sLabel");
+ $aCriteria['values'][] = $aValue;
+ }
+ $aCriteria['operator'] = 'IN';
+ }
+
+ return $aCriteria;
+ }
+
+}
\ No newline at end of file
diff --git a/sources/application/search/criterionconversionabstract.class.inc.php b/sources/application/search/criterionconversionabstract.class.inc.php
new file mode 100644
index 000000000..041d840e8
--- /dev/null
+++ b/sources/application/search/criterionconversionabstract.class.inc.php
@@ -0,0 +1,40 @@
+
+ *
+ */
+
+
+namespace Combodo\iTop\Application\Search;
+
+
+abstract class CriterionConversionAbstract
+{
+
+ const OP_CONTAINS = 'contains';
+ const OP_STARTS_WITH = 'starts_with';
+ const OP_ENDS_WITH = 'ends_with';
+ const OP_EMPTY = 'empty';
+ const OP_NOT_EMPTY = 'not_empty';
+ const OP_IN = 'IN';
+ const OP_BETWEEN_DATES = 'between_dates';
+ const OP_BETWEEN = 'between';
+ const OP_ALL = 'all';
+
+}
+
diff --git a/sources/application/search/criterionparser.class.inc.php b/sources/application/search/criterionparser.class.inc.php
new file mode 100644
index 000000000..903bb7b71
--- /dev/null
+++ b/sources/application/search/criterionparser.class.inc.php
@@ -0,0 +1,103 @@
+
+ *
+ */
+
+/**
+ * Created by PhpStorm.
+ * User: Eric
+ * Date: 08/03/2018
+ * Time: 11:25
+ */
+
+namespace Combodo\iTop\Application\Search;
+
+
+use Combodo\iTop\Application\Search\CriterionConversion\CriterionToOQL;
+use DBObjectSearch;
+use Expression;
+use IssueLog;
+use OQLException;
+
+class CriterionParser
+{
+
+ /**
+ * @param $sBaseOql
+ * @param $aCriterion
+ * @param $sHiddenCriteria
+ *
+ * @return \DBSearch
+ */
+ public static function Parse($sBaseOql, $aCriterion, $sHiddenCriteria = null)
+ {
+ $aExpression = array();
+ $aOr = $aCriterion['or'];
+ foreach($aOr as $aAndList)
+ {
+
+ $sExpression = self::ParseAndList($aAndList['and']);
+ if (!empty($sExpression))
+ {
+ $aExpression[] = $sExpression;
+ }
+ }
+
+ try
+ {
+ $oSearch = DBObjectSearch::FromOQL($sBaseOql);
+
+ if (!empty($sHiddenCriteria))
+ {
+ $oHiddenCriteriaExpression = Expression::FromOQL($sHiddenCriteria);
+ $oSearch->AddConditionExpression($oHiddenCriteriaExpression);
+ }
+
+ if (empty($aExpression))
+ {
+ return $oSearch;
+ }
+
+ $oExpression = Expression::FromOQL(implode(" OR ", $aExpression));
+ $oSearch->AddConditionExpression($oExpression);
+
+ return $oSearch;
+ } catch (OQLException $e)
+ {
+ IssueLog::Error($e->getMessage());
+ }
+ return null;
+ }
+
+ private static function ParseAndList($aAnd)
+ {
+ $aExpression = array();
+ foreach($aAnd as $aCriteria)
+ {
+ $aExpression[] = CriterionToOQL::Convert($aCriteria);
+ }
+
+ if (empty($aExpression))
+ {
+ return '';
+ }
+
+ return '('.implode(" AND ", $aExpression).')';
+ }
+}
\ No newline at end of file
diff --git a/sources/application/search/searchform.class.inc.php b/sources/application/search/searchform.class.inc.php
new file mode 100644
index 000000000..f3a67c541
--- /dev/null
+++ b/sources/application/search/searchform.class.inc.php
@@ -0,0 +1,483 @@
+
+ *
+ */
+
+
+namespace Combodo\iTop\Application\Search;
+
+
+use ApplicationContext;
+use AttributeDefinition;
+use CMDBObjectSet;
+use Combodo\iTop\Application\Search\CriterionConversion\CriterionToSearchForm;
+use CoreException;
+use DBObjectSearch;
+use DBObjectSet;
+use Dict;
+use Exception;
+use Expression;
+use IssueLog;
+use MetaModel;
+use TrueExpression;
+use utils;
+use WebPage;
+
+class SearchForm
+{
+
+ /**
+ * @param \WebPage $oPage
+ * @param \CMDBObjectSet $oSet
+ * @param array $aExtraParams
+ *
+ * @return string
+ * @throws \CoreException
+ * @throws \DictExceptionMissingString
+ */
+ public function GetSearchForm(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
+ {
+ $sHtml = '';
+ $oAppContext = new ApplicationContext();
+ $sClassName = $oSet->GetFilter()->GetClass();
+ $aListParams = array();
+
+ foreach($aExtraParams as $key => $value)
+ {
+ $aListParams[$key] = $value;
+ }
+
+ // Simple search form
+ if (isset($aExtraParams['currentId']))
+ {
+ $sSearchFormId = $aExtraParams['currentId'];
+ }
+ else
+ {
+ $iSearchFormId = $oPage->GetUniqueId();
+ $sSearchFormId = 'SimpleSearchForm'.$iSearchFormId;
+ $sHtml .= "