From eaa49bce056959691784904e395769d49d676e59 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Thu, 23 Aug 2018 16:19:54 +0000 Subject: [PATCH 001/113] =?UTF-8?q?N=C2=B0931=20woops=20forgot=20to=20rena?= =?UTF-8?q?me=20TagSetFielData=20file=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6018] --- core/autoload.php | 2 +- core/{tagfield.class.inc.php => tagsetfield.class.inc.php} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename core/{tagfield.class.inc.php => tagsetfield.class.inc.php} (100%) diff --git a/core/autoload.php b/core/autoload.php index c1d674e59d..15eb719a15 100644 --- a/core/autoload.php +++ b/core/autoload.php @@ -29,7 +29,7 @@ MetaModel::IncludeModule('core/action.class.inc.php'); MetaModel::IncludeModule('core/trigger.class.inc.php'); MetaModel::IncludeModule('core/bulkexport.class.inc.php'); MetaModel::IncludeModule('core/ownershiplock.class.inc.php'); -MetaModel::IncludeModule('core/tagfield.class.inc.php'); +MetaModel::IncludeModule('core/tagsetfield.class.inc.php'); MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php'); MetaModel::IncludeModule('core/backgroundtask.class.inc.php'); MetaModel::IncludeModule('core/inlineimage.class.inc.php'); diff --git a/core/tagfield.class.inc.php b/core/tagsetfield.class.inc.php similarity index 100% rename from core/tagfield.class.inc.php rename to core/tagsetfield.class.inc.php From 9a13eb0f902d7ba8cf3f3024ac0647ad9382a44b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Fri, 24 Aug 2018 12:09:55 +0000 Subject: [PATCH 002/113] warnings cleanup, search mode switched to BOOLEAN SVN:b931[6019] --- core/attributedef.class.inc.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 38440d17fe..02f2d0f417 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -272,7 +272,7 @@ abstract class AttributeDefinition * Check the validity of the given value * * @param \DBObject $oHostObject - * @param $value An error if any, null otherwise + * @param $value Object error if any, null otherwise * * @return bool */ @@ -5210,7 +5210,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid { return parent::GetAllowedValues($aArgs, $sContains); } - catch (MissingQueryArgument $e) //FIXME never enters here... + catch (Exception $e) { // Some required arguments could not be found, enlarge to any existing value $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); @@ -5709,7 +5709,9 @@ class AttributeExternalField extends AttributeDefinition return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! case EXTKEY_RELATIVE: - return MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + /** @var \AttributeExternalKey $oAttDef */ + $oAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + return $oAttDef; default: throw new CoreException("Unexpected value for argument iType: '$iType'"); @@ -7527,7 +7529,7 @@ class AttributeTable extends AttributeDBField public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - if (count($value) == 0) + if (!is_array($value) || count($value) == 0) { return ""; } @@ -7593,7 +7595,7 @@ class AttributePropertySet extends AttributeTable public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) { - if (count($value) == 0) + if (!is_array($value) || count($value) == 0) { return ""; } @@ -7619,7 +7621,7 @@ class AttributePropertySet extends AttributeTable public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - if (count($value) == 0) + if (!is_array($value) || count($value) == 0) { return ""; } From 4df497a76892139c4d1db24fa300284b6d685a99 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Fri, 24 Aug 2018 14:11:28 +0000 Subject: [PATCH 003/113] =?UTF-8?q?N=C2=B0931=20Increment=20iTop=20XML=20v?= =?UTF-8?q?ersion=20(1.5=20->=201.6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6020] --- application/datamodel.application.xml | 2 +- core/datamodel.core.xml | 2 +- .../datamodel.itop-attachments.xml | 2 +- .../2.x/itop-backup/datamodel.itop-backup.xml | 2 +- ...del.itop-bridge-virtualization-storage.xml | 2 +- .../datamodel.itop-change-mgmt-itil.xml | 2 +- .../datamodel.itop-change-mgmt.xml | 2 +- .../datamodel.itop-config-mgmt.xml | 2 +- .../2.x/itop-config/datamodel.itop-config.xml | 2 +- .../datamodel.itop-datacenter-mgmt.xml | 2 +- .../datamodel.itop-enduser-devices.xml | 2 +- .../datamodel.itop-full-itil.xml | 2 +- .../datamodel.itop-hub-connector.xml | 2 +- .../datamodel.itop-incident-mgmt-itil.xml | 2 +- .../datamodel.itop-knownerror-mgmt.xml | 2 +- .../2.x/itop-portal/datamodel.itop-portal.xml | 2 +- .../datamodel.itop-problem-mgmt.xml | 2 +- .../datamodel.itop-profiles-itil.xml | 2 +- .../datamodel.itop-request-mgmt-itil.xml | 2 +- .../datamodel.itop-request-mgmt.xml | 2 +- .../datamodel.itop-service-mgmt-provider.xml | 2 +- .../datamodel.itop-service-mgmt.xml | 2 +- .../datamodel.itop-storage-mgmt.xml | 2 +- .../itop-tickets/datamodel.itop-tickets.xml | 2 +- .../datamodel.itop-virtualization-mgmt.xml | 2 +- .../datamodel.itop-welcome-itil.xml | 2 +- setup/itopdesignformat.class.inc.php | 38 +++++++++++++++++++ 27 files changed, 64 insertions(+), 26 deletions(-) diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml index e1b66547bf..d919af78cd 100644 --- a/application/datamodel.application.xml +++ b/application/datamodel.application.xml @@ -1,5 +1,5 @@ - + portal/index.php diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml index 8045a5c40f..624854e32f 100644 --- a/core/datamodel.core.xml +++ b/core/datamodel.core.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml index 53a1b60517..d91967942a 100755 --- a/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml +++ b/datamodels/2.x/itop-attachments/datamodel.itop-attachments.xml @@ -1,5 +1,5 @@ - + DBObject diff --git a/datamodels/2.x/itop-backup/datamodel.itop-backup.xml b/datamodels/2.x/itop-backup/datamodel.itop-backup.xml index 2a89ca50ae..8076ad7208 100644 --- a/datamodels/2.x/itop-backup/datamodel.itop-backup.xml +++ b/datamodels/2.x/itop-backup/datamodel.itop-backup.xml @@ -1,5 +1,5 @@ - + 15 diff --git a/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml b/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml index cf8a48ee51..0789ae6bea 100644 --- a/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml +++ b/datamodels/2.x/itop-bridge-virtualization-storage/datamodel.itop-bridge-virtualization-storage.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml index ab583cc253..81ca9de2ae 100755 --- a/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml +++ b/datamodels/2.x/itop-change-mgmt-itil/datamodel.itop-change-mgmt-itil.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml index d4667c05da..3ee83ab339 100755 --- a/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml +++ b/datamodels/2.x/itop-change-mgmt/datamodel.itop-change-mgmt.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml b/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml index 484bd9dcbb..1c918b16cd 100755 --- a/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml +++ b/datamodels/2.x/itop-config-mgmt/datamodel.itop-config-mgmt.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/datamodels/2.x/itop-config/datamodel.itop-config.xml b/datamodels/2.x/itop-config/datamodel.itop-config.xml index 3b3224180d..28be1062a9 100644 --- a/datamodels/2.x/itop-config/datamodel.itop-config.xml +++ b/datamodels/2.x/itop-config/datamodel.itop-config.xml @@ -1,5 +1,5 @@ - + 50 diff --git a/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml b/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml index 5fb365d05c..e13611cef3 100755 --- a/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml +++ b/datamodels/2.x/itop-datacenter-mgmt/datamodel.itop-datacenter-mgmt.xml @@ -1,5 +1,5 @@ - + PhysicalDevice diff --git a/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml b/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml index e7ae6a50ad..dc6d526cf4 100644 --- a/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml +++ b/datamodels/2.x/itop-endusers-devices/datamodel.itop-enduser-devices.xml @@ -1,5 +1,5 @@ - + PhysicalDevice diff --git a/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml b/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml index e11a59c570..13a688cf96 100644 --- a/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml +++ b/datamodels/2.x/itop-full-itil/datamodel.itop-full-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml b/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml index 73d49712d3..46e44f66b8 100644 --- a/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml +++ b/datamodels/2.x/itop-hub-connector/datamodel.itop-hub-connector.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml index e4bb3960d7..8ef1302b64 100755 --- a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml +++ b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml b/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml index 60bb7f632f..692f62f5bb 100755 --- a/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml +++ b/datamodels/2.x/itop-knownerror-mgmt/datamodel.itop-knownerror-mgmt.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml index 5234eb5152..0a45fdce5f 100644 --- a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml +++ b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml index c9351b1ea5..adde16aa0b 100755 --- a/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml +++ b/datamodels/2.x/itop-problem-mgmt/datamodel.itop-problem-mgmt.xml @@ -1,5 +1,5 @@ - + Ticket diff --git a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml index dfc7ac5b5a..66e1bcf753 100755 --- a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml +++ b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index 8a3afd3355..a2fe11a65a 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index 02666bdfc8..a1001bfbd8 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml index 15479c3983..9cd3cec9f4 100755 --- a/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml +++ b/datamodels/2.x/itop-service-mgmt-provider/datamodel.itop-service-mgmt-provider.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml index 1e6d98ac22..fc3a16ebbc 100755 --- a/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml +++ b/datamodels/2.x/itop-service-mgmt/datamodel.itop-service-mgmt.xml @@ -1,5 +1,5 @@ - + diff --git a/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml b/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml index 538e8604e0..2e96229528 100644 --- a/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml +++ b/datamodels/2.x/itop-storage-mgmt/datamodel.itop-storage-mgmt.xml @@ -1,5 +1,5 @@ - + DatacenterDevice diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 2fc03de439..74f6d7e557 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -1,5 +1,5 @@ - + service_id AND sc.org_id = :this->org_id AND slt.request_type = :request_type AND slt.priority = :this->priority]]> diff --git a/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml b/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml index 03d219ba10..e7faa08dba 100644 --- a/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml +++ b/datamodels/2.x/itop-virtualization-mgmt/datamodel.itop-virtualization-mgmt.xml @@ -1,5 +1,5 @@ - + FunctionalCI diff --git a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml index 86c439bd42..63b19d8ae2 100644 --- a/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml +++ b/datamodels/2.x/itop-welcome-itil/datamodel.itop-welcome-itil.xml @@ -1,5 +1,5 @@ - + cmdbAbstractObject diff --git a/setup/itopdesignformat.class.inc.php b/setup/itopdesignformat.class.inc.php index 089b1657bb..43f8ce21fe 100644 --- a/setup/itopdesignformat.class.inc.php +++ b/setup/itopdesignformat.class.inc.php @@ -73,6 +73,12 @@ class iTopDesignFormat '1.5' => array( 'previous' => '1.4', 'go_to_previous' => 'From15To14', + 'next' => '1.6', + 'go_to_next' => 'From15To16', //TODO + ), + '1.6' => array( + 'previous' => '1.5', + 'go_to_previous' => 'From16To15', //TODO 'next' => null, 'go_to_next' => null, ), @@ -629,6 +635,38 @@ class iTopDesignFormat { } + /** + * @param $oFactory + * + * @return void (Errors are logged) + */ + protected function From15To16($oFactory) + { + // nothing changed ! + } + + /** + * @param $oFactory + * + * @return void (Errors are logged) + */ + protected function From16To15($oFactory) + { + $oXPath = new DOMXPath($this->oDocument); + + // Remove AttributeTagSet nodes + // + $sPath = "/itop_design/classes/class/fields/field[@xsi:type='AttributeTagSet']"; + $oNodeList = $oXPath->query($sPath); + foreach ($oNodeList as $oNode) + { + $this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be removed.'); + $this->DeleteNode($oNode); + } + } + + + /** * Delete a node from the DOM and make sure to also remove the immediately following line break (DOMText), if any. * This prevents generating empty lines in the middle of the XML From 1415f125068f5581680726b9e28d6ac08c9a3c66 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Fri, 24 Aug 2018 14:11:48 +0000 Subject: [PATCH 004/113] composer.json : add missing ext-dom (iTopDesignFormat) SVN:b931[6021] --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8bd34a0a76..dbbe9b0013 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "ext-soap": "*", "ext-json": "*", "ext-zip": "*", - "ext-mysqli": "*" + "ext-mysqli": "*", + "ext-dom": "*" }, "config": { "platform": { From 157193c8315e8c077d066b76d068f87ffc63a4c7 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 28 Aug 2018 07:26:41 +0000 Subject: [PATCH 005/113] =?UTF-8?q?N=C2=B0931=20Change=20default=20XML=20v?= =?UTF-8?q?ersion=20and=20remove=20done=20TODOs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6022] --- setup/itopdesignformat.class.inc.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup/itopdesignformat.class.inc.php b/setup/itopdesignformat.class.inc.php index 43f8ce21fe..cbddfe07d9 100644 --- a/setup/itopdesignformat.class.inc.php +++ b/setup/itopdesignformat.class.inc.php @@ -34,8 +34,8 @@ * echo "Error, failed to upgrade the format, reason(s):\n".implode("\n", $oFormat->GetErrors()); * } */ - -define('ITOP_DESIGN_LATEST_VERSION', '1.5'); // iTop >= 2.5.0 + +define('ITOP_DESIGN_LATEST_VERSION', '1.6'); // iTop >= 2.6.0 class iTopDesignFormat { @@ -74,11 +74,11 @@ class iTopDesignFormat 'previous' => '1.4', 'go_to_previous' => 'From15To14', 'next' => '1.6', - 'go_to_next' => 'From15To16', //TODO + 'go_to_next' => 'From15To16', ), '1.6' => array( 'previous' => '1.5', - 'go_to_previous' => 'From16To15', //TODO + 'go_to_previous' => 'From16To15', 'next' => null, 'go_to_next' => null, ), From 9631021f84b5bb9414049d7bc0d7f11167e08d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Tue, 28 Aug 2018 13:17:11 +0000 Subject: [PATCH 006/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20Attribute=20defi?= =?UTF-8?q?nition=20and=20values=20(with=20unit=20tests=20on=20values)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6024] --- application/applicationextension.inc.php | 8 + core/attributedef.class.inc.php | 230 ++++++++++++-- core/metamodel.class.php | 6 + core/ormtagset.class.inc.php | 294 ++++++++++++++++++ core/tagsetfield.class.inc.php | 108 ++++--- .../itop-tickets/datamodel.itop-tickets.xml | 2 + .../2.x/itop-tickets/en.dict.itop-tickets.php | 3 +- dictionaries/en.dictionary.itop.core.php | 5 +- dictionaries/fr.dictionary.itop.core.php | 4 +- setup/compiler.class.inc.php | 26 +- test/ItopDataTestCase.php | 30 +- test/core/ormTagSetTest.php | 155 +++++++++ 12 files changed, 792 insertions(+), 79 deletions(-) create mode 100644 core/ormtagset.class.inc.php create mode 100644 test/core/ormTagSetTest.php diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 84311db42b..6ca36e94b1 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1165,6 +1165,14 @@ class RestUtils } $value = DBObjectSet::FromArray($sLnkClass, $aLinks); } + elseif ($oAttDef instanceof AttributeTagSet) + { + if (!is_array($value)) + { + throw new Exception("A tag set must be defined by an array of objects"); + } + $value = $oAttDef->FromJSONToValue($value); + } else { $value = $oAttDef->FromJSONToValue($value); diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 02f2d0f417..fa9c1fc456 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -31,6 +31,7 @@ require_once('ormstopwatch.class.inc.php'); require_once('ormpassword.class.inc.php'); require_once('ormcaselog.class.inc.php'); require_once('ormlinkset.class.inc.php'); +require_once('ormtagset.class.inc.php'); require_once('htmlsanitizer.class.inc.php'); require_once(APPROOT.'sources/autoload.php'); require_once('customfieldshandler.class.inc.php'); @@ -4210,7 +4211,7 @@ class AttributeEnum extends AttributeString { if ($oFormField === null) { - // TODO : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } @@ -5297,7 +5298,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid { if ($oFormField === null) { - // TODO : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value $sFormFieldClass = static::GetFormFieldClass(); $oFormField = new $sFormFieldClass($this->GetCode()); } @@ -5363,7 +5364,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey unset($aParams[$idx]); $idx = array_search('jointype', $aParams); unset($aParams[$idx]); - return $aParams; // TODO: mettre les bons parametres ici !! + return $aParams; // Later: mettre les bons parametres ici !! } public function GetEditClass() {return "ExtKey";} @@ -5899,23 +5900,216 @@ class AttributeExternalField extends AttributeDefinition */ class AttributeTagSet extends AttributeString { - //TODO SQL type length (nb of tags per record, max tag length) - //TODO implement ?? - //TODO specific filters - public function RequiresIndex() - { - return true; - } + public function GetEditClass() {return "TagSet";} - public function RequiresFullTextIndex() - { - return true; - } + protected function GetSQLCol($bFullSpec = false) + { + return 'VARCHAR(1024)' + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function RequiresIndex() {return true;} + + public function RequiresFullTextIndex() {return true;} + + public function Equals($val1, $val2) { + if (($val1 instanceof ormTagSet) && ($val2 instanceof ormTagSet)) + { + return $val1->Equals($val2); + } + return ($val1 == $val2); + } + + /** + * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! + * + * @param $proposedValue + * @param $oHostObj + * + * @return mixed + * @throws \Exception + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + $oTagSet = new ormTagSet($this->GetHostClass(), $this->GetCode()); + if (is_string($proposedValue) && !empty($proposedValue)) + { + $aTagCodes = explode(' ', "$proposedValue"); + $oTagSet->SetValue($aTagCodes); + } + return $oTagSet; + } + + public function ScalarToSQL($value) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + return null; + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * + */ + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + $sRes = "\n"; + $aValues = $value->GetValue(); + if (!empty($aValuess)) + { + $sRes .= ''.implode('', $aValues).''; + } + $sRes .= "\n"; + } + else + { + $sRes = ''; + } + return $sRes; + } + + /** + * @param $value + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return mixed|string + * @throws \CoreException + */ + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); + + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aValues = $value->GetValue(); + $sRes = implode($sSepItem, $aValues); + } + else + { + $sRes = ''; + } + $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); + $sRes = $sTextQualifier.$sRes.$sTextQualifier; + return $sRes; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text (unlocalized) representation', + 'html' => 'HTML representation (unordered list)', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param mixed $value The current value of the field + * @param string $sVerb The verb specifying the representation of the value + * @param DBObject $oHostObject The object + * @param bool $bLocalize Whether or not to localize the value + * + * @return string + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aValues = $value->GetValue(); + + switch ($sVerb) + { + case '': + return implode("\n", $aValues); + + case 'html': + return '
  • '.implode("
  • ", $aValues).'
'; + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + throw new CoreUnexpectedValue("Bad value '$value' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param \ormTagSet $value + * + * @return array + * @throws \CoreException + */ + public function GetForJSON($value) + { + $aRet = array(); + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aRet = $value->GetValue(); + } + return $aRet; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param $json + * + * @return \ormTagSet + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function FromJSONToValue($json) + { + $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode()); + $oSet->SetValue($json); + return $oSet; + } - public function IsNullAllowed() - { - return true; - } } /** diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 06cd005eba..3c5b06e413 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -7105,6 +7105,12 @@ abstract class MetaModel return $sRet; } + public static function GetTagDataClass($sClass, $sAttCode) + { + $sTagSuffix = $sClass.'_'.$sAttCode; + return 'TagSetFieldDataFor_'.$sTagSuffix; + } + } diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php new file mode 100644 index 0000000000..8a617c5e97 --- /dev/null +++ b/core/ormtagset.class.inc.php @@ -0,0 +1,294 @@ + + * + */ + +/** + * Created by PhpStorm. + * Date: 24/08/2018 + * Time: 14:35 + */ + +require_once('dbobjectiterator.php'); + +final class ormTagSet +{ + private $sClass; // class of the tag + private $sAttCode; // attcode of the tag + + private $aAllowedTags; + private $oOriginalSet; + private $aOriginalObjects = null; + + /** + * @var bool + */ + private $bHasDelta = false; + + /** + * Object from the original set, minus the removed objects + * @var DBObject[] array of iObjectId => DBObject + */ + private $aPreserved = array(); + + /** + * @var DBObject[] New items + */ + private $aAdded = array(); + + /** + * @var int[] Removed items + */ + private $aRemoved = array(); + + /** + * __toString magical function overload. + */ + public function __toString() + { + return ''; + } + + /** + * ormTagSet constructor. + * + * @param string $sClass + * @param string $sAttCode + * @param DBObjectSet|null $oOriginalSet + * + * @throws \Exception + */ + public function __construct($sClass, $sAttCode, DBObjectSet $oOriginalSet = null) + { + $this->sAttCode = $sAttCode; + $this->oOriginalSet = $oOriginalSet ? clone $oOriginalSet : null; + + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!$oAttDef instanceof AttributeTagSet) + { + throw new Exception("ormTagSet: field {$sClass}:{$sAttCode} is not a tag"); + } + $this->sClass = $sClass; + } + + /** + * + * @param array $aTagCodes + * + * @throws \CoreException + * @throws \CoreUnexpectedValue when a code is invalid + */ + public function SetValue($aTagCodes) + { + if (!is_array($aTagCodes)) + { + throw new CoreUnexpectedValue("Wrong value {$aTagCodes} for {$this->sClass}:{$this->sAttCode}"); + } + + $oTags = array(); + foreach($aTagCodes as $sTagCode) + { + $oTag = $this->GetTagFromCode($sTagCode); + $oTags[$oTag->GetKey()] = $oTag; + } + + $this->aPreserved = &$oTags; + $this->aRemoved = array(); + $this->aAdded = array(); + $this->aOriginalObjects = $oTags; + $this->bHasDelta = false; + } + + /** + * @return array of tag codes + */ + public function GetValue() + { + $aValue = array(); + foreach ($this->aPreserved as $oTag) + { + try + { + $aValue[] = $oTag->Get('tag_code'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + foreach ($this->aAdded as $oTag) + { + try + { + $aValue[] = $oTag->Get('tag_code'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + + sort($aValue); + + return $aValue; + } + + /** + * @param $sTagCode + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function AddTag($sTagCode) + { + if ($this->IsTagInList($this->aPreserved, $sTagCode) || $this->IsTagInList($this->aAdded, $sTagCode)) + { + // nothing to do, already existing tag + return; + } + // if removed then added again + if (($oTag = $this->RemoveTagFromList($this->aRemoved, $sTagCode)) !== false) + { + // put it back into preserved + $this->aPreserved[] = $oTag; + } + else + { + $this->aAdded[] = $this->GetTagFromCode($sTagCode); + } + $this->UpdateHasDeltaFlag(); + } + + /** + * @param $sTagCode + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function RemoveTag($sTagCode) + { + if ($this->IsTagInList($this->aRemoved, $sTagCode)) + { + // nothing to do, already removed tag + return; + } + // if added then remove it + if (($oTag = $this->RemoveTagFromList($this->aAdded, $sTagCode)) === false) + { + // if present then remove it + if (($oTag = $this->RemoveTagFromList($this->aPreserved, $sTagCode)) !== false) + { + $this->aRemoved[] = $oTag; + } + } + $this->UpdateHasDeltaFlag(); + } + + private function IsTagInList($aTagList, $sTagCode) + { + foreach ($aTagList as $oTag) + { + $sCode = $oTag->Get('tag_code'); + if ($sCode === $sTagCode) + { + return true; + } + } + return false; + } + + private function RemoveTagFromList(&$aTagList, $sTagCode) + { + foreach ($aTagList as $index => $oTag) + { + $sCode = $oTag->Get('tag_code'); + if ($sCode === $sTagCode) + { + unset($aTagList[$index]); + return $oTag; + } + } + return false; + } + + private function UpdateHasDeltaFlag() + { + if ((count($this->aAdded) == 0) && (count($this->aRemoved) == 0)) + { + $this->bHasDelta = false; + } + else + { + $this->bHasDelta = true; + } + } + + /** + * @param $sTagCode + * + * @return DBObject tag + * @throws \CoreUnexpectedValue + * @throws \CoreException + */ + private function GetTagFromCode($sTagCode) + { + $aAllowedTags = $this->GetAllowedTags(); + foreach($aAllowedTags as $oAllowedTag) + { + if ($oAllowedTag->Get('tag_code') === $sTagCode) + { + return $oAllowedTag; + } + } + throw new CoreUnexpectedValue("{$sTagCode} is not defined as a valid tag for {$this->sClass}:{$this->sAttCode}"); + } + + /** + * @return array + * @throws \CoreException + * @throws \Exception + */ + private function GetAllowedTags() + { + if (!$this->aAllowedTags) + { + $oSearch = new DBObjectSearch($this->GetTagDataClass()); + $oSearch->AddCondition('tag_class', $this->sClass); + $oSearch->AddCondition('tag_attcode', $this->sAttCode); + $oSet = new DBObjectSet($oSearch); + $this->aAllowedTags = $oSet->ToArray(); + } + return $this->aAllowedTags; + } + + /** + * Compare Tag Set + * + * @param \ormTagSet $other + * + * @return bool true if same tag set + */ + public function Equals(ormTagSet $other) + { + if ($this->GetTagDataClass() !== $other->GetTagDataClass()) + { + return false; + } + return implode(' ',$this->GetValue()) === implode(' ', $other->GetValue()); + } + +} \ No newline at end of file diff --git a/core/tagsetfield.class.inc.php b/core/tagsetfield.class.inc.php index a8a785fb98..5a583de36a 100644 --- a/core/tagsetfield.class.inc.php +++ b/core/tagsetfield.class.inc.php @@ -27,55 +27,69 @@ */ abstract class TagSetFieldData extends cmdbAbstractObject { - public static function Init() - { - $aParams = array - ( - 'category' => 'bizmodel', - 'key_type' => 'autoincrement', - 'name_attcode' => array('tag_label'), - 'state_attcode' => '', - 'reconc_keys' => array('tag_code'), - 'db_table' => 'priv_tagfielddata', - 'db_key_field' => 'id', - 'db_finalclass_field' => 'finalclass', - ); + public static function Init() + { + $aParams = array + ( + 'category' => 'bizmodel', + 'key_type' => 'autoincrement', + 'name_attcode' => array('tag_label'), + 'state_attcode' => '', + 'reconc_keys' => array('tag_code'), + 'db_table' => 'priv_tagfielddata', + 'db_key_field' => 'id', + 'db_finalclass_field' => 'finalclass', + ); - MetaModel::Init_Params($aParams); - MetaModel::Init_InheritAttributes(); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); - MetaModel::Init_AddAttribute(new AttributeString("tag_code", array( - "allowed_values" => null, - "sql" => 'tag_code', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeString("tag_label", array( - "allowed_values" => null, - "sql" => 'tag_label', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeString("tag_description", array( - "allowed_values" => null, - "sql" => 'tag_description', - "default_value" => '', - "is_null_allowed" => false, - "depends_on" => array() - ))); - MetaModel::Init_AddAttribute(new AttributeBoolean("is_default", array( - "allowed_values" => null, - "sql" => "is_default", - "default_value" => false, - "is_null_allowed" => false, - "depends_on" => array() - ))); + MetaModel::Init_AddAttribute(new AttributeString("tag_code", array( + "allowed_values" => null, + "sql" => 'tag_code', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("tag_label", array( + "allowed_values" => null, + "sql" => 'tag_label', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("tag_description", array( + "allowed_values" => null, + "sql" => 'tag_description', + "default_value" => '', + "is_null_allowed" => true, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("tag_class", array( + "allowed_values" => null, + "sql" => 'tag_class', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeString("tag_attcode", array( + "allowed_values" => null, + "sql" => 'tag_attcode', + "default_value" => '', + "is_null_allowed" => false, + "depends_on" => array() + ))); + MetaModel::Init_AddAttribute(new AttributeBoolean("is_default", array( + "allowed_values" => null, + "sql" => "is_default", + "default_value" => false, + "is_null_allowed" => false, + "depends_on" => array() + ))); - MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - } + MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'is_default')); + MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default')); + MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default')); + } } \ No newline at end of file diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 74f6d7e557..4bfc212eb0 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -198,6 +198,8 @@ tagfield + + true
diff --git a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php index 09d1133606..d1704a9e91 100755 --- a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php @@ -74,8 +74,9 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Ticket/Attribute:close_date+' => '', 'Class:Ticket/Attribute:private_log' => 'Private log', 'Class:Ticket/Attribute:private_log+' => '', - 'Class:Ticket/Attribute:contacts_list' => 'Contacts', + 'Class:Ticket/Attribute:contacts_list' => 'Contacts', 'Class:Ticket/Attribute:contacts_list+' => 'All the contacts linked to this ticket', + 'Class:Ticket/Attribute:tagfield' => 'Tag Test', 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', 'Class:Ticket/Attribute:functionalcis_list+' => 'All the configuration items impacted by this ticket. Items marked as "Computed" have been automatically marked as impacted. Items marked as "Not impacted" are excluded from the impact.', 'Class:Ticket/Attribute:workorders_list' => 'Work orders', diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index 8aefdc5b2c..3664f03bc9 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -34,7 +34,10 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:AttributeLinkedSet' => 'Array of objects', 'Core:AttributeLinkedSet+' => 'Any kind of objects of the same class or subclass', - 'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)', + 'Core:AttributeTagSet' => 'List of tags', + 'Core:AttributeTagSet+' => '', + + 'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)', 'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class', 'Core:AttributeInteger' => 'Integer', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index bb6ae6ae2f..ffd70fcd5c 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -426,7 +426,9 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:appUserPreferences/Attribute:preferences+' => '', 'Core:AttributeLinkedSet' => 'Objets liés (1-n)', 'Core:AttributeLinkedSet+' => 'Liste d\'objets d\'une classe donnée et pointant sur l\'objet courant', - 'Core:AttributeLinkedSetIndirect' => 'Objets liés (1-n)', + 'Core:AttributeTagSet' => 'Liste d\'étiquettes', + 'Core:AttributeTagSet+' => '', + 'Core:AttributeLinkedSetIndirect' => 'Objets liés (1-n)', 'Core:AttributeLinkedSetIndirect+' => 'Liste d\'objets d\'une classe donnée et liés à l\'objet courant via une classe intermédiaire', 'Core:AttributeInteger' => 'Nombre entier', 'Core:AttributeInteger+' => 'Valeur numérique entière', diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 8adb472ee4..1099bde056 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1388,19 +1388,25 @@ EOF { $aParameters['handler_class'] = $this->GetMandatoryPropString($oField, 'handler_class'); } - else + elseif ($sAttType == 'AttributeTagSet') + { + $aTagFieldsInfo[] = $sAttCode; + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; + $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); + } + else { - $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" - $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); - $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); - $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); - $aParameters['depends_on'] = $sDependencies; + $aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')" + $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); + $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); + $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); + $aParameters['depends_on'] = $sDependencies; } - if ($sAttType == 'AttributeTagSet') - { - $aTagFieldsInfo[] = $sAttCode; - } + // Optional parameters (more for historical reasons) // Added if present... diff --git a/test/ItopDataTestCase.php b/test/ItopDataTestCase.php index 0123f3bae5..58c42915b9 100644 --- a/test/ItopDataTestCase.php +++ b/test/ItopDataTestCase.php @@ -171,7 +171,35 @@ class ItopDataTestCase extends ItopTestCase return $oTicket; } - /** + /** + * Create a Ticket in database + * + * @param string $sClass + * @param string $sAttCode + * @param string $sTagCode + * @param string $sTagLabel + * @param string $sTagDescription + * + * @return \TagSetFieldData + * @throws \CoreException + */ + protected function CreateTagData($sClass, $sAttCode, $sTagCode, $sTagLabel, $sTagDescription = '') + { + $sTagClass = MetaModel::GetTagDataClass($sClass, $sAttCode); + $oTagData = self::createObject($sTagClass, array( + 'tag_code' => $sTagCode, + 'tag_label' => $sTagLabel, + 'tag_class' => $sClass, + 'tag_attcode' => $sAttCode, + 'tag_description' => $sTagDescription, + )); + $this->debug("\nCreated {$oTagData->Get('tag_code')} ({$oTagData->Get('tag_label')})"); + + /** @var \TagSetFieldData $oTagData */ + return $oTagData; + } + + /** * Create a UserRequest in database * * @param int $iNum diff --git a/test/core/ormTagSetTest.php b/test/core/ormTagSetTest.php new file mode 100644 index 0000000000..b1bf7fae10 --- /dev/null +++ b/test/core/ormTagSetTest.php @@ -0,0 +1,155 @@ + + * + */ + +/** + * Created by PhpStorm. + * User: Eric + * Date: 27/08/2018 + * Time: 17:26 + */ + +namespace Combodo\iTop\Test\UnitTest\Core; + +use Combodo\iTop\Test\UnitTest\ItopDataTestCase; +use Exception; +use ormTagSet; + +/** + * Tests of the ormTagSet class + * + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + * @backupGlobals disabled + */class ormTagSetTest extends ItopDataTestCase +{ + + /** + * @throws Exception + */ + protected function setUp() + { + parent::setUp(); + } + + public function testGetTagDataClass() + { + $oTagSet = new ormTagSet('Ticket', 'tagfield'); + static::assertEquals($oTagSet->GetTagDataClass(), 'TagSetFieldDataFor_Ticket_tagfield'); + } + + public function testGetValue() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + + $oTagSet = new ormTagSet('Ticket', 'tagfield'); + static::assertEquals($oTagSet->GetValue(), array()); + + $oTagSet->AddTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag1')); + + $oTagSet->AddTag('tag2'); + static::assertEquals($oTagSet->GetValue(), array('tag1', 'tag2')); + } + + public function testAddTag() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + + $oTagSet = new ormTagSet('Ticket', 'tagfield'); + + $oTagSet->AddTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag1')); + + $oTagSet->SetValue(array('tag1', 'tag2')); + static::assertEquals($oTagSet->GetValue(), array('tag1', 'tag2')); + + $oTagSet->RemoveTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag2')); + + $oTagSet->AddTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag1', 'tag2')); + } + + public function testEquals() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + + $oTagSet1 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet1->AddTag('tag1'); + static::assertTrue($oTagSet1->Equals($oTagSet1)); + + $oTagSet2 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet2->SetValue(array('tag1')); + + static::assertTrue($oTagSet1->Equals($oTagSet2)); + + $oTagSet1->AddTag('tag2'); + static::assertFalse($oTagSet1->Equals($oTagSet2)); + } + + public function testSetValue() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + + $oTagSet = new ormTagSet('Ticket', 'tagfield'); + + $oTagSet->SetValue(array('tag1')); + static::assertEquals($oTagSet->GetValue(), array('tag1')); + + $oTagSet->SetValue(array('tag1', 'tag2')); + static::assertEquals($oTagSet->GetValue(), array('tag1', 'tag2')); + + } + + public function testRemoveTag() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + + $oTagSet = new ormTagSet('Ticket', 'tagfield'); + $oTagSet->RemoveTag('tag_unknown'); + static::assertEquals($oTagSet->GetValue(), array()); + + $oTagSet->SetValue(array('tag1')); + $oTagSet->RemoveTag('tag_unknown'); + static::assertEquals($oTagSet->GetValue(), array('tag1')); + + $oTagSet->SetValue(array('tag1', 'tag2')); + $oTagSet->RemoveTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag2')); + + $oTagSet->AddTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag1', 'tag2')); + + $oTagSet->RemoveTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag2')); + + $oTagSet->RemoveTag('tag1'); + static::assertEquals($oTagSet->GetValue(), array('tag2')); + + $oTagSet->RemoveTag('tag2'); + static::assertEquals($oTagSet->GetValue(), array()); + } +} From 5e7db7a27e6d140432262df41016d8f22d04855e Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 28 Aug 2018 13:54:15 +0000 Subject: [PATCH 007/113] @@@@ OQL parsing SVN:b931[6025] --- core/oql/build/build.cmd | 2 +- core/oql/oql-lexer.plex | 1 + core/oql/oql-parser.y | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/oql/build/build.cmd b/core/oql/build/build.cmd index 9edd6d3d47..b31c58bfd6 100644 --- a/core/oql/build/build.cmd +++ b/core/oql/build/build.cmd @@ -1,6 +1,6 @@ rem must be run with current directory = the directory of the batch rem PEAR is required to build -php -d include_path=".;C:\iTop\PHP\PEAR" ".\PHP\LexerGenerator\cli.php" ..\oql-lexer.plex +php -d include_path=".;C:\Dev\wamp64\bin\php\php5.6.31\pear" ".\PHP\LexerGenerator\cli.php" ..\oql-lexer.plex php ".\PHP\ParserGenerator\cli.php" ..\oql-parser.y php -r "echo date('Y-m-d');" > ..\version.txt pause \ No newline at end of file diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index fc0101b424..c08a2f852b 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -88,6 +88,7 @@ where = "WHERE" join = "JOIN" on = "ON" coma = "," +matches = "MATCHES" par_open = "(" par_close = ")" math_div = "/" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index 03bb3fe525..b0aff4537c 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -105,6 +105,7 @@ expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; } expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio1(A) ::= expression_basic(X). { A = X; } +expression_prio1(A) ::= match_expression(X). { A = X; } expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio2(A) ::= expression_prio1(X). { A = X; } @@ -117,6 +118,9 @@ expression_prio4(A) ::= expression_prio3(X). { A = X; } expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); } +match_expression(A) ::= field_id(X) MATCHES scalar(Y). { A = new MatchOqlExpression(X, Y); } + + list(A) ::= PAR_OPEN list_items(X) PAR_CLOSE. { A = new ListOqlExpression(X); } @@ -235,7 +239,6 @@ func_name(A) ::= F_FLOOR(X). { A=X; } func_name(A) ::= F_INET_ATON(X). { A=X; } func_name(A) ::= F_INET_NTOA(X). { A=X; } - %code { class OQLParserException extends OQLException From 0ee4b52baa46cad10047de587f6334dec3531aa5 Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 28 Aug 2018 13:54:26 +0000 Subject: [PATCH 008/113] =?UTF-8?q?N=C2=B0931=20rights=20on=20TagSetFieldD?= =?UTF-8?q?ata=20on=20ConfigurationManager=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6026] --- .../2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml index 66e1bcf753..ec98e4c500 100755 --- a/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml +++ b/datamodels/2.x/itop-profiles-itil/datamodel.itop-profiles-itil.xml @@ -27,6 +27,7 @@ +
From d40ffd944f894df0e688229b7f23593e1e44a74b Mon Sep 17 00:00:00 2001 From: Pierre Goiffon Date: Tue, 28 Aug 2018 13:55:40 +0000 Subject: [PATCH 009/113] Revert "@@@@ OQL parsing" This reverts commit 0beafc1e9a7c4a629a458aec669aa5e11b27db49. SVN:b931[6027] --- core/oql/build/build.cmd | 2 +- core/oql/oql-lexer.plex | 1 - core/oql/oql-parser.y | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/oql/build/build.cmd b/core/oql/build/build.cmd index b31c58bfd6..9edd6d3d47 100644 --- a/core/oql/build/build.cmd +++ b/core/oql/build/build.cmd @@ -1,6 +1,6 @@ rem must be run with current directory = the directory of the batch rem PEAR is required to build -php -d include_path=".;C:\Dev\wamp64\bin\php\php5.6.31\pear" ".\PHP\LexerGenerator\cli.php" ..\oql-lexer.plex +php -d include_path=".;C:\iTop\PHP\PEAR" ".\PHP\LexerGenerator\cli.php" ..\oql-lexer.plex php ".\PHP\ParserGenerator\cli.php" ..\oql-parser.y php -r "echo date('Y-m-d');" > ..\version.txt pause \ No newline at end of file diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index c08a2f852b..fc0101b424 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -88,7 +88,6 @@ where = "WHERE" join = "JOIN" on = "ON" coma = "," -matches = "MATCHES" par_open = "(" par_close = ")" math_div = "/" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index b0aff4537c..03bb3fe525 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -105,7 +105,6 @@ expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; } expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio1(A) ::= expression_basic(X). { A = X; } -expression_prio1(A) ::= match_expression(X). { A = X; } expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio2(A) ::= expression_prio1(X). { A = X; } @@ -118,9 +117,6 @@ expression_prio4(A) ::= expression_prio3(X). { A = X; } expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); } -match_expression(A) ::= field_id(X) MATCHES scalar(Y). { A = new MatchOqlExpression(X, Y); } - - list(A) ::= PAR_OPEN list_items(X) PAR_CLOSE. { A = new ListOqlExpression(X); } @@ -239,6 +235,7 @@ func_name(A) ::= F_FLOOR(X). { A=X; } func_name(A) ::= F_INET_ATON(X). { A=X; } func_name(A) ::= F_INET_NTOA(X). { A=X; } + %code { class OQLParserException extends OQLException From cd2ea3793e96426c44a3e22e069633efb21e84b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Tue, 28 Aug 2018 14:16:01 +0000 Subject: [PATCH 010/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20Fix=20Attribute?= =?UTF-8?q?=20definition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6028] --- application/applicationextension.inc.php | 2 +- core/attributedef.class.inc.php | 2 +- core/ormtagset.class.inc.php | 5 ++++- .../datamodel.itop-request-mgmt-itil.xml | 3 +++ datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml | 3 +++ datamodels/2.x/itop-tickets/en.dict.itop-tickets.php | 2 +- datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php | 3 ++- 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/application/applicationextension.inc.php b/application/applicationextension.inc.php index 6ca36e94b1..fcca471d6c 100644 --- a/application/applicationextension.inc.php +++ b/application/applicationextension.inc.php @@ -1169,7 +1169,7 @@ class RestUtils { if (!is_array($value)) { - throw new Exception("A tag set must be defined by an array of objects"); + throw new Exception("A tag set must be defined by an array of tag codes"); } $value = $oAttDef->FromJSONToValue($value); } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index fa9c1fc456..ab764fefa3 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -5932,7 +5932,7 @@ class AttributeTagSet extends AttributeString */ public function MakeRealValue($proposedValue, $oHostObj) { - $oTagSet = new ormTagSet($this->GetHostClass(), $this->GetCode()); + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); if (is_string($proposedValue) && !empty($proposedValue)) { $aTagCodes = explode(' ', "$proposedValue"); diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index 8a617c5e97..afe67458a2 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -25,7 +25,6 @@ * Time: 14:35 */ -require_once('dbobjectiterator.php'); final class ormTagSet { @@ -291,4 +290,8 @@ final class ormTagSet return implode(' ',$this->GetValue()) === implode(' ', $other->GetValue()); } + public function GetTagDataClass() + { + return MetaModel::GetTagDataClass($this->sClass, $this->sAttCode); + } } \ No newline at end of file diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index a2fe11a65a..84c374074a 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -1500,6 +1500,9 @@ 40 + + 76 + diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index 4bfc212eb0..10ac91f9c3 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -284,6 +284,9 @@ 75 + + 76 + 80 diff --git a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php index d1704a9e91..94d810ed28 100755 --- a/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/en.dict.itop-tickets.php @@ -76,7 +76,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:Ticket/Attribute:private_log+' => '', 'Class:Ticket/Attribute:contacts_list' => 'Contacts', 'Class:Ticket/Attribute:contacts_list+' => 'All the contacts linked to this ticket', - 'Class:Ticket/Attribute:tagfield' => 'Tag Test', + 'Class:Ticket/Attribute:tagfield' => 'Tag', 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', 'Class:Ticket/Attribute:functionalcis_list+' => 'All the configuration items impacted by this ticket. Items marked as "Computed" have been automatically marked as impacted. Items marked as "Not impacted" are excluded from the impact.', 'Class:Ticket/Attribute:workorders_list' => 'Work orders', diff --git a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php index 2b84a7de9d..e30d28cfc6 100755 --- a/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php +++ b/datamodels/2.x/itop-tickets/fr.dict.itop-tickets.php @@ -63,7 +63,8 @@ Dict::Add('FR FR', 'French', 'Français', array( 'Class:Ticket/Attribute:private_log+' => '', 'Class:Ticket/Attribute:contacts_list' => 'Contacts', 'Class:Ticket/Attribute:contacts_list+' => '', - 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', + 'Class:Ticket/Attribute:tagfield' => 'Etiquette', + 'Class:Ticket/Attribute:functionalcis_list' => 'CIs', 'Class:Ticket/Attribute:functionalcis_list+' => 'Tous les éléments de configuration impactés par ce ticket. Les éléments marqués comme "Calculés" sont le résultat du calcul de l\'analyse d\'impact. Les éléments marqués comme "Non impactés" sont exclus de cette analyse.', 'Class:Ticket/Attribute:workorders_list' => 'Tâches', 'Class:Ticket/Attribute:workorders_list+' => '', From ecdc4076d9d0e7f27f1530aa86b788927c89dc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Tue, 28 Aug 2018 15:12:36 +0000 Subject: [PATCH 011/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20Edition=20in=20s?= =?UTF-8?q?tring=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6029] --- core/attributedef.class.inc.php | 45 +++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ab764fefa3..438ec07e8d 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -5375,7 +5375,7 @@ class AttributeHierarchicalKey extends AttributeExternalKey /* * The target class is the class for which the attribute has been defined first - */ + */ public function SetHostClass($sHostClass) { if (!isset($this->m_sTargetClass)) @@ -5900,7 +5900,23 @@ class AttributeExternalField extends AttributeDefinition */ class AttributeTagSet extends AttributeString { - public function GetEditClass() {return "TagSet";} + public function GetEditClass() { + return "String"; + } + + public function GetEditValue($value, $oHostObj = null) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + return ''; + } protected function GetSQLCol($bFullSpec = false) { @@ -5921,6 +5937,16 @@ class AttributeTagSet extends AttributeString return ($val1 == $val2); } + public function GetType() + { + return Dict::S('Core:TagSetFieldData'); + } + + public function GetTypeDesc() + { + return Dict::S('Core:TagSetFieldData+'); + } + /** * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! * @@ -6110,6 +6136,21 @@ class AttributeTagSet extends AttributeString return $oSet; } + /** + * The part of the current attribute in the object's signature, for the supplied value + * @param mixed $value The value of this attribute for the object + * @return string The "signature" for this field/attribute + */ + public function Fingerprint($value) + { + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + return parent::Fingerprint($value); + } + } /** From a03c5530009567d88e6739c3e1b752d9884bc8a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Wed, 29 Aug 2018 10:10:44 +0000 Subject: [PATCH 012/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20attribute=20defi?= =?UTF-8?q?nition=20dev=20(continued)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6030] --- core/attributedef.class.inc.php | 69 +++++++++++++++++------- dictionaries/en.dictionary.itop.core.php | 20 +++++-- setup/compiler.class.inc.php | 4 +- 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 438ec07e8d..580bab4f45 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -279,7 +279,7 @@ abstract class AttributeDefinition */ public function CheckValue(DBObject $oHostObject, $value) { - // todo: factorize here the cases implemented into DBObject + // later: factorize here the cases implemented into DBObject return true; } @@ -5898,8 +5898,10 @@ class AttributeExternalField extends AttributeDefinition * @see TagSetFieldData * @since 2.6 N°931 tag fields */ -class AttributeTagSet extends AttributeString +class AttributeTagSet extends AttributeDBField { + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + public function GetEditClass() { return "String"; } @@ -5920,11 +5922,16 @@ class AttributeTagSet extends AttributeString protected function GetSQLCol($bFullSpec = false) { - return 'VARCHAR(1024)' + return 'VARCHAR(255)' .CMDBSource::GetSqlStringColumnDefinition() .($bFullSpec ? $this->GetSQLColSpec() : ''); } + public function GetMaxSize() + { + return 255; + } + public function RequiresIndex() {return true;} public function RequiresFullTextIndex() {return true;} @@ -5937,16 +5944,6 @@ class AttributeTagSet extends AttributeString return ($val1 == $val2); } - public function GetType() - { - return Dict::S('Core:TagSetFieldData'); - } - - public function GetTypeDesc() - { - return Dict::S('Core:TagSetFieldData+'); - } - /** * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! * @@ -5964,9 +5961,43 @@ class AttributeTagSet extends AttributeString $aTagCodes = explode(' ', "$proposedValue"); $oTagSet->SetValue($aTagCodes); } + elseif ($proposedValue instanceof ormTagSet) + { + $oTagSet = $proposedValue; + } return $oTagSet; } + + public function GetNullValue() { + return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + } + + public function IsNull($proposedValue) + { + /** @var \ormTagSet $proposedValue */ + return count($proposedValue->GetValue()) == 0; + } + + /** + * To be overloaded for localized enums + * + * @param $sValue + * + * @return string label corresponding to the given value (in plain text) + */ + public function GetValueLabel($sValue) + { + // TODO + return $sValue; + } + + /** + * @param $value + * + * @return string + * @throws \CoreWarning + */ public function ScalarToSQL($value) { if (empty($value)) @@ -5992,6 +6023,7 @@ class AttributeTagSet extends AttributeString */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { + // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { $aValues = $value->GetValue(); @@ -6010,6 +6042,7 @@ class AttributeTagSet extends AttributeString */ public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { + // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { $sRes = "\n"; @@ -6040,19 +6073,16 @@ class AttributeTagSet extends AttributeString */ public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) { - $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); - + // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { $aValues = $value->GetValue(); - $sRes = implode($sSepItem, $aValues); + $sRes = implode(' ', $aValues); } else { $sRes = ''; } - $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); - $sRes = $sTextQualifier.$sRes.$sTextQualifier; return $sRes; } @@ -6080,6 +6110,7 @@ class AttributeTagSet extends AttributeString */ public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) { + // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { $aValues = $value->GetValue(); @@ -6087,7 +6118,7 @@ class AttributeTagSet extends AttributeString switch ($sVerb) { case '': - return implode("\n", $aValues); + return implode(' ', $aValues); case 'html': return '
  • '.implode("
  • ", $aValues).'
'; diff --git a/dictionaries/en.dictionary.itop.core.php b/dictionaries/en.dictionary.itop.core.php index 3664f03bc9..a46a8c5315 100644 --- a/dictionaries/en.dictionary.itop.core.php +++ b/dictionaries/en.dictionary.itop.core.php @@ -37,6 +37,12 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:AttributeTagSet' => 'List of tags', 'Core:AttributeTagSet+' => '', + 'Core:AttributeCaseLog' => 'Log', + 'Core:AttributeCaseLog+' => '', + + 'Core:AttributeMetaEnum' => 'Computed enum', + 'Core:AttributeMetaEnum+' => '', + 'Core:AttributeLinkedSetIndirect' => 'Array of objects (N-N)', 'Core:AttributeLinkedSetIndirect+' => 'Any kind of objects [subclass] of the same class', @@ -620,7 +626,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:name' => 'Name', 'Class:SynchroDataSource/Attribute:name+' => 'Name', 'Class:SynchroDataSource/Attribute:description' => 'Description', - 'Class:SynchroDataSource/Attribute:status' => 'Status', //TODO: enum values + 'Class:SynchroDataSource/Attribute:status' => 'Status', 'Class:SynchroDataSource/Attribute:scope_class' => 'Target class', 'Class:SynchroDataSource/Attribute:user_id' => 'User', 'Class:SynchroDataSource/Attribute:notify_contact_id' => 'Contact to notify', @@ -629,7 +635,7 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:url_icon+' => 'Hyperlink a (small) image representing the application with which iTop is synchronized', 'Class:SynchroDataSource/Attribute:url_application' => 'Application\'s hyperlink', 'Class:SynchroDataSource/Attribute:url_application+' => 'Hyperlink to the iTop object in the external application with which iTop is synchronized (if applicable). Possible placeholders: $this->attribute$ and $replica->primary_key$', - 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy', //TODO enum values + 'Class:SynchroDataSource/Attribute:reconciliation_policy' => 'Reconciliation policy', 'Class:SynchroDataSource/Attribute:full_load_periodicity' => 'Full load interval', 'Class:SynchroDataSource/Attribute:full_load_periodicity+' => 'A complete reload of all data must occur at least as often as specified here', 'Class:SynchroDataSource/Attribute:action_on_zero' => 'Action on zero', @@ -640,7 +646,6 @@ Dict::Add('EN US', 'English', 'English', array( 'Class:SynchroDataSource/Attribute:action_on_multiple+' => 'Action taken when the search returns more than one object', 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed', 'Class:SynchroDataSource/Attribute:user_delete_policy+' => 'Who is allowed to delete synchronized objects', - 'Class:SynchroDataSource/Attribute:user_delete_policy' => 'Users allowed', 'Class:SynchroDataSource/Attribute:delete_policy/Value:never' => 'Nobody', 'Class:SynchroDataSource/Attribute:delete_policy/Value:depends' => 'Administrators only', 'Class:SynchroDataSource/Attribute:delete_policy/Value:always' => 'All allowed users', @@ -691,7 +696,6 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:Synchro:label_obj_new_updated' => 'Updated (%1$s)', 'Core:Synchro:label_obj_created' => 'Created (%1$s)', 'Core:Synchro:label_obj_new_errors' => 'Errors (%1$s)', - 'Core:Synchro:History' => 'Synchronization History', 'Core:SynchroLogTitle' => '%1$s - %2$s', 'Core:Synchro:Nb_Replica' => 'Replica processed: %1$s', 'Core:Synchro:Nb_Class:Objects' => '%1$s: %2$s', @@ -905,3 +909,11 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:Validator:MustBeInteger' => 'Must be an integer', 'Core:Validator:MustSelectOne' => 'Please, select one', )); + +// +// Class: TagSetFieldData +// +Dict::Add('EN US', 'English', 'English', array( + 'Class:TagSetFieldData' => 'List of tags', + 'Class:TagSetFieldData+' => '', +)); diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 1099bde056..e64074cb0c 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1395,7 +1395,7 @@ EOF $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); $aParameters['depends_on'] = $sDependencies; - $aParameters['default_value'] = $this->GetPropString($oField, 'default_value', ''); + $aParameters['default_value'] = ''; } else { @@ -1406,8 +1406,6 @@ EOF $aParameters['depends_on'] = $sDependencies; } - - // Optional parameters (more for historical reasons) // Added if present... // From 1953c05b3375ea4ac5858591240c994c41f2ec27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Wed, 29 Aug 2018 10:26:36 +0000 Subject: [PATCH 013/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20-=20add=20label?= =?UTF-8?q?=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6031] --- core/attributedef.class.inc.php | 41 +++++++++++++++++++++++------- core/ormtagset.class.inc.php | 45 ++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 580bab4f45..637aee863b 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -6023,10 +6023,16 @@ class AttributeTagSet extends AttributeDBField */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { - // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { - $aValues = $value->GetValue(); + if ($bLocalize) + { + $aValues = $value->GetLabel(); + } + else + { + $aValues = $value->GetValue(); + } return implode(' ', $aValues); } return null; @@ -6042,12 +6048,17 @@ class AttributeTagSet extends AttributeDBField */ public function GetAsXML($value, $oHostObject = null, $bLocalize = true) { - // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { $sRes = "\n"; - $aValues = $value->GetValue(); - if (!empty($aValuess)) + if ($bLocalize) + { + $aValues = $value->GetLabel(); + } + else + { + $aValues = $value->GetValue(); + } if (!empty($aValuess)) { $sRes .= ''.implode('', $aValues).''; } @@ -6073,10 +6084,16 @@ class AttributeTagSet extends AttributeDBField */ public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) { - // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { - $aValues = $value->GetValue(); + if ($bLocalize) + { + $aValues = $value->GetLabel(); + } + else + { + $aValues = $value->GetValue(); + } $sRes = implode(' ', $aValues); } else @@ -6110,10 +6127,16 @@ class AttributeTagSet extends AttributeDBField */ public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) { - // TODO $bLocalize = true if (is_object($value) && ($value instanceof ormTagSet)) { - $aValues = $value->GetValue(); + if ($bLocalize) + { + $aValues = $value->GetLabel(); + } + else + { + $aValues = $value->GetValue(); + } switch ($sVerb) { diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index afe67458a2..ead0212f2d 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -28,8 +28,8 @@ final class ormTagSet { - private $sClass; // class of the tag - private $sAttCode; // attcode of the tag + private $sClass; // class of the tag field + private $sAttCode; // attcode of the tag field private $aAllowedTags; private $oOriginalSet; @@ -119,12 +119,12 @@ final class ormTagSet */ public function GetValue() { - $aValue = array(); + $aValues = array(); foreach ($this->aPreserved as $oTag) { try { - $aValue[] = $oTag->Get('tag_code'); + $aValues[] = $oTag->Get('tag_code'); } catch (CoreException $e) { IssueLog::Error($e->getMessage()); @@ -134,16 +134,47 @@ final class ormTagSet { try { - $aValue[] = $oTag->Get('tag_code'); + $aValues[] = $oTag->Get('tag_code'); } catch (CoreException $e) { IssueLog::Error($e->getMessage()); } } - sort($aValue); + sort($aValues); - return $aValue; + return $aValues; + } + + public function GetLabel() + { + $aLabels = array(); + foreach ($this->aPreserved as $oTag) + { + try + { + $aValues[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + foreach ($this->aAdded as $oTag) + { + try + { + $aValues[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + ksort($aValues); + foreach($aValues as $sLabel) + { + $aLabels[] = $sLabel; + } + return $aLabels; } /** From f458a774498d5daef0cd46325f22c0183c9ca8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Wed, 29 Aug 2018 16:50:37 +0000 Subject: [PATCH 014/113] =?UTF-8?q?N=C2=B0962:=20TagSet=20-=20Attribute=20?= =?UTF-8?q?definition=20(continued)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SVN:b931[6032] --- core/attributedef.class.inc.php | 55 ++++++++++++++++++++++++---- core/cmdbchangeop.class.inc.php | 58 ++++++++++++++++++++++++++++++ core/cmdbobject.class.inc.php | 14 +++++++- core/excelbulkexport.class.inc.php | 15 +++++--- core/metamodel.class.php | 2 +- core/tagsetfield.class.inc.php | 4 +-- css/light-grey.css | 8 +++++ css/light-grey.scss | 9 +++++ setup/compiler.class.inc.php | 1 - setup/xmldataloader.class.inc.php | 4 +++ 10 files changed, 154 insertions(+), 16 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 637aee863b..1b18299cdc 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -284,6 +284,11 @@ abstract class AttributeDefinition } // table, key field, name field + + /** + * @return string + * @deprecated never used + */ public function ListDBJoins() { return ""; @@ -710,7 +715,11 @@ abstract class AttributeDefinition { return null; } - + + /** + * @return mixed|null + * @deprecated never used + */ public function MakeValue() { $sComputeFunc = $this->Get("compute_func"); @@ -5898,9 +5907,15 @@ class AttributeExternalField extends AttributeDefinition * @see TagSetFieldData * @since 2.6 N°931 tag fields */ -class AttributeTagSet extends AttributeDBField +class AttributeTagSet extends AttributeDBFieldVoid { const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("is_null_allowed")); + } + public function GetDefaultValue(DBObject $oHostObject = null) {return null;} + public function IsNullAllowed() {return $this->Get("is_null_allowed");} public function GetEditClass() { return "String"; @@ -5968,6 +5983,24 @@ class AttributeTagSet extends AttributeDBField return $oTagSet; } + /** + * Get the value from a given string (plain text, CSV import) + * + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return mixed null if no match could be found + * @throws \Exception + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + // TODO $bLocalizedValue + return $this->MakeRealValue($sProposedValue, null); + } public function GetNullValue() { return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); @@ -6023,7 +6056,7 @@ class AttributeTagSet extends AttributeDBField */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { - if (is_object($value) && ($value instanceof ormTagSet)) + if ($value instanceof ormTagSet) { if ($bLocalize) { @@ -6033,9 +6066,19 @@ class AttributeTagSet extends AttributeDBField { $aValues = $value->GetValue(); } - return implode(' ', $aValues); + if (empty($aValues)) + { + return ''; + } + return ''.implode('', $aValues).''; } - return null; + if (is_string($value)) + { + $oValue = $this->MakeRealValue($value, $oHostObject); + return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); + } + return parent::GetAsHTML($value, $oHostObject, $bLocalize); + } /** @@ -6094,7 +6137,7 @@ class AttributeTagSet extends AttributeDBField { $aValues = $value->GetValue(); } - $sRes = implode(' ', $aValues); + $sRes = implode('|', $aValues); } else { diff --git a/core/cmdbchangeop.class.inc.php b/core/cmdbchangeop.class.inc.php index f6145083d2..dae48b873b 100644 --- a/core/cmdbchangeop.class.inc.php +++ b/core/cmdbchangeop.class.inc.php @@ -242,6 +242,64 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute return $sResult; } } + +/** + * Record the modification of a tag set attribute + * + * @package iTopORM + */ +class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute +{ + public static function Init() + { + $aParams = array + ( + "category" => "core/cmdb", + "key_type" => "", + "name_attcode" => "change", + "state_attcode" => "", + "reconc_keys" => array(), + "db_table" => "priv_changeop_setatt_tagset", + "db_key_field" => "id", + "db_finalclass_field" => "", + ); + MetaModel::Init_Params($aParams); + MetaModel::Init_InheritAttributes(); + MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array()))); + + // Display lists + MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details + MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list + } + + /** + * Describe (as a text string) the modifications corresponding to this change + */ + public function GetDescription() + { + $sResult = ''; + $sTargetObjectClass = $this->Get('objclass'); + $oTargetObjectKey = $this->Get('objkey'); + $sAttCode = $this->Get('attcode'); + $oTargetSearch = new DBObjectSearch($sTargetObjectClass); + $oTargetSearch->AddCondition('id', $oTargetObjectKey, '='); + + $oMonoObjectSet = new DBObjectSet($oTargetSearch); + if (UserRights::IsActionAllowedOnAttribute($sTargetObjectClass, $sAttCode, UR_ACTION_READ, $oMonoObjectSet) == UR_ALLOWED_YES) + { + if (!MetaModel::IsValidAttCode($this->Get('objclass'), $this->Get('attcode'))) return ''; // Protects against renamed attributes... + + $oAttDef = MetaModel::GetAttributeDef($this->Get('objclass'), $this->Get('attcode')); + $sAttName = $oAttDef->GetLabel(); + $sNewValue = $this->Get('newvalue'); + $sOldValue = $this->Get('oldvalue'); + $sResult = $oAttDef->DescribeChangeAsHTML($sOldValue, $sNewValue); + } + return $sResult; + } +} + /** * Record the modification of an URL * diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php index 7089057bfd..b75559c6d7 100644 --- a/core/cmdbobject.class.inc.php +++ b/core/cmdbobject.class.inc.php @@ -409,7 +409,19 @@ abstract class CMDBObject extends DBObject $oMyChangeOp->Set("newvalue", $value); $iId = $oMyChangeOp->DBInsertNoReload(); } - else + elseif ($oAttDef instanceOf AttributeTagSet) + { + // Tag Set + // + $oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttributeTagSet"); + $oMyChangeOp->Set("objclass", get_class($this)); + $oMyChangeOp->Set("objkey", $this->GetKey()); + $oMyChangeOp->Set("attcode", $sAttCode); + $oMyChangeOp->Set("oldvalue", implode(' ', $original->GetValue())); + $oMyChangeOp->Set("newvalue", implode(' ', $value->GetValue())); + $iId = $oMyChangeOp->DBInsertNoReload(); + } + else { // Scalars // diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index ed15c94658..0a93193b44 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -188,11 +188,16 @@ EOF $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); } - else if ($value instanceOf ormDocument) - { - $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); - $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); - } + else if ($value instanceOf ormDocument) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); + } + else if ($value instanceOf ormTagSet) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); + } else { $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 3c5b06e413..711074b43e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1024,7 +1024,7 @@ abstract class MetaModel /** * array of ("classname" => array of attributes) * - * @var array + * @var \AttributeDefinition[] */ private static $m_aAttribDefs = array(); /** diff --git a/core/tagsetfield.class.inc.php b/core/tagsetfield.class.inc.php index 5a583de36a..0947b71902 100644 --- a/core/tagsetfield.class.inc.php +++ b/core/tagsetfield.class.inc.php @@ -83,12 +83,12 @@ abstract class TagSetFieldData extends cmdbAbstractObject "allowed_values" => null, "sql" => "is_default", "default_value" => false, - "is_null_allowed" => false, + "is_null_allowed" => true, "depends_on" => array() ))); - MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'is_default')); + MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'tag_class', 'tag_attcode')); MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default')); MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default')); } diff --git a/css/light-grey.css b/css/light-grey.css index 465e5821b0..dead225043 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -2862,3 +2862,11 @@ table.listResults .originColor { .menu-icon-select > .ui-menu-item { padding: 0.3em 3%; } +.attribute-tagset { + display: inline-block; + padding: 3px; + background-color: grey; + color: white; + margin-right: 3px; + border-radius: 4px; +} diff --git a/css/light-grey.scss b/css/light-grey.scss index 00fb6f8dab..677e46e953 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -3259,4 +3259,13 @@ table.listResults .originColor{ } .menu-icon-select > .ui-menu-item{ padding: .3em 3%; +} + +.attribute-tagset{ + display: inline-block; + padding: 3px; + background-color: grey; + color: white; + margin-right: 3px; + border-radius: 4px; } \ No newline at end of file diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index e64074cb0c..e45518fbdd 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -1395,7 +1395,6 @@ EOF $aParameters['sql'] = $this->GetMandatoryPropString($oField, 'sql'); $aParameters['is_null_allowed'] = $this->GetPropBoolean($oField, 'is_null_allowed', false); $aParameters['depends_on'] = $sDependencies; - $aParameters['default_value'] = ''; } else { diff --git a/setup/xmldataloader.class.inc.php b/setup/xmldataloader.class.inc.php index d5f82a06c3..da22e8712c 100644 --- a/setup/xmldataloader.class.inc.php +++ b/setup/xmldataloader.class.inc.php @@ -273,6 +273,10 @@ class XMLDataLoader $oDoc = new ormDocument($data, $sMimeType, $sFileName); $oTargetObj->Set($sAttCode, $oDoc); } + elseif ($oAttDef instanceof AttributeTagSet) + { + // TODO + } else { $value = (string)$oSubNode; From 3250e0a1e68a15363424157c49d0a6aaae170e9f Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 30 Aug 2018 12:42:56 +0200 Subject: [PATCH 015/113] =?UTF-8?q?N=C2=B0931:=20TagSet=20-=20Support=20fo?= =?UTF-8?q?r=20removed=20tags=20in=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/attributedef.class.inc.php | 56 ++++++++++++++++++++++++++------- core/ormtagset.class.inc.php | 1 + css/light-grey.css | 9 ++++++ css/light-grey.scss | 10 ++++++ 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 1b18299cdc..75103971b3 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -6045,15 +6045,16 @@ class AttributeTagSet extends AttributeDBFieldVoid throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); } - /** - * @param $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string|null - * - * @throws \CoreException - */ + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + * @throws \Exception + */ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) { if ($value instanceof ormTagSet) @@ -6074,8 +6075,41 @@ class AttributeTagSet extends AttributeDBFieldVoid } if (is_string($value)) { - $oValue = $this->MakeRealValue($value, $oHostObject); - return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); + try + { + $oValue = $this->MakeRealValue($value, $oHostObject); + return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); + } + catch (Exception $e) + { + // unknown tags are present display the code instead + } + $aTagCodes = explode(' ', $value); + $aValues = array(); + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + foreach ($aTagCodes as $sTagCode) + { + try + { + $oTagSet->AddTag($sTagCode); + } + catch (Exception $e) + { + $aValues[] = $sTagCode; + } + } + $sHTML = ''; + if (!empty($aValues)) + { + $sHTML .= ''.implode('', $aValues).''; + } + $aValues = $oTagSet->GetLabel(); + if (!empty($aValues)) + { + $sHTML .= ''.implode('', $aValues).''; + } + + return $sHTML; } return parent::GetAsHTML($value, $oHostObject, $bLocalize); diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index ead0212f2d..97a57986fe 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -149,6 +149,7 @@ final class ormTagSet public function GetLabel() { $aLabels = array(); + $aValues = array(); foreach ($this->aPreserved as $oTag) { try diff --git a/css/light-grey.css b/css/light-grey.css index dead225043..c04598d913 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -2870,3 +2870,12 @@ table.listResults .originColor { margin-right: 3px; border-radius: 4px; } +.attribute-tagset-undefined { + color: grey; + font-style: italic; + display: inline-block; + padding: 3px; + background-color: lightgrey; + margin-right: 3px; + border-radius: 4px; +} diff --git a/css/light-grey.scss b/css/light-grey.scss index 677e46e953..d4bae0b83e 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -3268,4 +3268,14 @@ table.listResults .originColor{ color: white; margin-right: 3px; border-radius: 4px; +} + +.attribute-tagset-undefined{ + color: grey; + font-style: italic; + display: inline-block; + padding: 3px; + background-color: lightgrey; + margin-right: 3px; + border-radius: 4px; } \ No newline at end of file From e58bc738d06a9f41197ee1e153e077d897dc3404 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 31 Aug 2018 15:58:55 +0200 Subject: [PATCH 016/113] =?UTF-8?q?N=C2=B0931:=20TagSet=20:=20Parsing=20OQ?= =?UTF-8?q?L?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/dbobjectsearch.class.php | 7 + .../build/PHP/LexerGenerator/Exception.php | 4 +- core/oql/expression.class.inc.php | 3 + core/oql/oql-lexer.php | 86 +- core/oql/oql-lexer.plex | 4 + core/oql/oql-parser.php | 2801 +++++++++-------- core/oql/oql-parser.y | 20 +- core/oql/oqlquery.class.inc.php | 20 + core/oql/version.txt | 2 +- 9 files changed, 1511 insertions(+), 1436 deletions(-) diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index ce6153aa45..c6e9fcb28e 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1314,6 +1314,13 @@ class DBObjectSearch extends DBSearch $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); return new BinaryExpression($oLeft, $sOperator, $oRight); } + elseif ($oExpression instanceof MatchOqlExpression) + { + $oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases); + $oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases); + + return new MatchExpression($oLeft, $oRight); + } elseif ($oExpression instanceof FieldOqlExpression) { $sClassAlias = $oExpression->GetParent(); diff --git a/core/oql/build/PHP/LexerGenerator/Exception.php b/core/oql/build/PHP/LexerGenerator/Exception.php index 69b3066d25..c5cb2fa552 100644 --- a/core/oql/build/PHP/LexerGenerator/Exception.php +++ b/core/oql/build/PHP/LexerGenerator/Exception.php @@ -42,7 +42,7 @@ * @copyright 2006 Gregory Beaver * @license http://www.opensource.org/licenses/bsd-license.php New BSD License */ -require_once 'PEAR/Exception.php'; +//require_once 'PEAR/Exception.php'; /** * @package PHP_LexerGenerator * @author Gregory Beaver @@ -51,5 +51,5 @@ require_once 'PEAR/Exception.php'; * @version @package_version@ * @since File available since Release 0.1.0 */ -class PHP_LexerGenerator_Exception extends PEAR_Exception {} +class PHP_LexerGenerator_Exception extends Exception {} ?> \ No newline at end of file diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 7173a68aed..d578a1a3cd 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -22,6 +22,9 @@ class MissingQueryArgument extends CoreException } +/** + * @method Check($oModelReflection, array $aAliases, $sSourceQuery) + */ abstract class Expression { /** diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php index 23d9c7b6e6..c4649da7a8 100644 --- a/core/oql/oql-lexer.php +++ b/core/oql/oql-lexer.php @@ -140,6 +140,7 @@ class OQLLexerRaw '/\GNOT LIKE/ ', '/\GIN/ ', '/\GNOT IN/ ', + '/\GMATCHES/ ', '/\GINTERVAL/ ', '/\GIF/ ', '/\GELT/ ', @@ -441,204 +442,209 @@ class OQLLexerRaw function yy_r1_33($yy_subpatterns) { - $this->token = OQLParser::INTERVAL; + $this->token = OQLParser::MATCHES; } function yy_r1_34($yy_subpatterns) { - $this->token = OQLParser::F_IF; + $this->token = OQLParser::INTERVAL; } function yy_r1_35($yy_subpatterns) { - $this->token = OQLParser::F_ELT; + $this->token = OQLParser::F_IF; } function yy_r1_36($yy_subpatterns) { - $this->token = OQLParser::F_COALESCE; + $this->token = OQLParser::F_ELT; } function yy_r1_37($yy_subpatterns) { - $this->token = OQLParser::F_ISNULL; + $this->token = OQLParser::F_COALESCE; } function yy_r1_38($yy_subpatterns) { - $this->token = OQLParser::F_CONCAT; + $this->token = OQLParser::F_ISNULL; } function yy_r1_39($yy_subpatterns) { - $this->token = OQLParser::F_SUBSTR; + $this->token = OQLParser::F_CONCAT; } function yy_r1_40($yy_subpatterns) { - $this->token = OQLParser::F_TRIM; + $this->token = OQLParser::F_SUBSTR; } function yy_r1_41($yy_subpatterns) { - $this->token = OQLParser::F_DATE; + $this->token = OQLParser::F_TRIM; } function yy_r1_42($yy_subpatterns) { - $this->token = OQLParser::F_DATE_FORMAT; + $this->token = OQLParser::F_DATE; } function yy_r1_43($yy_subpatterns) { - $this->token = OQLParser::F_CURRENT_DATE; + $this->token = OQLParser::F_DATE_FORMAT; } function yy_r1_44($yy_subpatterns) { - $this->token = OQLParser::F_NOW; + $this->token = OQLParser::F_CURRENT_DATE; } function yy_r1_45($yy_subpatterns) { - $this->token = OQLParser::F_TIME; + $this->token = OQLParser::F_NOW; } function yy_r1_46($yy_subpatterns) { - $this->token = OQLParser::F_TO_DAYS; + $this->token = OQLParser::F_TIME; } function yy_r1_47($yy_subpatterns) { - $this->token = OQLParser::F_FROM_DAYS; + $this->token = OQLParser::F_TO_DAYS; } function yy_r1_48($yy_subpatterns) { - $this->token = OQLParser::F_YEAR; + $this->token = OQLParser::F_FROM_DAYS; } function yy_r1_49($yy_subpatterns) { - $this->token = OQLParser::F_MONTH; + $this->token = OQLParser::F_YEAR; } function yy_r1_50($yy_subpatterns) { - $this->token = OQLParser::F_DAY; + $this->token = OQLParser::F_MONTH; } function yy_r1_51($yy_subpatterns) { - $this->token = OQLParser::F_HOUR; + $this->token = OQLParser::F_DAY; } function yy_r1_52($yy_subpatterns) { - $this->token = OQLParser::F_MINUTE; + $this->token = OQLParser::F_HOUR; } function yy_r1_53($yy_subpatterns) { - $this->token = OQLParser::F_SECOND; + $this->token = OQLParser::F_MINUTE; } function yy_r1_54($yy_subpatterns) { - $this->token = OQLParser::F_DATE_ADD; + $this->token = OQLParser::F_SECOND; } function yy_r1_55($yy_subpatterns) { - $this->token = OQLParser::F_DATE_SUB; + $this->token = OQLParser::F_DATE_ADD; } function yy_r1_56($yy_subpatterns) { - $this->token = OQLParser::F_ROUND; + $this->token = OQLParser::F_DATE_SUB; } function yy_r1_57($yy_subpatterns) { - $this->token = OQLParser::F_FLOOR; + $this->token = OQLParser::F_ROUND; } function yy_r1_58($yy_subpatterns) { - $this->token = OQLParser::F_INET_ATON; + $this->token = OQLParser::F_FLOOR; } function yy_r1_59($yy_subpatterns) { - $this->token = OQLParser::F_INET_NTOA; + $this->token = OQLParser::F_INET_ATON; } function yy_r1_60($yy_subpatterns) { - $this->token = OQLParser::BELOW; + $this->token = OQLParser::F_INET_NTOA; } function yy_r1_61($yy_subpatterns) { - $this->token = OQLParser::BELOW_STRICT; + $this->token = OQLParser::BELOW; } function yy_r1_62($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW; + $this->token = OQLParser::BELOW_STRICT; } function yy_r1_63($yy_subpatterns) { - $this->token = OQLParser::NOT_BELOW_STRICT; + $this->token = OQLParser::NOT_BELOW; } function yy_r1_64($yy_subpatterns) { - $this->token = OQLParser::ABOVE; + $this->token = OQLParser::NOT_BELOW_STRICT; } function yy_r1_65($yy_subpatterns) { - $this->token = OQLParser::ABOVE_STRICT; + $this->token = OQLParser::ABOVE; } function yy_r1_66($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE; + $this->token = OQLParser::ABOVE_STRICT; } function yy_r1_67($yy_subpatterns) { - $this->token = OQLParser::NOT_ABOVE_STRICT; + $this->token = OQLParser::NOT_ABOVE; } function yy_r1_68($yy_subpatterns) { - $this->token = OQLParser::HEXVAL; + $this->token = OQLParser::NOT_ABOVE_STRICT; } function yy_r1_69($yy_subpatterns) { - $this->token = OQLParser::NUMVAL; + $this->token = OQLParser::HEXVAL; } function yy_r1_70($yy_subpatterns) { - $this->token = OQLParser::STRVAL; + $this->token = OQLParser::NUMVAL; } function yy_r1_71($yy_subpatterns) { - $this->token = OQLParser::NAME; + $this->token = OQLParser::STRVAL; } function yy_r1_72($yy_subpatterns) { - $this->token = OQLParser::VARNAME; + $this->token = OQLParser::NAME; } function yy_r1_73($yy_subpatterns) + { + + $this->token = OQLParser::VARNAME; + } + function yy_r1_74($yy_subpatterns) { $this->token = OQLParser::DOT; diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index fc0101b424..78fb13d74b 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -139,6 +139,7 @@ f_round = "ROUND" f_floor = "FLOOR" f_inet_aton = "INET_ATON" f_inet_ntoa = "INET_NTOA" +matches = "MATCHES" below = "BELOW" below_strict = "BELOW STRICT" not_below = "NOT BELOW" @@ -273,6 +274,9 @@ in { not_in { $this->token = OQLParser::NOT_IN; } +matches { + $this->token = OQLParser::MATCHES; +} interval { $this->token = OQLParser::INTERVAL; } diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index e884c96ccb..a07f2ab3af 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -1,116 +1,116 @@ -string = $s->string; - $this->metadata = $s->metadata; - } else { - $this->string = (string) $s; - if ($m instanceof OQLParser_yyToken) { - $this->metadata = $m->metadata; - } elseif (is_array($m)) { - $this->metadata = $m; - } - } - } - - function __toString() - { - return $this->string; - } - - function offsetExists($offset) - { - return isset($this->metadata[$offset]); - } - - function offsetGet($offset) - { - return $this->metadata[$offset]; - } - - function offsetSet($offset, $value) - { - if ($offset === null) { - if (isset($value[0])) { - $x = ($value instanceof OQLParser_yyToken) ? - $value->metadata : $value; - $this->metadata = array_merge($this->metadata, $x); - return; - } - $offset = count($this->metadata); - } - if ($value === null) { - return; - } - if ($value instanceof OQLParser_yyToken) { - if ($value->metadata) { - $this->metadata[$offset] = $value->metadata; - } - } elseif ($value) { - $this->metadata[$offset] = $value; - } - } - - function offsetUnset($offset) - { - unset($this->metadata[$offset]); - } -} - -/** The following structure represents a single element of the - * parser's stack. Information stored includes: - * - * + The state number for the parser at this level of the stack. - * - * + The value of the token stored at this level of the stack. - * (In other words, the "major" token.) - * - * + The semantic value stored at this level of the stack. This is - * the information used by the action routines in the grammar. - * It is sometimes called the "minor" token. - */ -class OQLParser_yyStackEntry -{ - public $stateno; /* The state-number */ - public $major; /* The major token value. This is the code - ** number for the token at this stack level */ - public $minor; /* The user-supplied minor token value. This - ** is the value of the token */ -}; - -// code external to the class is included here - -// declare_class is output here -#line 24 "..\oql-parser.y" -class OQLParserRaw#line 102 "..\oql-parser.php" -{ -/* First off, code is included which follows the "include_class" declaration -** in the input file. */ - -/* Next is all token values, as class constants -*/ -/* -** These constants (all generated automatically by the parser generator) -** specify the various kinds of tokens (terminals) that the parser -** understands. -** -** Each symbol here is a terminal symbol in the grammar. -*/ +string = $s->string; + $this->metadata = $s->metadata; + } else { + $this->string = (string) $s; + if ($m instanceof OQLParser_yyToken) { + $this->metadata = $m->metadata; + } elseif (is_array($m)) { + $this->metadata = $m; + } + } + } + + function __toString() + { + return $this->string; + } + + function offsetExists($offset) + { + return isset($this->metadata[$offset]); + } + + function offsetGet($offset) + { + return $this->metadata[$offset]; + } + + function offsetSet($offset, $value) + { + if ($offset === null) { + if (isset($value[0])) { + $x = ($value instanceof OQLParser_yyToken) ? + $value->metadata : $value; + $this->metadata = array_merge($this->metadata, $x); + return; + } + $offset = count($this->metadata); + } + if ($value === null) { + return; + } + if ($value instanceof OQLParser_yyToken) { + if ($value->metadata) { + $this->metadata[$offset] = $value->metadata; + } + } elseif ($value) { + $this->metadata[$offset] = $value; + } + } + + function offsetUnset($offset) + { + unset($this->metadata[$offset]); + } +} + +/** The following structure represents a single element of the + * parser's stack. Information stored includes: + * + * + The state number for the parser at this level of the stack. + * + * + The value of the token stored at this level of the stack. + * (In other words, the "major" token.) + * + * + The semantic value stored at this level of the stack. This is + * the information used by the action routines in the grammar. + * It is sometimes called the "minor" token. + */ +class OQLParser_yyStackEntry +{ + public $stateno; /* The state-number */ + public $major; /* The major token value. This is the code + ** number for the token at this stack level */ + public $minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; + +// code external to the class is included here + +// declare_class is output here +#line 24 "../oql-parser.y" +class OQLParserRaw#line 102 "../oql-parser.php" +{ +/* First off, code is included which follows the "include_class" declaration +** in the input file. */ + +/* Next is all token values, as class constants +*/ +/* +** These constants (all generated automatically by the parser generator) +** specify the various kinds of tokens (terminals) that the parser +** understands. +** +** Each symbol here is a terminal symbol in the grammar. +*/ const UNION = 1; const SELECT = 2; const AS_ALIAS = 3; @@ -157,243 +157,246 @@ class OQLParserRaw#line 102 "..\oql-parser.php" const LE = 44; const LIKE = 45; const NOT_LIKE = 46; - const BITWISE_LEFT_SHIFT = 47; - const BITWISE_RIGHT_SHIFT = 48; - const BITWISE_AND = 49; - const BITWISE_OR = 50; - const BITWISE_XOR = 51; - const IN = 52; - const NOT_IN = 53; - const F_IF = 54; - const F_ELT = 55; - const F_COALESCE = 56; - const F_ISNULL = 57; - const F_CONCAT = 58; - const F_SUBSTR = 59; - const F_TRIM = 60; - const F_DATE = 61; - const F_DATE_FORMAT = 62; - const F_CURRENT_DATE = 63; - const F_NOW = 64; - const F_TIME = 65; - const F_TO_DAYS = 66; - const F_FROM_DAYS = 67; - const F_DATE_ADD = 68; - const F_DATE_SUB = 69; - const F_ROUND = 70; - const F_FLOOR = 71; - const F_INET_ATON = 72; - const F_INET_NTOA = 73; - const YY_NO_ACTION = 290; - const YY_ACCEPT_ACTION = 289; - const YY_ERROR_ACTION = 288; - -/* Next are that tables used to determine what action to take based on the -** current state and lookahead token. These tables are used to implement -** functions that take a state number and lookahead value and return an -** action integer. -** -** Suppose the action integer is N. Then the action is determined as -** follows -** -** 0 <= N < self::YYNSTATE Shift N. That is, -** push the lookahead -** token onto the stack -** and goto state N. -** -** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE. -** -** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred. -** -** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its -** input. (and concludes parsing) -** -** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused -** slots in the yy_action[] table. -** -** The action table is constructed as a single large static array $yy_action. -** Given state S and lookahead X, the action is computed as -** -** self::$yy_action[self::$yy_shift_ofst[S] + X ] -** -** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value -** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if -** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that -** the action is not in the table and that self::$yy_default[S] should be used instead. -** -** The formula above is for computing the action when the lookahead is -** a terminal symbol. If the lookahead is a non-terminal (as occurs after -** a reduce action) then the static $yy_reduce_ofst array is used in place of -** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of -** self::YY_SHIFT_USE_DFLT. -** -** The following are the tables generated in this section: -** -** self::$yy_action A single table containing all actions. -** self::$yy_lookahead A table containing the lookahead for each entry in -** yy_action. Used to detect hash collisions. -** self::$yy_shift_ofst For each state, the offset into self::$yy_action for -** shifting terminals. -** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for -** shifting non-terminals after a reduce. -** self::$yy_default Default action for each state. -*/ - const YY_SZ_ACTTAB = 549; + const MATCHES = 47; + const BITWISE_LEFT_SHIFT = 48; + const BITWISE_RIGHT_SHIFT = 49; + const BITWISE_AND = 50; + const BITWISE_OR = 51; + const BITWISE_XOR = 52; + const IN = 53; + const NOT_IN = 54; + const F_IF = 55; + const F_ELT = 56; + const F_COALESCE = 57; + const F_ISNULL = 58; + const F_CONCAT = 59; + const F_SUBSTR = 60; + const F_TRIM = 61; + const F_DATE = 62; + const F_DATE_FORMAT = 63; + const F_CURRENT_DATE = 64; + const F_NOW = 65; + const F_TIME = 66; + const F_TO_DAYS = 67; + const F_FROM_DAYS = 68; + const F_DATE_ADD = 69; + const F_DATE_SUB = 70; + const F_ROUND = 71; + const F_FLOOR = 72; + const F_INET_ATON = 73; + const F_INET_NTOA = 74; + const YY_NO_ACTION = 292; + const YY_ACCEPT_ACTION = 291; + const YY_ERROR_ACTION = 290; + +/* Next are that tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N < self::YYNSTATE Shift N. That is, +** push the lookahead +** token onto the stack +** and goto state N. +** +** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE. +** +** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred. +** +** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its +** input. (and concludes parsing) +** +** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large static array $yy_action. +** Given state S and lookahead X, the action is computed as +** +** self::$yy_action[self::$yy_shift_ofst[S] + X ] +** +** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value +** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if +** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that +** the action is not in the table and that self::$yy_default[S] should be used instead. +** +** The formula above is for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the static $yy_reduce_ofst array is used in place of +** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of +** self::YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** self::$yy_action A single table containing all actions. +** self::$yy_lookahead A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** self::$yy_shift_ofst For each state, the offset into self::$yy_action for +** shifting terminals. +** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for +** shifting non-terminals after a reduce. +** self::$yy_default Default action for each state. +*/ + const YY_SZ_ACTTAB = 552; static public $yy_action = array( - /* 0 */ 7, 30, 6, 66, 60, 4, 153, 152, 155, 98, - /* 10 */ 107, 106, 105, 65, 101, 102, 25, 22, 23, 21, - /* 20 */ 19, 27, 26, 28, 24, 162, 151, 43, 177, 177, - /* 30 */ 80, 41, 62, 85, 150, 140, 103, 108, 109, 114, - /* 40 */ 115, 113, 112, 110, 111, 133, 134, 157, 158, 156, - /* 50 */ 154, 159, 160, 165, 166, 164, 20, 121, 125, 3, - /* 60 */ 68, 70, 69, 75, 99, 93, 142, 66, 76, 64, - /* 70 */ 119, 120, 7, 79, 50, 122, 121, 42, 153, 152, - /* 80 */ 155, 139, 107, 106, 105, 65, 101, 102, 149, 119, - /* 90 */ 120, 137, 143, 121, 132, 130, 62, 148, 147, 100, - /* 100 */ 146, 144, 145, 167, 61, 116, 119, 120, 103, 108, - /* 110 */ 109, 114, 115, 113, 112, 110, 111, 133, 134, 157, - /* 120 */ 158, 156, 154, 159, 160, 165, 166, 164, 7, 106, - /* 130 */ 11, 66, 9, 80, 153, 152, 155, 81, 107, 106, - /* 140 */ 105, 65, 101, 102, 163, 161, 72, 8, 10, 88, - /* 150 */ 78, 1, 46, 38, 44, 104, 54, 51, 41, 42, - /* 160 */ 62, 118, 135, 136, 103, 108, 109, 114, 115, 113, - /* 170 */ 112, 110, 111, 133, 134, 157, 158, 156, 154, 159, - /* 180 */ 160, 165, 166, 164, 289, 138, 67, 86, 66, 39, - /* 190 */ 87, 58, 37, 45, 126, 34, 48, 131, 124, 63, - /* 200 */ 13, 40, 66, 17, 230, 15, 12, 36, 97, 2, - /* 210 */ 47, 55, 41, 129, 127, 128, 117, 62, 66, 80, - /* 220 */ 80, 80, 80, 123, 126, 31, 48, 131, 124, 63, - /* 230 */ 59, 62, 237, 17, 91, 15, 20, 36, 73, 80, - /* 240 */ 237, 92, 237, 129, 127, 128, 117, 62, 66, 237, - /* 250 */ 94, 74, 237, 42, 126, 31, 48, 131, 124, 63, - /* 260 */ 237, 237, 66, 17, 66, 15, 66, 36, 83, 95, - /* 270 */ 56, 77, 82, 129, 127, 128, 117, 62, 141, 66, - /* 280 */ 237, 237, 237, 237, 237, 126, 34, 48, 131, 124, - /* 290 */ 63, 62, 237, 62, 17, 62, 15, 52, 36, 5, - /* 300 */ 42, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 310 */ 237, 8, 237, 71, 237, 126, 32, 48, 131, 124, - /* 320 */ 63, 237, 237, 66, 17, 118, 15, 53, 36, 89, - /* 330 */ 42, 57, 237, 237, 129, 127, 128, 117, 62, 66, - /* 340 */ 237, 237, 237, 237, 237, 126, 16, 48, 131, 124, - /* 350 */ 63, 237, 62, 66, 17, 237, 15, 237, 36, 90, - /* 360 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 370 */ 237, 237, 237, 237, 237, 126, 29, 48, 131, 124, - /* 380 */ 63, 237, 62, 237, 17, 237, 15, 237, 36, 237, - /* 390 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 400 */ 237, 237, 237, 237, 237, 126, 33, 48, 131, 124, - /* 410 */ 63, 237, 237, 237, 17, 237, 15, 237, 36, 237, - /* 420 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 430 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 440 */ 63, 237, 237, 237, 17, 237, 15, 237, 35, 237, - /* 450 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, 66, - /* 460 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 470 */ 63, 237, 237, 237, 17, 237, 14, 66, 237, 237, - /* 480 */ 237, 237, 84, 56, 129, 127, 128, 117, 62, 66, - /* 490 */ 237, 237, 237, 237, 237, 126, 237, 48, 131, 124, - /* 500 */ 63, 237, 237, 237, 18, 66, 62, 237, 237, 237, - /* 510 */ 237, 96, 237, 237, 129, 127, 128, 117, 62, 66, - /* 520 */ 237, 237, 237, 237, 237, 126, 237, 49, 131, 124, - /* 530 */ 63, 237, 237, 237, 62, 237, 237, 237, 237, 237, - /* 540 */ 237, 237, 237, 237, 129, 127, 128, 117, 62, + /* 0 */ 28, 25, 26, 23, 20, 27, 19, 21, 22, 24, + /* 10 */ 145, 42, 178, 178, 93, 40, 5, 44, 55, 125, + /* 20 */ 70, 44, 160, 153, 159, 127, 111, 112, 110, 67, + /* 30 */ 108, 107, 142, 39, 71, 146, 144, 127, 13, 129, + /* 40 */ 130, 143, 141, 140, 139, 138, 137, 135, 100, 132, + /* 50 */ 126, 129, 130, 106, 105, 104, 103, 102, 133, 117, + /* 60 */ 134, 152, 154, 155, 156, 157, 158, 162, 163, 164, + /* 70 */ 165, 166, 167, 5, 29, 7, 10, 59, 11, 160, + /* 80 */ 153, 159, 46, 111, 112, 110, 67, 108, 107, 131, + /* 90 */ 99, 147, 161, 151, 5, 53, 119, 66, 44, 120, + /* 100 */ 160, 153, 159, 80, 111, 112, 110, 67, 108, 107, + /* 110 */ 106, 105, 104, 103, 102, 133, 117, 134, 152, 154, + /* 120 */ 155, 156, 157, 158, 162, 163, 164, 165, 166, 167, + /* 130 */ 112, 106, 105, 104, 103, 102, 133, 117, 134, 152, + /* 140 */ 154, 155, 156, 157, 158, 162, 163, 164, 165, 166, + /* 150 */ 167, 136, 150, 36, 38, 62, 291, 98, 61, 168, + /* 160 */ 65, 149, 148, 4, 60, 41, 123, 32, 48, 122, + /* 170 */ 124, 63, 231, 54, 65, 17, 44, 14, 8, 37, + /* 180 */ 85, 75, 80, 80, 80, 116, 101, 115, 114, 64, + /* 190 */ 65, 113, 128, 80, 80, 68, 123, 33, 48, 122, + /* 200 */ 124, 63, 58, 64, 65, 17, 65, 14, 65, 37, + /* 210 */ 82, 77, 56, 95, 86, 116, 101, 115, 114, 64, + /* 220 */ 121, 65, 45, 52, 80, 9, 40, 123, 32, 48, + /* 230 */ 122, 124, 63, 64, 83, 64, 17, 64, 14, 69, + /* 240 */ 37, 8, 6, 47, 43, 94, 116, 101, 115, 114, + /* 250 */ 64, 65, 92, 109, 12, 128, 74, 123, 34, 48, + /* 260 */ 122, 124, 63, 1, 51, 40, 17, 44, 14, 3, + /* 270 */ 37, 50, 2, 57, 28, 239, 116, 101, 115, 114, + /* 280 */ 64, 65, 239, 239, 239, 239, 118, 123, 33, 48, + /* 290 */ 122, 124, 63, 239, 239, 65, 17, 239, 14, 239, + /* 300 */ 37, 88, 239, 239, 72, 239, 116, 101, 115, 114, + /* 310 */ 64, 65, 239, 239, 239, 239, 239, 123, 31, 48, + /* 320 */ 122, 124, 63, 239, 64, 239, 17, 239, 14, 239, + /* 330 */ 37, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 340 */ 64, 65, 239, 239, 239, 239, 239, 123, 16, 48, + /* 350 */ 122, 124, 63, 239, 239, 239, 17, 239, 14, 239, + /* 360 */ 37, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 370 */ 64, 65, 239, 239, 239, 239, 239, 123, 30, 48, + /* 380 */ 122, 124, 63, 239, 239, 65, 17, 239, 14, 239, + /* 390 */ 37, 90, 239, 239, 239, 239, 116, 101, 115, 114, + /* 400 */ 64, 65, 239, 239, 239, 239, 239, 123, 239, 48, + /* 410 */ 122, 124, 63, 239, 64, 239, 17, 239, 14, 239, + /* 420 */ 35, 239, 239, 239, 239, 239, 116, 101, 115, 114, + /* 430 */ 64, 65, 239, 239, 239, 239, 239, 123, 239, 48, + /* 440 */ 122, 124, 63, 239, 239, 239, 17, 239, 15, 76, + /* 450 */ 73, 78, 89, 97, 96, 239, 116, 101, 115, 114, + /* 460 */ 64, 65, 239, 239, 239, 127, 239, 123, 239, 48, + /* 470 */ 122, 124, 63, 239, 239, 239, 18, 65, 65, 129, + /* 480 */ 130, 239, 91, 56, 79, 239, 116, 101, 115, 114, + /* 490 */ 64, 65, 65, 239, 239, 239, 239, 123, 81, 49, + /* 500 */ 122, 124, 63, 239, 239, 239, 64, 64, 239, 65, + /* 510 */ 239, 239, 239, 239, 239, 87, 116, 101, 115, 114, + /* 520 */ 64, 64, 65, 239, 239, 239, 239, 239, 84, 239, + /* 530 */ 239, 239, 239, 239, 239, 239, 239, 239, 64, 239, + /* 540 */ 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, + /* 550 */ 239, 64, ); static public $yy_lookahead = array( - /* 0 */ 18, 79, 20, 79, 82, 6, 24, 25, 26, 85, - /* 10 */ 28, 29, 30, 31, 32, 33, 9, 10, 11, 12, - /* 20 */ 13, 14, 15, 16, 17, 38, 39, 3, 4, 5, - /* 30 */ 108, 7, 108, 81, 47, 48, 54, 55, 56, 57, - /* 40 */ 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, - /* 50 */ 68, 69, 70, 71, 72, 73, 2, 37, 93, 18, - /* 60 */ 21, 22, 23, 24, 25, 26, 9, 79, 76, 77, - /* 70 */ 50, 51, 18, 85, 80, 19, 37, 83, 24, 25, - /* 80 */ 26, 81, 28, 29, 30, 31, 32, 33, 31, 50, - /* 90 */ 51, 34, 35, 37, 52, 53, 108, 40, 41, 42, - /* 100 */ 43, 44, 45, 46, 79, 108, 50, 51, 54, 55, - /* 110 */ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - /* 120 */ 66, 67, 68, 69, 70, 71, 72, 73, 18, 29, - /* 130 */ 95, 79, 99, 108, 24, 25, 26, 85, 28, 29, - /* 140 */ 30, 31, 32, 33, 109, 110, 113, 100, 97, 81, - /* 150 */ 103, 18, 4, 5, 3, 30, 80, 27, 7, 83, - /* 160 */ 108, 114, 111, 112, 54, 55, 56, 57, 58, 59, - /* 170 */ 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, - /* 180 */ 70, 71, 72, 73, 75, 76, 77, 78, 79, 1, - /* 190 */ 79, 79, 79, 79, 85, 86, 87, 88, 89, 90, - /* 200 */ 8, 3, 79, 94, 27, 96, 8, 98, 85, 5, - /* 210 */ 79, 92, 7, 104, 105, 106, 107, 108, 79, 108, - /* 220 */ 108, 108, 108, 19, 85, 86, 87, 88, 89, 90, - /* 230 */ 91, 108, 115, 94, 81, 96, 2, 98, 36, 108, - /* 240 */ 115, 102, 115, 104, 105, 106, 107, 108, 79, 115, - /* 250 */ 80, 49, 115, 83, 85, 86, 87, 88, 89, 90, - /* 260 */ 115, 115, 79, 94, 79, 96, 79, 98, 85, 84, - /* 270 */ 85, 102, 85, 104, 105, 106, 107, 108, 78, 79, - /* 280 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 290 */ 90, 108, 115, 108, 94, 108, 96, 80, 98, 5, - /* 300 */ 83, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 310 */ 115, 100, 115, 19, 115, 85, 86, 87, 88, 89, - /* 320 */ 90, 115, 115, 79, 94, 114, 96, 80, 98, 85, - /* 330 */ 83, 101, 115, 115, 104, 105, 106, 107, 108, 79, - /* 340 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 350 */ 90, 115, 108, 79, 94, 115, 96, 115, 98, 85, - /* 360 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 370 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 380 */ 90, 115, 108, 115, 94, 115, 96, 115, 98, 115, - /* 390 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 400 */ 115, 115, 115, 115, 115, 85, 86, 87, 88, 89, - /* 410 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, - /* 420 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 430 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 440 */ 90, 115, 115, 115, 94, 115, 96, 115, 98, 115, - /* 450 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, 79, - /* 460 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 470 */ 90, 115, 115, 115, 94, 115, 96, 79, 115, 115, - /* 480 */ 115, 115, 84, 85, 104, 105, 106, 107, 108, 79, - /* 490 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 500 */ 90, 115, 115, 115, 94, 79, 108, 115, 115, 115, - /* 510 */ 115, 85, 115, 115, 104, 105, 106, 107, 108, 79, - /* 520 */ 115, 115, 115, 115, 115, 85, 115, 87, 88, 89, - /* 530 */ 90, 115, 115, 115, 108, 115, 115, 115, 115, 115, - /* 540 */ 115, 115, 115, 115, 104, 105, 106, 107, 108, + /* 0 */ 2, 9, 10, 11, 12, 13, 14, 15, 16, 17, + /* 10 */ 9, 3, 4, 5, 81, 7, 18, 84, 81, 19, + /* 20 */ 36, 84, 24, 25, 26, 37, 28, 29, 30, 31, + /* 30 */ 32, 33, 31, 3, 50, 34, 35, 37, 8, 51, + /* 40 */ 52, 40, 41, 42, 43, 44, 45, 46, 47, 53, + /* 50 */ 54, 51, 52, 55, 56, 57, 58, 59, 60, 61, + /* 60 */ 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, + /* 70 */ 72, 73, 74, 18, 80, 20, 98, 83, 96, 24, + /* 80 */ 25, 26, 1, 28, 29, 30, 31, 32, 33, 94, + /* 90 */ 112, 113, 110, 111, 18, 81, 77, 78, 84, 82, + /* 100 */ 24, 25, 26, 109, 28, 29, 30, 31, 32, 33, + /* 110 */ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + /* 120 */ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + /* 130 */ 29, 55, 56, 57, 58, 59, 60, 61, 62, 63, + /* 140 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + /* 150 */ 74, 38, 39, 80, 80, 80, 76, 77, 78, 79, + /* 160 */ 80, 48, 49, 6, 80, 80, 86, 87, 88, 89, + /* 170 */ 90, 91, 27, 81, 80, 95, 84, 97, 101, 99, + /* 180 */ 86, 104, 109, 109, 109, 105, 106, 107, 108, 109, + /* 190 */ 80, 109, 115, 109, 109, 80, 86, 87, 88, 89, + /* 200 */ 90, 91, 92, 109, 80, 95, 80, 97, 80, 99, + /* 210 */ 86, 85, 86, 103, 86, 105, 106, 107, 108, 109, + /* 220 */ 79, 80, 3, 27, 109, 100, 7, 86, 87, 88, + /* 230 */ 89, 90, 91, 109, 82, 109, 95, 109, 97, 114, + /* 240 */ 99, 101, 5, 4, 5, 82, 105, 106, 107, 108, + /* 250 */ 109, 80, 82, 30, 8, 115, 19, 86, 87, 88, + /* 260 */ 89, 90, 91, 18, 81, 7, 95, 84, 97, 18, + /* 270 */ 99, 93, 5, 102, 2, 116, 105, 106, 107, 108, + /* 280 */ 109, 80, 116, 116, 116, 116, 19, 86, 87, 88, + /* 290 */ 89, 90, 91, 116, 116, 80, 95, 116, 97, 116, + /* 300 */ 99, 86, 116, 116, 103, 116, 105, 106, 107, 108, + /* 310 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 320 */ 89, 90, 91, 116, 109, 116, 95, 116, 97, 116, + /* 330 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 340 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 350 */ 89, 90, 91, 116, 116, 116, 95, 116, 97, 116, + /* 360 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 370 */ 109, 80, 116, 116, 116, 116, 116, 86, 87, 88, + /* 380 */ 89, 90, 91, 116, 116, 80, 95, 116, 97, 116, + /* 390 */ 99, 86, 116, 116, 116, 116, 105, 106, 107, 108, + /* 400 */ 109, 80, 116, 116, 116, 116, 116, 86, 116, 88, + /* 410 */ 89, 90, 91, 116, 109, 116, 95, 116, 97, 116, + /* 420 */ 99, 116, 116, 116, 116, 116, 105, 106, 107, 108, + /* 430 */ 109, 80, 116, 116, 116, 116, 116, 86, 116, 88, + /* 440 */ 89, 90, 91, 116, 116, 116, 95, 116, 97, 21, + /* 450 */ 22, 23, 24, 25, 26, 116, 105, 106, 107, 108, + /* 460 */ 109, 80, 116, 116, 116, 37, 116, 86, 116, 88, + /* 470 */ 89, 90, 91, 116, 116, 116, 95, 80, 80, 51, + /* 480 */ 52, 116, 85, 86, 86, 116, 105, 106, 107, 108, + /* 490 */ 109, 80, 80, 116, 116, 116, 116, 86, 86, 88, + /* 500 */ 89, 90, 91, 116, 116, 116, 109, 109, 116, 80, + /* 510 */ 116, 116, 116, 116, 116, 86, 105, 106, 107, 108, + /* 520 */ 109, 109, 80, 116, 116, 116, 116, 116, 86, 116, + /* 530 */ 116, 116, 116, 116, 116, 116, 116, 116, 109, 116, + /* 540 */ 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, + /* 550 */ 116, 109, ); - const YY_SHIFT_USE_DFLT = -19; + const YY_SHIFT_USE_DFLT = -17; const YY_SHIFT_MAX = 67; static public $yy_shift_ofst = array( - /* 0 */ 54, -18, -18, 110, 110, 110, 110, 110, 110, 110, - /* 10 */ 110, 110, 100, 100, 57, 57, 39, -13, -13, 100, - /* 20 */ 100, 100, 100, 100, 100, 100, 100, 100, 100, 56, - /* 30 */ 24, 20, 20, 20, 20, 202, 202, 151, 100, 234, - /* 40 */ 100, 100, 205, 100, 100, 205, 100, 205, 42, 42, - /* 50 */ -1, 100, -1, -1, -1, 41, 7, 294, 198, 204, - /* 60 */ 148, 192, 177, 133, 188, 125, 130, 188, + /* 0 */ -2, 55, 55, 76, 76, 76, 76, 76, 76, 76, + /* 10 */ 76, 76, 101, 101, 1, 1, 428, 113, 113, 101, + /* 20 */ 101, 101, 101, 101, 101, 101, 101, 101, 101, 8, + /* 30 */ 0, -12, -12, -12, -12, -16, 219, -16, 258, 101, + /* 40 */ 101, 258, 101, 101, 258, 101, 272, 101, -4, -4, + /* 50 */ 251, 157, 101, 157, 157, 157, -8, 237, 267, 239, + /* 60 */ 30, 81, 246, 245, 145, 196, 81, 223, ); - const YY_REDUCE_USE_DFLT = -79; + const YY_REDUCE_USE_DFLT = -68; const YY_REDUCE_MAX = 55; static public $yy_reduce_ofst = array( - /* 0 */ 109, 139, 169, 230, 200, 320, 260, 290, 350, 380, - /* 10 */ 410, 440, 398, 185, 51, 51, 47, 35, 35, 274, - /* 20 */ -78, 426, -12, 123, 52, -76, 183, 244, 187, 211, - /* 30 */ 247, 211, 211, 211, 211, 33, 33, 76, 111, -8, - /* 40 */ 25, 112, 170, 131, 114, 217, 113, -6, 119, 119, - /* 50 */ 153, -3, 68, 0, -48, -35, + /* 0 */ 80, 110, 201, 171, 141, 291, 231, 261, 321, 351, + /* 10 */ 381, 411, 397, 126, -22, -22, 77, -18, -18, 94, + /* 20 */ 429, 442, 124, 398, 412, 305, 215, 128, -6, 183, + /* 30 */ 140, 140, 140, 140, 140, 125, 14, 125, -63, 75, + /* 40 */ 84, 92, 85, 115, -67, 74, 19, 73, 178, 178, + /* 50 */ -5, 17, 82, 152, 163, 170, ); static public $yyExpectedTokens = array( - /* 0 */ array(2, 18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 1 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 2 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 3 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 4 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 5 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 6 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 7 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 8 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 9 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 10 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), - /* 11 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, ), + /* 0 */ array(2, 18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 1 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 2 */ array(18, 20, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 3 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 4 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 5 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 6 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 7 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 8 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 9 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 10 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), + /* 11 */ array(18, 24, 25, 26, 28, 29, 30, 31, 32, 33, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, ), /* 12 */ array(29, ), /* 13 */ array(29, ), - /* 14 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), - /* 15 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, ), - /* 16 */ array(21, 22, 23, 24, 25, 26, 37, 50, 51, ), - /* 17 */ array(38, 39, 47, 48, ), - /* 18 */ array(38, 39, 47, 48, ), + /* 14 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, ), + /* 15 */ array(9, 31, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, ), + /* 16 */ array(21, 22, 23, 24, 25, 26, 37, 51, 52, ), + /* 17 */ array(38, 39, 48, 49, ), + /* 18 */ array(38, 39, 48, 49, ), /* 19 */ array(29, ), /* 20 */ array(29, ), /* 21 */ array(29, ), @@ -404,45 +407,45 @@ static public $yy_action = array( /* 26 */ array(29, ), /* 27 */ array(29, ), /* 28 */ array(29, ), - /* 29 */ array(19, 37, 50, 51, ), - /* 30 */ array(3, 4, 5, 7, ), - /* 31 */ array(37, 50, 51, ), - /* 32 */ array(37, 50, 51, ), - /* 33 */ array(37, 50, 51, ), - /* 34 */ array(37, 50, 51, ), - /* 35 */ array(36, 49, ), - /* 36 */ array(36, 49, ), - /* 37 */ array(3, 7, ), - /* 38 */ array(29, ), - /* 39 */ array(2, ), + /* 29 */ array(3, 4, 5, 7, ), + /* 30 */ array(19, 37, 51, 52, ), + /* 31 */ array(37, 51, 52, ), + /* 32 */ array(37, 51, 52, ), + /* 33 */ array(37, 51, 52, ), + /* 34 */ array(37, 51, 52, ), + /* 35 */ array(36, 50, ), + /* 36 */ array(3, 7, ), + /* 37 */ array(36, 50, ), + /* 38 */ array(7, ), + /* 39 */ array(29, ), /* 40 */ array(29, ), - /* 41 */ array(29, ), - /* 42 */ array(7, ), + /* 41 */ array(7, ), + /* 42 */ array(29, ), /* 43 */ array(29, ), - /* 44 */ array(29, ), - /* 45 */ array(7, ), - /* 46 */ array(29, ), - /* 47 */ array(7, ), - /* 48 */ array(52, 53, ), - /* 49 */ array(52, 53, ), - /* 50 */ array(6, ), - /* 51 */ array(29, ), - /* 52 */ array(6, ), + /* 44 */ array(7, ), + /* 45 */ array(29, ), + /* 46 */ array(2, ), + /* 47 */ array(29, ), + /* 48 */ array(53, 54, ), + /* 49 */ array(53, 54, ), + /* 50 */ array(18, ), + /* 51 */ array(6, ), + /* 52 */ array(29, ), /* 53 */ array(6, ), /* 54 */ array(6, ), - /* 55 */ array(18, ), + /* 55 */ array(6, ), /* 56 */ array(9, 10, 11, 12, 13, 14, 15, 16, 17, ), /* 57 */ array(5, 19, ), - /* 58 */ array(3, 8, ), - /* 59 */ array(5, 19, ), - /* 60 */ array(4, 5, ), - /* 61 */ array(8, ), - /* 62 */ array(27, ), + /* 58 */ array(5, 19, ), + /* 59 */ array(4, 5, ), + /* 60 */ array(3, 8, ), + /* 61 */ array(1, ), + /* 62 */ array(8, ), /* 63 */ array(18, ), - /* 64 */ array(1, ), - /* 65 */ array(30, ), - /* 66 */ array(27, ), - /* 67 */ array(1, ), + /* 64 */ array(27, ), + /* 65 */ array(27, ), + /* 66 */ array(1, ), + /* 67 */ array(30, ), /* 68 */ array(), /* 69 */ array(), /* 70 */ array(), @@ -543,127 +546,128 @@ static public $yy_action = array( /* 165 */ array(), /* 166 */ array(), /* 167 */ array(), + /* 168 */ array(), ); static public $yy_default = array( - /* 0 */ 288, 213, 288, 288, 288, 288, 288, 288, 288, 288, - /* 10 */ 288, 288, 288, 288, 207, 206, 288, 204, 205, 288, - /* 20 */ 288, 288, 288, 288, 288, 288, 288, 288, 288, 288, - /* 30 */ 183, 216, 211, 212, 195, 209, 208, 183, 288, 288, - /* 40 */ 288, 288, 182, 288, 288, 183, 288, 183, 202, 203, - /* 50 */ 180, 288, 180, 180, 180, 288, 288, 288, 288, 288, - /* 60 */ 288, 288, 228, 288, 171, 288, 288, 169, 218, 220, - /* 70 */ 219, 210, 245, 244, 260, 221, 172, 215, 217, 187, - /* 80 */ 230, 194, 193, 192, 185, 175, 170, 178, 176, 191, - /* 90 */ 190, 174, 214, 223, 181, 184, 189, 188, 186, 222, - /* 100 */ 253, 235, 236, 265, 234, 233, 232, 231, 266, 267, - /* 110 */ 272, 273, 271, 270, 268, 269, 229, 227, 247, 261, - /* 120 */ 262, 246, 200, 199, 198, 201, 197, 225, 226, 224, - /* 130 */ 264, 196, 263, 274, 275, 239, 240, 241, 168, 173, - /* 140 */ 259, 179, 242, 243, 255, 256, 254, 252, 250, 251, - /* 150 */ 258, 249, 280, 281, 282, 279, 278, 276, 277, 283, - /* 160 */ 284, 238, 248, 237, 287, 285, 286, 257, + /* 0 */ 290, 214, 290, 290, 290, 290, 290, 290, 290, 290, + /* 10 */ 290, 290, 290, 290, 207, 208, 290, 205, 206, 290, + /* 20 */ 290, 290, 290, 290, 290, 290, 290, 290, 290, 184, + /* 30 */ 290, 213, 196, 217, 212, 210, 184, 209, 184, 290, + /* 40 */ 290, 184, 290, 290, 183, 290, 290, 290, 203, 204, + /* 50 */ 290, 181, 290, 181, 181, 181, 290, 290, 290, 290, + /* 60 */ 290, 170, 290, 290, 229, 290, 172, 290, 179, 246, + /* 70 */ 245, 262, 216, 220, 211, 218, 219, 186, 221, 189, + /* 80 */ 231, 195, 194, 176, 193, 192, 191, 190, 188, 222, + /* 90 */ 187, 185, 177, 182, 175, 215, 224, 223, 169, 240, + /* 100 */ 259, 226, 271, 270, 269, 268, 267, 237, 236, 235, + /* 110 */ 234, 232, 233, 230, 228, 227, 225, 273, 200, 173, + /* 120 */ 174, 180, 197, 198, 199, 201, 266, 247, 248, 263, + /* 130 */ 264, 202, 265, 272, 274, 258, 249, 257, 256, 255, + /* 140 */ 254, 253, 252, 251, 244, 243, 242, 241, 261, 260, + /* 150 */ 250, 239, 275, 282, 276, 277, 278, 279, 280, 281, + /* 160 */ 283, 238, 284, 285, 286, 287, 288, 289, 171, ); -/* The next thing included is series of defines which control -** various aspects of the generated parser. -** self::YYNOCODE is a number which corresponds -** to no legal terminal or nonterminal number. This -** number is used to fill in empty slots of the hash -** table. -** self::YYFALLBACK If defined, this indicates that one or more tokens -** have fall-back values which should be used if the -** original value of the token will not parse. -** self::YYSTACKDEPTH is the maximum depth of the parser's stack. -** self::YYNSTATE the combined number of states. -** self::YYNRULE the number of rules in the grammar -** self::YYERRORSYMBOL is the code number of the error symbol. If not -** defined, then do no error processing. -*/ - const YYNOCODE = 116; +/* The next thing included is series of defines which control +** various aspects of the generated parser. +** self::YYNOCODE is a number which corresponds +** to no legal terminal or nonterminal number. This +** number is used to fill in empty slots of the hash +** table. +** self::YYFALLBACK If defined, this indicates that one or more tokens +** have fall-back values which should be used if the +** original value of the token will not parse. +** self::YYSTACKDEPTH is the maximum depth of the parser's stack. +** self::YYNSTATE the combined number of states. +** self::YYNRULE the number of rules in the grammar +** self::YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +*/ + const YYNOCODE = 117; const YYSTACKDEPTH = 100; - const YYNSTATE = 168; - const YYNRULE = 120; - const YYERRORSYMBOL = 74; + const YYNSTATE = 169; + const YYNRULE = 121; + const YYERRORSYMBOL = 75; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; - /** The next table maps tokens into fallback tokens. If a construct - * like the following: - * - * %fallback ID X Y Z. - * - * appears in the grammer, then ID becomes a fallback token for X, Y, - * and Z. Whenever one of the tokens X, Y, or Z is input to the parser - * but it does not parse, the type of the token is changed to ID and - * the parse is retried before an error is thrown. - */ - static public $yyFallback = array( - ); - /** - * Turn parser tracing on by giving a stream to which to write the trace - * and a prompt to preface each trace message. Tracing is turned off - * by making either argument NULL - * - * Inputs: - * - * - A stream resource to which trace output should be written. - * If NULL, then tracing is turned off. - * - A prefix string written at the beginning of every - * line of trace output. If NULL, then tracing is - * turned off. - * - * Outputs: - * - * - None. - * @param resource - * @param string - */ - static function Trace($TraceFILE, $zTracePrompt) - { - if (!$TraceFILE) { - $zTracePrompt = 0; - } elseif (!$zTracePrompt) { - $TraceFILE = 0; - } - self::$yyTraceFILE = $TraceFILE; - self::$yyTracePrompt = $zTracePrompt; - } - - /** - * Output debug information to output (php://output stream) - */ - static function PrintTrace() - { - self::$yyTraceFILE = fopen('php://output', 'w'); - self::$yyTracePrompt = ''; - } - - /** - * @var resource|0 - */ - static public $yyTraceFILE; - /** - * String to prepend to debug output - * @var string|0 - */ - static public $yyTracePrompt; - /** - * @var int - */ - public $yyidx = -1; /* Index of top element in stack */ - /** - * @var int - */ - public $yyerrcnt; /* Shifts left before out of the error */ - /** - * @var array - */ - public $yystack = array(); /* The parser's stack */ - - /** - * For tracing shifts, the names of all terminals and nonterminals - * are required. The following table supplies these names - * @var array - */ - static public $yyTokenName = array( + /** The next table maps tokens into fallback tokens. If a construct + * like the following: + * + * %fallback ID X Y Z. + * + * appears in the grammer, then ID becomes a fallback token for X, Y, + * and Z. Whenever one of the tokens X, Y, or Z is input to the parser + * but it does not parse, the type of the token is changed to ID and + * the parse is retried before an error is thrown. + */ + static public $yyFallback = array( + ); + /** + * Turn parser tracing on by giving a stream to which to write the trace + * and a prompt to preface each trace message. Tracing is turned off + * by making either argument NULL + * + * Inputs: + * + * - A stream resource to which trace output should be written. + * If NULL, then tracing is turned off. + * - A prefix string written at the beginning of every + * line of trace output. If NULL, then tracing is + * turned off. + * + * Outputs: + * + * - None. + * @param resource + * @param string + */ + static function Trace($TraceFILE, $zTracePrompt) + { + if (!$TraceFILE) { + $zTracePrompt = 0; + } elseif (!$zTracePrompt) { + $TraceFILE = 0; + } + self::$yyTraceFILE = $TraceFILE; + self::$yyTracePrompt = $zTracePrompt; + } + + /** + * Output debug information to output (php://output stream) + */ + static function PrintTrace() + { + self::$yyTraceFILE = fopen('php://output', 'w'); + self::$yyTracePrompt = ''; + } + + /** + * @var resource|0 + */ + static public $yyTraceFILE; + /** + * String to prepend to debug output + * @var string|0 + */ + static public $yyTracePrompt; + /** + * @var int + */ + public $yyidx = -1; /* Index of top element in stack */ + /** + * @var int + */ + public $yyerrcnt; /* Shifts left before out of the error */ + /** + * @var array + */ + public $yystack = array(); /* The parser's stack */ + + /** + * For tracing shifts, the names of all terminals and nonterminals + * are required. The following table supplies these names + * @var array + */ + static public $yyTokenName = array( '$', 'UNION', 'SELECT', 'AS_ALIAS', 'FROM', 'COMA', 'WHERE', 'JOIN', 'ON', 'EQ', 'BELOW', 'BELOW_STRICT', @@ -675,31 +679,31 @@ static public $yy_action = array( 'HEXVAL', 'STRVAL', 'REGEXP', 'NOT_EQ', 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', 'GT', 'LT', 'GE', - 'LE', 'LIKE', 'NOT_LIKE', 'BITWISE_LEFT_SHIFT', - 'BITWISE_RIGHT_SHIFT', 'BITWISE_AND', 'BITWISE_OR', 'BITWISE_XOR', - 'IN', 'NOT_IN', 'F_IF', 'F_ELT', - 'F_COALESCE', 'F_ISNULL', 'F_CONCAT', 'F_SUBSTR', - 'F_TRIM', 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', - 'F_NOW', 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', - 'F_DATE_ADD', 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', - 'F_INET_ATON', 'F_INET_NTOA', 'error', 'result', - 'union', 'query', 'condition', 'class_name', - 'join_statement', 'where_statement', 'class_list', 'join_item', - 'join_condition', 'field_id', 'expression_prio4', 'expression_basic', - 'scalar', 'var_name', 'func_name', 'arg_list', - 'list_operator', 'list', 'expression_prio1', 'operator1', - 'expression_prio2', 'operator2', 'expression_prio3', 'operator3', - 'operator4', 'list_items', 'argument', 'interval_unit', - 'num_scalar', 'str_scalar', 'num_value', 'str_value', - 'name', 'num_operator1', 'bitwise_operator1', 'num_operator2', - 'str_operator', 'bitwise_operator3', 'bitwise_operator4', - ); - - /** - * For tracing reduce actions, the names of all rules are required. - * @var array - */ - static public $yyRuleName = array( + 'LE', 'LIKE', 'NOT_LIKE', 'MATCHES', + 'BITWISE_LEFT_SHIFT', 'BITWISE_RIGHT_SHIFT', 'BITWISE_AND', 'BITWISE_OR', + 'BITWISE_XOR', 'IN', 'NOT_IN', 'F_IF', + 'F_ELT', 'F_COALESCE', 'F_ISNULL', 'F_CONCAT', + 'F_SUBSTR', 'F_TRIM', 'F_DATE', 'F_DATE_FORMAT', + 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', 'F_TO_DAYS', + 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', 'F_ROUND', + 'F_FLOOR', 'F_INET_ATON', 'F_INET_NTOA', 'error', + 'result', 'union', 'query', 'condition', + 'class_name', 'join_statement', 'where_statement', 'class_list', + 'join_item', 'join_condition', 'field_id', 'expression_prio4', + 'expression_basic', 'scalar', 'var_name', 'func_name', + 'arg_list', 'list_operator', 'list', 'expression_prio1', + 'operator1', 'expression_prio2', 'operator2', 'expression_prio3', + 'operator3', 'operator4', 'list_items', 'argument', + 'interval_unit', 'num_scalar', 'str_scalar', 'num_value', + 'str_value', 'name', 'num_operator1', 'bitwise_operator1', + 'num_operator2', 'str_operator', 'bitwise_operator3', 'bitwise_operator4', + ); + + /** + * For tracing reduce actions, the names of all rules are required. + * @var array + */ + static public $yyRuleName = array( /* 0 */ "result ::= union", /* 1 */ "result ::= query", /* 2 */ "result ::= condition", @@ -790,529 +794,531 @@ static public $yy_action = array( /* 87 */ "num_operator2 ::= LE", /* 88 */ "str_operator ::= LIKE", /* 89 */ "str_operator ::= NOT_LIKE", - /* 90 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", - /* 91 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", - /* 92 */ "bitwise_operator3 ::= BITWISE_AND", - /* 93 */ "bitwise_operator4 ::= BITWISE_OR", - /* 94 */ "bitwise_operator4 ::= BITWISE_XOR", - /* 95 */ "list_operator ::= IN", - /* 96 */ "list_operator ::= NOT_IN", - /* 97 */ "func_name ::= F_IF", - /* 98 */ "func_name ::= F_ELT", - /* 99 */ "func_name ::= F_COALESCE", - /* 100 */ "func_name ::= F_ISNULL", - /* 101 */ "func_name ::= F_CONCAT", - /* 102 */ "func_name ::= F_SUBSTR", - /* 103 */ "func_name ::= F_TRIM", - /* 104 */ "func_name ::= F_DATE", - /* 105 */ "func_name ::= F_DATE_FORMAT", - /* 106 */ "func_name ::= F_CURRENT_DATE", - /* 107 */ "func_name ::= F_NOW", - /* 108 */ "func_name ::= F_TIME", - /* 109 */ "func_name ::= F_TO_DAYS", - /* 110 */ "func_name ::= F_FROM_DAYS", - /* 111 */ "func_name ::= F_YEAR", - /* 112 */ "func_name ::= F_MONTH", - /* 113 */ "func_name ::= F_DAY", - /* 114 */ "func_name ::= F_DATE_ADD", - /* 115 */ "func_name ::= F_DATE_SUB", - /* 116 */ "func_name ::= F_ROUND", - /* 117 */ "func_name ::= F_FLOOR", - /* 118 */ "func_name ::= F_INET_ATON", - /* 119 */ "func_name ::= F_INET_NTOA", - ); - - /** - * This function returns the symbolic name associated with a token - * value. - * @param int - * @return string - */ - function tokenName($tokenType) - { - if ($tokenType === 0) { - return 'End of Input'; - } - if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) { - return self::$yyTokenName[$tokenType]; - } else { - return "Unknown"; - } - } - - /** - * The following function deletes the value associated with a - * symbol. The symbol can be either a terminal or nonterminal. - * @param int the symbol code - * @param mixed the symbol's value - */ - static function yy_destructor($yymajor, $yypminor) - { - switch ($yymajor) { - /* Here is inserted the actions which take place when a - ** terminal or non-terminal is destroyed. This can happen - ** when the symbol is popped from the stack during a - ** reduce or during error processing or when a parser is - ** being destroyed before it is finished parsing. - ** - ** Note: during a reduce, the only symbols destroyed are those - ** which appear on the RHS of the rule, but which are not used - ** inside the C code. - */ - default: break; /* If no destructor action specified: do nothing */ - } - } - - /** - * Pop the parser's stack once. - * - * If there is a destructor routine associated with the token which - * is popped from the stack, then call it. - * - * Return the major token number for the symbol popped. - * @param OQLParser_yyParser - * @return int - */ - function yy_pop_parser_stack() - { - if (!count($this->yystack)) { - return; - } - $yytos = array_pop($this->yystack); - if (self::$yyTraceFILE && $this->yyidx >= 0) { - fwrite(self::$yyTraceFILE, - self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] . - "\n"); - } - $yymajor = $yytos->major; - self::yy_destructor($yymajor, $yytos->minor); - $this->yyidx--; - return $yymajor; - } - - /** - * Deallocate and destroy a parser. Destructors are all called for - * all stack elements before shutting the parser down. - */ - function __destruct() - { - while ($this->yyidx >= 0) { - $this->yy_pop_parser_stack(); - } - if (is_resource(self::$yyTraceFILE)) { - fclose(self::$yyTraceFILE); - } - } - - /** - * Based on the current state and parser stack, get a list of all - * possible lookahead tokens - * @param int - * @return array - */ - function yy_get_expected_tokens($token) - { - $state = $this->yystack[$this->yyidx]->stateno; - $expected = self::$yyExpectedTokens[$state]; - if (in_array($token, self::$yyExpectedTokens[$state], true)) { - return $expected; - } - $stack = $this->yystack; - $yyidx = $this->yyidx; - do { - $yyact = $this->yy_find_shift_action($token); - if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { - // reduce action - $done = 0; - do { - if ($done++ == 100) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - // too much recursion prevents proper detection - // so give up - return array_unique($expected); - } - $yyruleno = $yyact - self::YYNSTATE; - $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; - $nextstate = $this->yy_find_reduce_action( - $this->yystack[$this->yyidx]->stateno, - self::$yyRuleInfo[$yyruleno]['lhs']); - if (isset(self::$yyExpectedTokens[$nextstate])) { - $expected += self::$yyExpectedTokens[$nextstate]; - if (in_array($token, - self::$yyExpectedTokens[$nextstate], true)) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - return array_unique($expected); - } - } - if ($nextstate < self::YYNSTATE) { - // we need to shift a non-terminal - $this->yyidx++; - $x = new OQLParser_yyStackEntry; - $x->stateno = $nextstate; - $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; - $this->yystack[$this->yyidx] = $x; - continue 2; - } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - // the last token was just ignored, we can't accept - // by ignoring input, this is in essence ignoring a - // syntax error! - return array_unique($expected); - } elseif ($nextstate === self::YY_NO_ACTION) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - // input accepted, but not shifted (I guess) - return $expected; - } else { - $yyact = $nextstate; - } - } while (true); - } - break; - } while (true); - return array_unique($expected); - } - - /** - * Based on the parser state and current parser stack, determine whether - * the lookahead token is possible. - * - * The parser will convert the token value to an error token if not. This - * catches some unusual edge cases where the parser would fail. - * @param int - * @return bool - */ - function yy_is_expected_token($token) - { - if ($token === 0) { - return true; // 0 is not part of this - } - $state = $this->yystack[$this->yyidx]->stateno; - if (in_array($token, self::$yyExpectedTokens[$state], true)) { - return true; - } - $stack = $this->yystack; - $yyidx = $this->yyidx; - do { - $yyact = $this->yy_find_shift_action($token); - if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { - // reduce action - $done = 0; - do { - if ($done++ == 100) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - // too much recursion prevents proper detection - // so give up - return true; - } - $yyruleno = $yyact - self::YYNSTATE; - $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; - $nextstate = $this->yy_find_reduce_action( - $this->yystack[$this->yyidx]->stateno, - self::$yyRuleInfo[$yyruleno]['lhs']); - if (isset(self::$yyExpectedTokens[$nextstate]) && - in_array($token, self::$yyExpectedTokens[$nextstate], true)) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - return true; - } - if ($nextstate < self::YYNSTATE) { - // we need to shift a non-terminal - $this->yyidx++; - $x = new OQLParser_yyStackEntry; - $x->stateno = $nextstate; - $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; - $this->yystack[$this->yyidx] = $x; - continue 2; - } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - if (!$token) { - // end of input: this is valid - return true; - } - // the last token was just ignored, we can't accept - // by ignoring input, this is in essence ignoring a - // syntax error! - return false; - } elseif ($nextstate === self::YY_NO_ACTION) { - $this->yyidx = $yyidx; - $this->yystack = $stack; - // input accepted, but not shifted (I guess) - return true; - } else { - $yyact = $nextstate; - } - } while (true); - } - break; - } while (true); - $this->yyidx = $yyidx; - $this->yystack = $stack; - return true; - } - - /** - * Find the appropriate action for a parser given the terminal - * look-ahead token iLookAhead. - * - * If the look-ahead token is YYNOCODE, then check to see if the action is - * independent of the look-ahead. If it is, return the action, otherwise - * return YY_NO_ACTION. - * @param int The look-ahead token - */ - function yy_find_shift_action($iLookAhead) - { - $stateno = $this->yystack[$this->yyidx]->stateno; - - /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */ - if (!isset(self::$yy_shift_ofst[$stateno])) { - // no shift actions - return self::$yy_default[$stateno]; - } - $i = self::$yy_shift_ofst[$stateno]; - if ($i === self::YY_SHIFT_USE_DFLT) { - return self::$yy_default[$stateno]; - } - if ($iLookAhead == self::YYNOCODE) { - return self::YY_NO_ACTION; - } - $i += $iLookAhead; - if ($i < 0 || $i >= self::YY_SZ_ACTTAB || - self::$yy_lookahead[$i] != $iLookAhead) { - if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback) - && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) { - if (self::$yyTraceFILE) { - fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " . - self::$yyTokenName[$iLookAhead] . " => " . - self::$yyTokenName[$iFallback] . "\n"); - } - return $this->yy_find_shift_action($iFallback); - } - return self::$yy_default[$stateno]; - } else { - return self::$yy_action[$i]; - } - } - - /** - * Find the appropriate action for a parser given the non-terminal - * look-ahead token $iLookAhead. - * - * If the look-ahead token is self::YYNOCODE, then check to see if the action is - * independent of the look-ahead. If it is, return the action, otherwise - * return self::YY_NO_ACTION. - * @param int Current state number - * @param int The look-ahead token - */ - function yy_find_reduce_action($stateno, $iLookAhead) - { - /* $stateno = $this->yystack[$this->yyidx]->stateno; */ - - if (!isset(self::$yy_reduce_ofst[$stateno])) { - return self::$yy_default[$stateno]; - } - $i = self::$yy_reduce_ofst[$stateno]; - if ($i == self::YY_REDUCE_USE_DFLT) { - return self::$yy_default[$stateno]; - } - if ($iLookAhead == self::YYNOCODE) { - return self::YY_NO_ACTION; - } - $i += $iLookAhead; - if ($i < 0 || $i >= self::YY_SZ_ACTTAB || - self::$yy_lookahead[$i] != $iLookAhead) { - return self::$yy_default[$stateno]; - } else { - return self::$yy_action[$i]; - } - } - - /** - * Perform a shift action. - * @param int The new state to shift in - * @param int The major token to shift in - * @param mixed the minor token to shift in - */ - function yy_shift($yyNewState, $yyMajor, $yypMinor) - { - $this->yyidx++; - if ($this->yyidx >= self::YYSTACKDEPTH) { - $this->yyidx--; - if (self::$yyTraceFILE) { - fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt); - } - while ($this->yyidx >= 0) { - $this->yy_pop_parser_stack(); - } - /* Here code is inserted which will execute if the parser - ** stack ever overflows */ - return; - } - $yytos = new OQLParser_yyStackEntry; - $yytos->stateno = $yyNewState; - $yytos->major = $yyMajor; - $yytos->minor = $yypMinor; - array_push($this->yystack, $yytos); - if (self::$yyTraceFILE && $this->yyidx > 0) { - fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt, - $yyNewState); - fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt); - for ($i = 1; $i <= $this->yyidx; $i++) { - fprintf(self::$yyTraceFILE, " %s", - self::$yyTokenName[$this->yystack[$i]->major]); - } - fwrite(self::$yyTraceFILE,"\n"); - } - } - - /** - * The following table contains information about every rule that - * is used during the reduce. - * - *
-     * array(
-     *  array(
-     *   int $lhs;         Symbol on the left-hand side of the rule
-     *   int $nrhs;     Number of right-hand side symbols in the rule
-     *  ),...
-     * );
-     * 
- */ - static public $yyRuleInfo = array( - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 76, 'rhs' => 3 ), - array( 'lhs' => 76, 'rhs' => 3 ), - array( 'lhs' => 77, 'rhs' => 4 ), - array( 'lhs' => 77, 'rhs' => 6 ), - array( 'lhs' => 77, 'rhs' => 6 ), - array( 'lhs' => 77, 'rhs' => 8 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 3 ), + /* 90 */ "str_operator ::= MATCHES", + /* 91 */ "bitwise_operator1 ::= BITWISE_LEFT_SHIFT", + /* 92 */ "bitwise_operator1 ::= BITWISE_RIGHT_SHIFT", + /* 93 */ "bitwise_operator3 ::= BITWISE_AND", + /* 94 */ "bitwise_operator4 ::= BITWISE_OR", + /* 95 */ "bitwise_operator4 ::= BITWISE_XOR", + /* 96 */ "list_operator ::= IN", + /* 97 */ "list_operator ::= NOT_IN", + /* 98 */ "func_name ::= F_IF", + /* 99 */ "func_name ::= F_ELT", + /* 100 */ "func_name ::= F_COALESCE", + /* 101 */ "func_name ::= F_ISNULL", + /* 102 */ "func_name ::= F_CONCAT", + /* 103 */ "func_name ::= F_SUBSTR", + /* 104 */ "func_name ::= F_TRIM", + /* 105 */ "func_name ::= F_DATE", + /* 106 */ "func_name ::= F_DATE_FORMAT", + /* 107 */ "func_name ::= F_CURRENT_DATE", + /* 108 */ "func_name ::= F_NOW", + /* 109 */ "func_name ::= F_TIME", + /* 110 */ "func_name ::= F_TO_DAYS", + /* 111 */ "func_name ::= F_FROM_DAYS", + /* 112 */ "func_name ::= F_YEAR", + /* 113 */ "func_name ::= F_MONTH", + /* 114 */ "func_name ::= F_DAY", + /* 115 */ "func_name ::= F_DATE_ADD", + /* 116 */ "func_name ::= F_DATE_SUB", + /* 117 */ "func_name ::= F_ROUND", + /* 118 */ "func_name ::= F_FLOOR", + /* 119 */ "func_name ::= F_INET_ATON", + /* 120 */ "func_name ::= F_INET_NTOA", + ); + + /** + * This function returns the symbolic name associated with a token + * value. + * @param int + * @return string + */ + function tokenName($tokenType) + { + if ($tokenType === 0) { + return 'End of Input'; + } + if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) { + return self::$yyTokenName[$tokenType]; + } else { + return "Unknown"; + } + } + + /** + * The following function deletes the value associated with a + * symbol. The symbol can be either a terminal or nonterminal. + * @param int the symbol code + * @param mixed the symbol's value + */ + static function yy_destructor($yymajor, $yypminor) + { + switch ($yymajor) { + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are not used + ** inside the C code. + */ + default: break; /* If no destructor action specified: do nothing */ + } + } + + /** + * Pop the parser's stack once. + * + * If there is a destructor routine associated with the token which + * is popped from the stack, then call it. + * + * Return the major token number for the symbol popped. + * @param OQLParser_yyParser + * @return int + */ + function yy_pop_parser_stack() + { + if (!count($this->yystack)) { + return; + } + $yytos = array_pop($this->yystack); + if (self::$yyTraceFILE && $this->yyidx >= 0) { + fwrite(self::$yyTraceFILE, + self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] . + "\n"); + } + $yymajor = $yytos->major; + self::yy_destructor($yymajor, $yytos->minor); + $this->yyidx--; + return $yymajor; + } + + /** + * Deallocate and destroy a parser. Destructors are all called for + * all stack elements before shutting the parser down. + */ + function __destruct() + { + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + if (is_resource(self::$yyTraceFILE)) { + fclose(self::$yyTraceFILE); + } + } + + /** + * Based on the current state and parser stack, get a list of all + * possible lookahead tokens + * @param int + * @return array + */ + function yy_get_expected_tokens($token) + { + $state = $this->yystack[$this->yyidx]->stateno; + $expected = self::$yyExpectedTokens[$state]; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return $expected; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return array_unique($expected); + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate])) { + $expected += self::$yyExpectedTokens[$nextstate]; + if (in_array($token, + self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return array_unique($expected); + } + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return array_unique($expected); + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return $expected; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + return array_unique($expected); + } + + /** + * Based on the parser state and current parser stack, determine whether + * the lookahead token is possible. + * + * The parser will convert the token value to an error token if not. This + * catches some unusual edge cases where the parser would fail. + * @param int + * @return bool + */ + function yy_is_expected_token($token) + { + if ($token === 0) { + return true; // 0 is not part of this + } + $state = $this->yystack[$this->yyidx]->stateno; + if (in_array($token, self::$yyExpectedTokens[$state], true)) { + return true; + } + $stack = $this->yystack; + $yyidx = $this->yyidx; + do { + $yyact = $this->yy_find_shift_action($token); + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) { + // reduce action + $done = 0; + do { + if ($done++ == 100) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // too much recursion prevents proper detection + // so give up + return true; + } + $yyruleno = $yyact - self::YYNSTATE; + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs']; + $nextstate = $this->yy_find_reduce_action( + $this->yystack[$this->yyidx]->stateno, + self::$yyRuleInfo[$yyruleno]['lhs']); + if (isset(self::$yyExpectedTokens[$nextstate]) && + in_array($token, self::$yyExpectedTokens[$nextstate], true)) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + return true; + } + if ($nextstate < self::YYNSTATE) { + // we need to shift a non-terminal + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $nextstate; + $x->major = self::$yyRuleInfo[$yyruleno]['lhs']; + $this->yystack[$this->yyidx] = $x; + continue 2; + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + if (!$token) { + // end of input: this is valid + return true; + } + // the last token was just ignored, we can't accept + // by ignoring input, this is in essence ignoring a + // syntax error! + return false; + } elseif ($nextstate === self::YY_NO_ACTION) { + $this->yyidx = $yyidx; + $this->yystack = $stack; + // input accepted, but not shifted (I guess) + return true; + } else { + $yyact = $nextstate; + } + } while (true); + } + break; + } while (true); + $this->yyidx = $yyidx; + $this->yystack = $stack; + return true; + } + + /** + * Find the appropriate action for a parser given the terminal + * look-ahead token iLookAhead. + * + * If the look-ahead token is YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return YY_NO_ACTION. + * @param int The look-ahead token + */ + function yy_find_shift_action($iLookAhead) + { + $stateno = $this->yystack[$this->yyidx]->stateno; + + /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */ + if (!isset(self::$yy_shift_ofst[$stateno])) { + // no shift actions + return self::$yy_default[$stateno]; + } + $i = self::$yy_shift_ofst[$stateno]; + if ($i === self::YY_SHIFT_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback) + && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) { + if (self::$yyTraceFILE) { + fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " . + self::$yyTokenName[$iLookAhead] . " => " . + self::$yyTokenName[$iFallback] . "\n"); + } + return $this->yy_find_shift_action($iFallback); + } + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Find the appropriate action for a parser given the non-terminal + * look-ahead token $iLookAhead. + * + * If the look-ahead token is self::YYNOCODE, then check to see if the action is + * independent of the look-ahead. If it is, return the action, otherwise + * return self::YY_NO_ACTION. + * @param int Current state number + * @param int The look-ahead token + */ + function yy_find_reduce_action($stateno, $iLookAhead) + { + /* $stateno = $this->yystack[$this->yyidx]->stateno; */ + + if (!isset(self::$yy_reduce_ofst[$stateno])) { + return self::$yy_default[$stateno]; + } + $i = self::$yy_reduce_ofst[$stateno]; + if ($i == self::YY_REDUCE_USE_DFLT) { + return self::$yy_default[$stateno]; + } + if ($iLookAhead == self::YYNOCODE) { + return self::YY_NO_ACTION; + } + $i += $iLookAhead; + if ($i < 0 || $i >= self::YY_SZ_ACTTAB || + self::$yy_lookahead[$i] != $iLookAhead) { + return self::$yy_default[$stateno]; + } else { + return self::$yy_action[$i]; + } + } + + /** + * Perform a shift action. + * @param int The new state to shift in + * @param int The major token to shift in + * @param mixed the minor token to shift in + */ + function yy_shift($yyNewState, $yyMajor, $yypMinor) + { + $this->yyidx++; + if ($this->yyidx >= self::YYSTACKDEPTH) { + $this->yyidx--; + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will execute if the parser + ** stack ever overflows */ + return; + } + $yytos = new OQLParser_yyStackEntry; + $yytos->stateno = $yyNewState; + $yytos->major = $yyMajor; + $yytos->minor = $yypMinor; + array_push($this->yystack, $yytos); + if (self::$yyTraceFILE && $this->yyidx > 0) { + fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt, + $yyNewState); + fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt); + for ($i = 1; $i <= $this->yyidx; $i++) { + fprintf(self::$yyTraceFILE, " %s", + self::$yyTokenName[$this->yystack[$i]->major]); + } + fwrite(self::$yyTraceFILE,"\n"); + } + } + + /** + * The following table contains information about every rule that + * is used during the reduce. + * + *
+     * array(
+     *  array(
+     *   int $lhs;         Symbol on the left-hand side of the rule
+     *   int $nrhs;     Number of right-hand side symbols in the rule
+     *  ),...
+     * );
+     * 
+ */ + static public $yyRuleInfo = array( + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 76, 'rhs' => 1 ), + array( 'lhs' => 77, 'rhs' => 3 ), + array( 'lhs' => 77, 'rhs' => 3 ), + array( 'lhs' => 78, 'rhs' => 4 ), + array( 'lhs' => 78, 'rhs' => 6 ), + array( 'lhs' => 78, 'rhs' => 6 ), + array( 'lhs' => 78, 'rhs' => 8 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 3 ), + array( 'lhs' => 82, 'rhs' => 2 ), + array( 'lhs' => 82, 'rhs' => 0 ), array( 'lhs' => 81, 'rhs' => 2 ), + array( 'lhs' => 81, 'rhs' => 1 ), array( 'lhs' => 81, 'rhs' => 0 ), - array( 'lhs' => 80, 'rhs' => 2 ), - array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 80, 'rhs' => 0 ), - array( 'lhs' => 83, 'rhs' => 6 ), - array( 'lhs' => 83, 'rhs' => 4 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 84, 'rhs' => 3 ), - array( 'lhs' => 78, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 87, 'rhs' => 4 ), - array( 'lhs' => 87, 'rhs' => 3 ), - array( 'lhs' => 87, 'rhs' => 3 ), - array( 'lhs' => 94, 'rhs' => 1 ), - array( 'lhs' => 94, 'rhs' => 3 ), - array( 'lhs' => 96, 'rhs' => 1 ), - array( 'lhs' => 96, 'rhs' => 3 ), - array( 'lhs' => 98, 'rhs' => 1 ), - array( 'lhs' => 98, 'rhs' => 3 ), - array( 'lhs' => 86, 'rhs' => 1 ), - array( 'lhs' => 86, 'rhs' => 3 ), - array( 'lhs' => 93, 'rhs' => 3 ), - array( 'lhs' => 101, 'rhs' => 1 ), - array( 'lhs' => 101, 'rhs' => 3 ), - array( 'lhs' => 91, 'rhs' => 0 ), - array( 'lhs' => 91, 'rhs' => 1 ), - array( 'lhs' => 91, 'rhs' => 3 ), - array( 'lhs' => 102, 'rhs' => 1 ), - array( 'lhs' => 102, 'rhs' => 3 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 103, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), - array( 'lhs' => 104, 'rhs' => 1 ), - array( 'lhs' => 105, 'rhs' => 1 ), - array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 6 ), + array( 'lhs' => 84, 'rhs' => 4 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), + array( 'lhs' => 85, 'rhs' => 3 ), array( 'lhs' => 85, 'rhs' => 3 ), array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 4 ), + array( 'lhs' => 88, 'rhs' => 3 ), + array( 'lhs' => 88, 'rhs' => 3 ), + array( 'lhs' => 95, 'rhs' => 1 ), + array( 'lhs' => 95, 'rhs' => 3 ), + array( 'lhs' => 97, 'rhs' => 1 ), + array( 'lhs' => 97, 'rhs' => 3 ), + array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 99, 'rhs' => 3 ), + array( 'lhs' => 87, 'rhs' => 1 ), + array( 'lhs' => 87, 'rhs' => 3 ), + array( 'lhs' => 94, 'rhs' => 3 ), + array( 'lhs' => 102, 'rhs' => 1 ), + array( 'lhs' => 102, 'rhs' => 3 ), + array( 'lhs' => 92, 'rhs' => 0 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 3 ), + array( 'lhs' => 103, 'rhs' => 1 ), + array( 'lhs' => 103, 'rhs' => 3 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), + array( 'lhs' => 104, 'rhs' => 1 ), array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 108, 'rhs' => 1 ), - array( 'lhs' => 106, 'rhs' => 1 ), - array( 'lhs' => 106, 'rhs' => 2 ), + array( 'lhs' => 89, 'rhs' => 1 ), + array( 'lhs' => 105, 'rhs' => 1 ), array( 'lhs' => 106, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 1 ), + array( 'lhs' => 86, 'rhs' => 3 ), + array( 'lhs' => 80, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 109, 'rhs' => 1 ), array( 'lhs' => 107, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 95, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 97, 'rhs' => 1 ), - array( 'lhs' => 99, 'rhs' => 1 ), - array( 'lhs' => 99, 'rhs' => 1 ), + array( 'lhs' => 107, 'rhs' => 2 ), + array( 'lhs' => 107, 'rhs' => 1 ), + array( 'lhs' => 108, 'rhs' => 1 ), + array( 'lhs' => 96, 'rhs' => 1 ), + array( 'lhs' => 96, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), + array( 'lhs' => 98, 'rhs' => 1 ), array( 'lhs' => 100, 'rhs' => 1 ), array( 'lhs' => 100, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 109, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 111, 'rhs' => 1 ), - array( 'lhs' => 112, 'rhs' => 1 ), - array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 101, 'rhs' => 1 ), + array( 'lhs' => 101, 'rhs' => 1 ), array( 'lhs' => 110, 'rhs' => 1 ), array( 'lhs' => 110, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), + array( 'lhs' => 112, 'rhs' => 1 ), array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 113, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), + array( 'lhs' => 111, 'rhs' => 1 ), array( 'lhs' => 114, 'rhs' => 1 ), - array( 'lhs' => 114, 'rhs' => 1 ), - array( 'lhs' => 92, 'rhs' => 1 ), - array( 'lhs' => 92, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - ); - - /** - * The following table contains a mapping of reduce action to method name - * that handles the reduction. - * - * If a rule is not set, it has no handler. - */ - static public $yyReduceMap = array( + array( 'lhs' => 115, 'rhs' => 1 ), + array( 'lhs' => 115, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 91, 'rhs' => 1 ), + ); + + /** + * The following table contains a mapping of reduce action to method name + * that handles the reduction. + * + * If a rule is not set, it has no handler. + */ + static public $yyReduceMap = array( 0 => 0, 1 => 0, 2 => 0, @@ -1365,9 +1371,9 @@ static public $yy_action = array( 32 => 32, 33 => 33, 35 => 33, - 37 => 33, 39 => 33, 41 => 33, + 37 => 37, 42 => 42, 45 => 45, 49 => 49, @@ -1376,7 +1382,6 @@ static public $yy_action = array( 60 => 60, 61 => 61, 62 => 62, - 97 => 62, 98 => 62, 99 => 62, 100 => 62, @@ -1399,6 +1404,7 @@ static public $yy_action = array( 117 => 62, 118 => 62, 119 => 62, + 120 => 62, 63 => 63, 64 => 64, 65 => 65, @@ -1433,151 +1439,164 @@ static public $yy_action = array( 94 => 69, 95 => 69, 96 => 69, - ); - /* Beginning here are the reduction cases. A typical example - ** follows: - ** #line - ** function yy_r0($yymsp){ ... } // User supplied code - ** #line - */ -#line 29 "..\oql-parser.y" + 97 => 69, + ); + /* Beginning here are the reduction cases. A typical example + ** follows: + ** #line + ** function yy_r0($yymsp){ ... } // User supplied code + ** #line + */ +#line 29 "../oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1449 "..\oql-parser.php" -#line 33 "..\oql-parser.y" +#line 1456 "../oql-parser.php" +#line 33 "../oql-parser.y" function yy_r3(){ $this->_retvalue = new OqlUnionQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1454 "..\oql-parser.php" -#line 40 "..\oql-parser.y" +#line 1461 "../oql-parser.php" +#line 40 "../oql-parser.y" function yy_r5(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1459 "..\oql-parser.php" -#line 43 "..\oql-parser.y" +#line 1466 "../oql-parser.php" +#line 43 "../oql-parser.y" function yy_r6(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1464 "..\oql-parser.php" -#line 47 "..\oql-parser.y" +#line 1471 "../oql-parser.php" +#line 47 "../oql-parser.y" function yy_r7(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); } -#line 1469 "..\oql-parser.php" -#line 50 "..\oql-parser.y" +#line 1476 "../oql-parser.php" +#line 50 "../oql-parser.y" function yy_r8(){ $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); } -#line 1474 "..\oql-parser.php" -#line 55 "..\oql-parser.y" +#line 1481 "../oql-parser.php" +#line 55 "../oql-parser.y" function yy_r9(){ $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); } -#line 1479 "..\oql-parser.php" -#line 58 "..\oql-parser.y" +#line 1486 "../oql-parser.php" +#line 58 "../oql-parser.y" function yy_r10(){ array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; } -#line 1485 "..\oql-parser.php" -#line 63 "..\oql-parser.y" +#line 1492 "../oql-parser.php" +#line 63 "../oql-parser.y" function yy_r11(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1488 "..\oql-parser.php" -#line 64 "..\oql-parser.y" +#line 1495 "../oql-parser.php" +#line 64 "../oql-parser.y" function yy_r12(){ $this->_retvalue = null; } -#line 1491 "..\oql-parser.php" -#line 66 "..\oql-parser.y" +#line 1498 "../oql-parser.php" +#line 66 "../oql-parser.y" function yy_r13(){ // insert the join statement on top of the existing list array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); // and return the updated array $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1499 "..\oql-parser.php" -#line 72 "..\oql-parser.y" +#line 1506 "../oql-parser.php" +#line 72 "../oql-parser.y" function yy_r14(){ $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1504 "..\oql-parser.php" -#line 78 "..\oql-parser.y" +#line 1511 "../oql-parser.php" +#line 78 "../oql-parser.y" function yy_r16(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1510 "..\oql-parser.php" -#line 83 "..\oql-parser.y" +#line 1517 "../oql-parser.php" +#line 83 "../oql-parser.y" function yy_r17(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1516 "..\oql-parser.php" -#line 88 "..\oql-parser.y" +#line 1523 "../oql-parser.php" +#line 88 "../oql-parser.y" function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1519 "..\oql-parser.php" -#line 89 "..\oql-parser.y" +#line 1526 "../oql-parser.php" +#line 89 "../oql-parser.y" function yy_r19(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1522 "..\oql-parser.php" -#line 90 "..\oql-parser.y" +#line 1529 "../oql-parser.php" +#line 90 "../oql-parser.y" function yy_r20(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1525 "..\oql-parser.php" -#line 91 "..\oql-parser.y" +#line 1532 "../oql-parser.php" +#line 91 "../oql-parser.y" function yy_r21(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW', $this->yystack[$this->yyidx + 0]->minor); } -#line 1528 "..\oql-parser.php" -#line 92 "..\oql-parser.y" +#line 1535 "../oql-parser.php" +#line 92 "../oql-parser.y" function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_BELOW_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1531 "..\oql-parser.php" -#line 93 "..\oql-parser.y" +#line 1538 "../oql-parser.php" +#line 93 "../oql-parser.y" function yy_r23(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1534 "..\oql-parser.php" -#line 94 "..\oql-parser.y" +#line 1541 "../oql-parser.php" +#line 94 "../oql-parser.y" function yy_r24(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1537 "..\oql-parser.php" -#line 95 "..\oql-parser.y" +#line 1544 "../oql-parser.php" +#line 95 "../oql-parser.y" function yy_r25(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE', $this->yystack[$this->yyidx + 0]->minor); } -#line 1540 "..\oql-parser.php" -#line 96 "..\oql-parser.y" +#line 1547 "../oql-parser.php" +#line 96 "../oql-parser.y" function yy_r26(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, 'NOT_ABOVE_STRICT', $this->yystack[$this->yyidx + 0]->minor); } -#line 1543 "..\oql-parser.php" -#line 98 "..\oql-parser.y" +#line 1550 "../oql-parser.php" +#line 98 "../oql-parser.y" function yy_r27(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1546 "..\oql-parser.php" -#line 103 "..\oql-parser.y" +#line 1553 "../oql-parser.php" +#line 103 "../oql-parser.y" function yy_r31(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1549 "..\oql-parser.php" -#line 104 "..\oql-parser.y" +#line 1556 "../oql-parser.php" +#line 104 "../oql-parser.y" function yy_r32(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1552 "..\oql-parser.php" -#line 105 "..\oql-parser.y" +#line 1559 "../oql-parser.php" +#line 105 "../oql-parser.y" function yy_r33(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1555 "..\oql-parser.php" -#line 120 "..\oql-parser.y" +#line 1562 "../oql-parser.php" +#line 111 "../oql-parser.y" + function yy_r37(){ + if ($this->yystack[$this->yyidx + -1]->minor == 'MATCHES') + { + $this->_retvalue = new MatchOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + } + else + { + $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); + } + } +#line 1574 "../oql-parser.php" +#line 129 "../oql-parser.y" function yy_r42(){ $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); } -#line 1560 "..\oql-parser.php" -#line 131 "..\oql-parser.y" +#line 1579 "../oql-parser.php" +#line 140 "../oql-parser.y" function yy_r45(){ $this->_retvalue = array(); } -#line 1565 "..\oql-parser.php" -#line 142 "..\oql-parser.y" +#line 1584 "../oql-parser.php" +#line 151 "../oql-parser.y" function yy_r49(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1568 "..\oql-parser.php" -#line 154 "..\oql-parser.y" +#line 1587 "../oql-parser.php" +#line 163 "../oql-parser.y" function yy_r58(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1571 "..\oql-parser.php" -#line 157 "..\oql-parser.y" +#line 1590 "../oql-parser.php" +#line 166 "../oql-parser.y" function yy_r60(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1574 "..\oql-parser.php" -#line 158 "..\oql-parser.y" +#line 1593 "../oql-parser.php" +#line 167 "../oql-parser.y" function yy_r61(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1577 "..\oql-parser.php" -#line 159 "..\oql-parser.y" +#line 1596 "../oql-parser.php" +#line 168 "../oql-parser.y" function yy_r62(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1580 "..\oql-parser.php" -#line 162 "..\oql-parser.y" +#line 1599 "../oql-parser.php" +#line 171 "../oql-parser.y" function yy_r63(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } -#line 1583 "..\oql-parser.php" -#line 164 "..\oql-parser.y" +#line 1602 "../oql-parser.php" +#line 173 "../oql-parser.y" function yy_r64(){ if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') { @@ -1589,304 +1608,304 @@ static public $yy_action = array( } $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1596 "..\oql-parser.php" -#line 175 "..\oql-parser.y" +#line 1615 "../oql-parser.php" +#line 184 "../oql-parser.y" function yy_r65(){$this->_retvalue=(int)$this->yystack[$this->yyidx + 0]->minor; } -#line 1599 "..\oql-parser.php" -#line 176 "..\oql-parser.y" +#line 1618 "../oql-parser.php" +#line 185 "../oql-parser.y" function yy_r66(){$this->_retvalue=(int)-$this->yystack[$this->yyidx + 0]->minor; } -#line 1602 "..\oql-parser.php" -#line 177 "..\oql-parser.y" +#line 1621 "../oql-parser.php" +#line 186 "../oql-parser.y" function yy_r67(){$this->_retvalue=new OqlHexValue($this->yystack[$this->yyidx + 0]->minor); } -#line 1605 "..\oql-parser.php" -#line 178 "..\oql-parser.y" +#line 1624 "../oql-parser.php" +#line 187 "../oql-parser.y" function yy_r68(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1608 "..\oql-parser.php" -#line 181 "..\oql-parser.y" +#line 1627 "../oql-parser.php" +#line 190 "../oql-parser.y" function yy_r69(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1611 "..\oql-parser.php" - - /** - * placeholder for the left hand side in a reduce operation. - * - * For a parser with a rule like this: - *
-     * rule(A) ::= B. { A = 1; }
-     * 
- * - * The parser will translate to something like: - * - * - * function yy_r0(){$this->_retvalue = 1;} - * - */ - private $_retvalue; - - /** - * Perform a reduce action and the shift that must immediately - * follow the reduce. - * - * For a rule such as: - * - *
-     * A ::= B blah C. { dosomething(); }
-     * 
- * - * This function will first call the action, if any, ("dosomething();" in our - * example), and then it will pop three states from the stack, - * one for each entry on the right-hand side of the expression - * (B, blah, and C in our example rule), and then push the result of the action - * back on to the stack with the resulting state reduced to (as described in the .out - * file) - * @param int Number of the rule by which to reduce - */ - function yy_reduce($yyruleno) - { - //int $yygoto; /* The next state */ - //int $yyact; /* The next action */ - //mixed $yygotominor; /* The LHS of the rule reduced */ - //OQLParser_yyStackEntry $yymsp; /* The top of the parser's stack */ - //int $yysize; /* Amount to pop the stack */ - $yymsp = $this->yystack[$this->yyidx]; - if (self::$yyTraceFILE && $yyruleno >= 0 - && $yyruleno < count(self::$yyRuleName)) { - fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n", - self::$yyTracePrompt, $yyruleno, - self::$yyRuleName[$yyruleno]); - } - - $this->_retvalue = $yy_lefthand_side = null; - if (array_key_exists($yyruleno, self::$yyReduceMap)) { - // call the action - $this->_retvalue = null; - $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}(); - $yy_lefthand_side = $this->_retvalue; - } - $yygoto = self::$yyRuleInfo[$yyruleno]['lhs']; - $yysize = self::$yyRuleInfo[$yyruleno]['rhs']; - $this->yyidx -= $yysize; - for ($i = $yysize; $i; $i--) { - // pop all of the right-hand side parameters - array_pop($this->yystack); - } - $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto); - if ($yyact < self::YYNSTATE) { - /* If we are not debugging and the reduce action popped at least - ** one element off the stack, then we can push the new element back - ** onto the stack here, and skip the stack overflow test in yy_shift(). - ** That gives a significant speed improvement. */ - if (!self::$yyTraceFILE && $yysize) { - $this->yyidx++; - $x = new OQLParser_yyStackEntry; - $x->stateno = $yyact; - $x->major = $yygoto; - $x->minor = $yy_lefthand_side; - $this->yystack[$this->yyidx] = $x; - } else { - $this->yy_shift($yyact, $yygoto, $yy_lefthand_side); - } - } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) { - $this->yy_accept(); - } - } - - /** - * The following code executes when the parse fails - * - * Code from %parse_fail is inserted here - */ - function yy_parse_failed() - { - if (self::$yyTraceFILE) { - fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt); - } - while ($this->yyidx >= 0) { - $this->yy_pop_parser_stack(); - } - /* Here code is inserted which will be executed whenever the - ** parser fails */ - } - - /** - * The following code executes when a syntax error first occurs. - * - * %syntax_error code is inserted here - * @param int The major type of the error token - * @param mixed The minor type of the error token - */ - function yy_syntax_error($yymajor, $TOKEN) - { -#line 25 "..\oql-parser.y" +#line 1630 "../oql-parser.php" + + /** + * placeholder for the left hand side in a reduce operation. + * + * For a parser with a rule like this: + *
+     * rule(A) ::= B. { A = 1; }
+     * 
+ * + * The parser will translate to something like: + * + * + * function yy_r0(){$this->_retvalue = 1;} + * + */ + private $_retvalue; + + /** + * Perform a reduce action and the shift that must immediately + * follow the reduce. + * + * For a rule such as: + * + *
+     * A ::= B blah C. { dosomething(); }
+     * 
+ * + * This function will first call the action, if any, ("dosomething();" in our + * example), and then it will pop three states from the stack, + * one for each entry on the right-hand side of the expression + * (B, blah, and C in our example rule), and then push the result of the action + * back on to the stack with the resulting state reduced to (as described in the .out + * file) + * @param int Number of the rule by which to reduce + */ + function yy_reduce($yyruleno) + { + //int $yygoto; /* The next state */ + //int $yyact; /* The next action */ + //mixed $yygotominor; /* The LHS of the rule reduced */ + //OQLParser_yyStackEntry $yymsp; /* The top of the parser's stack */ + //int $yysize; /* Amount to pop the stack */ + $yymsp = $this->yystack[$this->yyidx]; + if (self::$yyTraceFILE && $yyruleno >= 0 + && $yyruleno < count(self::$yyRuleName)) { + fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n", + self::$yyTracePrompt, $yyruleno, + self::$yyRuleName[$yyruleno]); + } + + $this->_retvalue = $yy_lefthand_side = null; + if (array_key_exists($yyruleno, self::$yyReduceMap)) { + // call the action + $this->_retvalue = null; + $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}(); + $yy_lefthand_side = $this->_retvalue; + } + $yygoto = self::$yyRuleInfo[$yyruleno]['lhs']; + $yysize = self::$yyRuleInfo[$yyruleno]['rhs']; + $this->yyidx -= $yysize; + for ($i = $yysize; $i; $i--) { + // pop all of the right-hand side parameters + array_pop($this->yystack); + } + $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto); + if ($yyact < self::YYNSTATE) { + /* If we are not debugging and the reduce action popped at least + ** one element off the stack, then we can push the new element back + ** onto the stack here, and skip the stack overflow test in yy_shift(). + ** That gives a significant speed improvement. */ + if (!self::$yyTraceFILE && $yysize) { + $this->yyidx++; + $x = new OQLParser_yyStackEntry; + $x->stateno = $yyact; + $x->major = $yygoto; + $x->minor = $yy_lefthand_side; + $this->yystack[$this->yyidx] = $x; + } else { + $this->yy_shift($yyact, $yygoto, $yy_lefthand_side); + } + } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) { + $this->yy_accept(); + } + } + + /** + * The following code executes when the parse fails + * + * Code from %parse_fail is inserted here + */ + function yy_parse_failed() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser fails */ + } + + /** + * The following code executes when a syntax error first occurs. + * + * %syntax_error code is inserted here + * @param int The major type of the error token + * @param mixed The minor type of the error token + */ + function yy_syntax_error($yymajor, $TOKEN) + { +#line 25 "../oql-parser.y" throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1727 "..\oql-parser.php" - } - - /** - * The following is executed when the parser accepts - * - * %parse_accept code is inserted here - */ - function yy_accept() - { - if (self::$yyTraceFILE) { - fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt); - } - while ($this->yyidx >= 0) { - $stack = $this->yy_pop_parser_stack(); - } - /* Here code is inserted which will be executed whenever the - ** parser accepts */ - } - - /** - * The main parser program. - * - * The first argument is the major token number. The second is - * the token value string as scanned from the input. - * - * @param int $yymajor the token number - * @param mixed $yytokenvalue the token value - * @param mixed ... any extra arguments that should be passed to handlers - * - * @return void - */ - function doParse($yymajor, $yytokenvalue) - { -// $yyact; /* The parser action. */ -// $yyendofinput; /* True if we are at the end of input */ - $yyerrorhit = 0; /* True if yymajor has invoked an error */ - - /* (re)initialize the parser, if necessary */ - if ($this->yyidx === null || $this->yyidx < 0) { - /* if ($yymajor == 0) return; // not sure why this was here... */ - $this->yyidx = 0; - $this->yyerrcnt = -1; - $x = new OQLParser_yyStackEntry; - $x->stateno = 0; - $x->major = 0; - $this->yystack = array(); - array_push($this->yystack, $x); - } - $yyendofinput = ($yymajor==0); - - if (self::$yyTraceFILE) { - fprintf( - self::$yyTraceFILE, - "%sInput %s\n", - self::$yyTracePrompt, - self::$yyTokenName[$yymajor] - ); - } - - do { - $yyact = $this->yy_find_shift_action($yymajor); - if ($yymajor < self::YYERRORSYMBOL - && !$this->yy_is_expected_token($yymajor) - ) { - // force a syntax error - $yyact = self::YY_ERROR_ACTION; - } - if ($yyact < self::YYNSTATE) { - $this->yy_shift($yyact, $yymajor, $yytokenvalue); - $this->yyerrcnt--; - if ($yyendofinput && $this->yyidx >= 0) { - $yymajor = 0; - } else { - $yymajor = self::YYNOCODE; - } - } elseif ($yyact < self::YYNSTATE + self::YYNRULE) { - $this->yy_reduce($yyact - self::YYNSTATE); - } elseif ($yyact == self::YY_ERROR_ACTION) { - if (self::$yyTraceFILE) { - fprintf( - self::$yyTraceFILE, - "%sSyntax Error!\n", - self::$yyTracePrompt - ); - } - if (self::YYERRORSYMBOL) { - /* A syntax error has occurred. - ** The response to an error depends upon whether or not the - ** grammar defines an error token "ERROR". - ** - ** This is what we do if the grammar does define ERROR: - ** - ** * Call the %syntax_error function. - ** - ** * Begin popping the stack until we enter a state where - ** it is legal to shift the error symbol, then shift - ** the error symbol. - ** - ** * Set the error count to three. - ** - ** * Begin accepting and shifting new tokens. No new error - ** processing will occur until three tokens have been - ** shifted successfully. - ** - */ - if ($this->yyerrcnt < 0) { - $this->yy_syntax_error($yymajor, $yytokenvalue); - } - $yymx = $this->yystack[$this->yyidx]->major; - if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ) { - if (self::$yyTraceFILE) { - fprintf( - self::$yyTraceFILE, - "%sDiscard input token %s\n", - self::$yyTracePrompt, - self::$yyTokenName[$yymajor] - ); - } - $this->yy_destructor($yymajor, $yytokenvalue); - $yymajor = self::YYNOCODE; - } else { - while ($this->yyidx >= 0 - && $yymx != self::YYERRORSYMBOL - && ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE - ) { - $this->yy_pop_parser_stack(); - } - if ($this->yyidx < 0 || $yymajor==0) { - $this->yy_destructor($yymajor, $yytokenvalue); - $this->yy_parse_failed(); - $yymajor = self::YYNOCODE; - } elseif ($yymx != self::YYERRORSYMBOL) { - $u2 = 0; - $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2); - } - } - $this->yyerrcnt = 3; - $yyerrorhit = 1; - } else { - /* YYERRORSYMBOL is not defined */ - /* This is what we do if the grammar does not define ERROR: - ** - ** * Report an error message, and throw away the input token. - ** - ** * If the input token is $, then fail the parse. - ** - ** As before, subsequent error messages are suppressed until - ** three input tokens have been successfully shifted. - */ - if ($this->yyerrcnt <= 0) { - $this->yy_syntax_error($yymajor, $yytokenvalue); - } - $this->yyerrcnt = 3; - $this->yy_destructor($yymajor, $yytokenvalue); - if ($yyendofinput) { - $this->yy_parse_failed(); - } - $yymajor = self::YYNOCODE; - } - } else { - $this->yy_accept(); - $yymajor = self::YYNOCODE; - } - } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); - } -} -#line 239 "..\oql-parser.y" +#line 1746 "../oql-parser.php" + } + + /** + * The following is executed when the parser accepts + * + * %parse_accept code is inserted here + */ + function yy_accept() + { + if (self::$yyTraceFILE) { + fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt); + } + while ($this->yyidx >= 0) { + $stack = $this->yy_pop_parser_stack(); + } + /* Here code is inserted which will be executed whenever the + ** parser accepts */ + } + + /** + * The main parser program. + * + * The first argument is the major token number. The second is + * the token value string as scanned from the input. + * + * @param int $yymajor the token number + * @param mixed $yytokenvalue the token value + * @param mixed ... any extra arguments that should be passed to handlers + * + * @return void + */ + function doParse($yymajor, $yytokenvalue) + { +// $yyact; /* The parser action. */ +// $yyendofinput; /* True if we are at the end of input */ + $yyerrorhit = 0; /* True if yymajor has invoked an error */ + + /* (re)initialize the parser, if necessary */ + if ($this->yyidx === null || $this->yyidx < 0) { + /* if ($yymajor == 0) return; // not sure why this was here... */ + $this->yyidx = 0; + $this->yyerrcnt = -1; + $x = new OQLParser_yyStackEntry; + $x->stateno = 0; + $x->major = 0; + $this->yystack = array(); + array_push($this->yystack, $x); + } + $yyendofinput = ($yymajor==0); + + if (self::$yyTraceFILE) { + fprintf( + self::$yyTraceFILE, + "%sInput %s\n", + self::$yyTracePrompt, + self::$yyTokenName[$yymajor] + ); + } + + do { + $yyact = $this->yy_find_shift_action($yymajor); + if ($yymajor < self::YYERRORSYMBOL + && !$this->yy_is_expected_token($yymajor) + ) { + // force a syntax error + $yyact = self::YY_ERROR_ACTION; + } + if ($yyact < self::YYNSTATE) { + $this->yy_shift($yyact, $yymajor, $yytokenvalue); + $this->yyerrcnt--; + if ($yyendofinput && $this->yyidx >= 0) { + $yymajor = 0; + } else { + $yymajor = self::YYNOCODE; + } + } elseif ($yyact < self::YYNSTATE + self::YYNRULE) { + $this->yy_reduce($yyact - self::YYNSTATE); + } elseif ($yyact == self::YY_ERROR_ACTION) { + if (self::$yyTraceFILE) { + fprintf( + self::$yyTraceFILE, + "%sSyntax Error!\n", + self::$yyTracePrompt + ); + } + if (self::YYERRORSYMBOL) { + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if ($this->yyerrcnt < 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $yymx = $this->yystack[$this->yyidx]->major; + if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ) { + if (self::$yyTraceFILE) { + fprintf( + self::$yyTraceFILE, + "%sDiscard input token %s\n", + self::$yyTracePrompt, + self::$yyTokenName[$yymajor] + ); + } + $this->yy_destructor($yymajor, $yytokenvalue); + $yymajor = self::YYNOCODE; + } else { + while ($this->yyidx >= 0 + && $yymx != self::YYERRORSYMBOL + && ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE + ) { + $this->yy_pop_parser_stack(); + } + if ($this->yyidx < 0 || $yymajor==0) { + $this->yy_destructor($yymajor, $yytokenvalue); + $this->yy_parse_failed(); + $yymajor = self::YYNOCODE; + } elseif ($yymx != self::YYERRORSYMBOL) { + $u2 = 0; + $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2); + } + } + $this->yyerrcnt = 3; + $yyerrorhit = 1; + } else { + /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if ($this->yyerrcnt <= 0) { + $this->yy_syntax_error($yymajor, $yytokenvalue); + } + $this->yyerrcnt = 3; + $this->yy_destructor($yymajor, $yytokenvalue); + if ($yyendofinput) { + $this->yy_parse_failed(); + } + $yymajor = self::YYNOCODE; + } + } else { + $this->yy_accept(); + $yymajor = self::YYNOCODE; + } + } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); + } +} +#line 255 "../oql-parser.y" class OQLParserException extends OQLException @@ -1951,4 +1970,4 @@ class OQLParser extends OQLParserRaw } } -#line 1960 "..\oql-parser.php" +#line 1979 "../oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index 03bb3fe525..3e5634a4ac 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -16,7 +16,7 @@ Example: %left TIMES DIVIDE MOD. %right EXP NOT. -TODO : solve the 2 remaining shift-reduce conflicts (JOIN) +later : solve the 2 remaining shift-reduce conflicts (JOIN) */ @@ -108,7 +108,16 @@ expression_prio1(A) ::= expression_basic(X). { A = X; } expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); } expression_prio2(A) ::= expression_prio1(X). { A = X; } -expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); } +expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z).{ + if (Y == 'MATCHES') + { + A = new MatchOqlExpression(X, Z); + } + else + { + A = new BinaryOqlExpression(X, Y, Z); + } +} expression_prio3(A) ::= expression_prio2(X). { A = X; } expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); } @@ -180,18 +189,22 @@ str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));} operator1(A) ::= num_operator1(X). {A=X;} operator1(A) ::= bitwise_operator1(X). {A=X;} + operator2(A) ::= num_operator2(X). {A=X;} operator2(A) ::= str_operator(X). {A=X;} operator2(A) ::= REGEXP(X). {A=X;} operator2(A) ::= EQ(X). {A=X;} operator2(A) ::= NOT_EQ(X). {A=X;} + operator3(A) ::= LOG_AND(X). {A=X;} operator3(A) ::= bitwise_operator3(X). {A=X;} + operator4(A) ::= LOG_OR(X). {A=X;} operator4(A) ::= bitwise_operator4(X). {A=X;} num_operator1(A) ::= MATH_DIV(X). {A=X;} num_operator1(A) ::= MATH_MULT(X). {A=X;} + num_operator2(A) ::= MATH_PLUS(X). {A=X;} num_operator2(A) ::= MATH_MINUS(X). {A=X;} num_operator2(A) ::= GT(X). {A=X;} @@ -201,10 +214,13 @@ num_operator2(A) ::= LE(X). {A=X;} str_operator(A) ::= LIKE(X). {A=X;} str_operator(A) ::= NOT_LIKE(X). {A=X;} +str_operator(A) ::= MATCHES(X). {A=X;} bitwise_operator1(A) ::= BITWISE_LEFT_SHIFT(X). {A=X;} bitwise_operator1(A) ::= BITWISE_RIGHT_SHIFT(X). {A=X;} + bitwise_operator3(A) ::= BITWISE_AND(X). {A=X;} + bitwise_operator4(A) ::= BITWISE_OR(X). {A=X;} bitwise_operator4(A) ::= BITWISE_XOR(X). {A=X;} diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index b0bff7db6c..13e026f73d 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -160,6 +160,26 @@ class BinaryOqlExpression extends BinaryExpression implements CheckableExpressio } } +class MatchOqlExpression extends MatchExpression implements CheckableExpression +{ + public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) + { + $this->m_oLeftExpr->Check($oModelReflection, $aAliases, $sSourceQuery); + $this->m_oRightExpr->Check($oModelReflection, $aAliases, $sSourceQuery); + + // Only field MATCHES scalar is allowed + if (!$this->m_oLeftExpr instanceof FieldExpression) + { + throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oLeftExpr->RenderExpression(true), 0)); + } + // Only field MATCHES scalar is allowed + if (!$this->m_oRightExpr instanceof ScalarExpression) + { + throw new OqlNormalizeException('Only "field MATCHES string" syntax is allowed', $sSourceQuery, new OqlName($this->m_oRightExpr->RenderExpression(true), 0)); + } + } +} + class ScalarOqlExpression extends ScalarExpression implements CheckableExpression { public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery) diff --git a/core/oql/version.txt b/core/oql/version.txt index 25ada144d7..052f0a1a18 100644 --- a/core/oql/version.txt +++ b/core/oql/version.txt @@ -1 +1 @@ -2015-08-31 +2018-08-31 \ No newline at end of file From 1329b5f684274845f98ae41faa9ac072b265cb72 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 31 Aug 2018 16:59:02 +0200 Subject: [PATCH 017/113] =?UTF-8?q?N=C2=B0931:=20TagSet=20:=20Set=20automa?= =?UTF-8?q?tically=20the=20class=20and=20attcode=20from=20tag=20class=20na?= =?UTF-8?q?me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/tagsetfield.class.inc.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/tagsetfield.class.inc.php b/core/tagsetfield.class.inc.php index 0947b71902..a5c7264916 100644 --- a/core/tagsetfield.class.inc.php +++ b/core/tagsetfield.class.inc.php @@ -79,17 +79,21 @@ abstract class TagSetFieldData extends cmdbAbstractObject "is_null_allowed" => false, "depends_on" => array() ))); - MetaModel::Init_AddAttribute(new AttributeBoolean("is_default", array( - "allowed_values" => null, - "sql" => "is_default", - "default_value" => false, - "is_null_allowed" => true, - "depends_on" => array() - ))); - MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description', 'tag_class', 'tag_attcode')); - MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description', 'is_default')); - MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description', 'is_default')); + MetaModel::Init_SetZListItems('details', array('tag_code', 'tag_label', 'tag_description')); + MetaModel::Init_SetZListItems('standard_search', array('tag_code', 'tag_label', 'tag_description')); + MetaModel::Init_SetZListItems('list', array('tag_code', 'tag_label', 'tag_description')); } + + public function ComputeValues() + { + $sClassName = get_class($this); + // Extract class and attcode from class name using pattern TagSetFieldDataFor__>; + if (preg_match('@^TagSetFieldDataFor_(?\w+)_(?\w+)$@', $sClassName, $aMatches)) + { + $this->_Set('tag_class', $aMatches['class']); + $this->_Set('tag_attcode', $aMatches['attcode']); + } + } } \ No newline at end of file From 39f3972a2430f1278fc1ba7704e05b6dc00ee6c4 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 31 Aug 2018 18:02:22 +0200 Subject: [PATCH 018/113] =?UTF-8?q?N=C2=B0931:=20TagSet=20:=20OQL=20Parsin?= =?UTF-8?q?g=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/oql/oql-lexer.php | 38 +++++------ core/oql/oql-lexer.plex | 38 +++++------ test/core/OQLTest.php | 140 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 38 deletions(-) create mode 100644 test/core/OQLTest.php diff --git a/core/oql/oql-lexer.php b/core/oql/oql-lexer.php index c4649da7a8..36e5ce4ef1 100644 --- a/core/oql/oql-lexer.php +++ b/core/oql/oql-lexer.php @@ -672,25 +672,25 @@ class OQLLexer extends OQLLexerRaw function yylex() { - try - { - return parent::yylex(); - } - catch (Exception $e) - { - $sMessage = $e->getMessage(); - if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) - { - $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); - if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) - { - $iLine = $aMatches[1]; - $sUnexpected = $aMatches[2]; - throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); - } - } - // Default: forward the exception - throw $e; + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; } } } diff --git a/core/oql/oql-lexer.plex b/core/oql/oql-lexer.plex index 78fb13d74b..604586215c 100644 --- a/core/oql/oql-lexer.plex +++ b/core/oql/oql-lexer.plex @@ -423,25 +423,25 @@ class OQLLexer extends OQLLexerRaw function yylex() { - try - { - return parent::yylex(); - } - catch (Exception $e) - { - $sMessage = $e->getMessage(); - if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) - { - $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); - if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) - { - $iLine = $aMatches[1]; - $sUnexpected = $aMatches[2]; - throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); - } - } - // Default: forward the exception - throw $e; + try + { + return parent::yylex(); + } + catch (Exception $e) + { + $sMessage = $e->getMessage(); + if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE) + { + $sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE)); + if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches)) + { + $iLine = $aMatches[1]; + $sUnexpected = $aMatches[2]; + throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected); + } + } + // Default: forward the exception + throw $e; } } } diff --git a/test/core/OQLTest.php b/test/core/OQLTest.php new file mode 100644 index 0000000000..51cd5dd2a4 --- /dev/null +++ b/test/core/OQLTest.php @@ -0,0 +1,140 @@ +debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oQuery = $oOql->ParseQuery(); + static::assertInstanceOf('OqlQuery', $oQuery); + } + + public function GoodQueryProvider() + { + return array( + array('SELECT toto'), + array('SELECT toto WHERE toto.a = 1'), + array('SELECT toto WHERE toto.a = -1'), + array('SELECT toto WHERE toto.a = (1-1)'), + array('SELECT toto WHERE toto.a = (-1+3)'), + array('SELECT toto WHERE toto.a = (3+-1)'), + array('SELECT toto WHERE toto.a = (3--1)'), + array('SELECT toto WHERE toto.a = 0xC'), + array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''), + array('SELECT toto WHERE toto.a = :myparameter'), + array('SELECT toto WHERE toto.a IN (:param1)'), + array('SELECT toto WHERE toto.a IN (:param1, :param2)'), + array('SELECT toto WHERE toto.a=1'), + array('SELECT toto WHERE toto.a = "1"'), + array('SELECT toto WHERE toto.a & 1'), + array('SELECT toto WHERE toto.a | 1'), + array('SELECT toto WHERE toto.a ^ 1'), + array('SELECT toto WHERE toto.a << 1'), + array('SELECT toto WHERE toto.a >> 1'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'), + array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''), + array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\""'), + array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'), + array('SELECT toto WHERE toto.a NOT LIKE ""'), + array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'), + array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'), + array('SELECT Device JOIN Site ON Device.site = Site.id'), + array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''), + array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'), + array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'), + array('SELECT A FROM A'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'), + array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A UNION SELECT B'), + array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'), + array('SELECT A UNION SELECT B UNION SELECT C'), + array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT Ticket WHERE tagfield MATCHES \'salad\''), + ); + } + + /** + * @dataProvider BadQueryProvider + * + * @param $sQuery + * + * @throws \OQLException + * + * @expectedException \Exception + */ + public function testBadQuery($sQuery) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oOql->ParseQuery(); + static::fail(); + } + + public function BadQueryProvider() + { + return array( + array('SELECT toto WHERE toto.a = (3++1)'), + array('SELECT toto WHHHERE toto.a = "1"'), + array('SELECT toto WHERE toto.a == "1"'), + array('SELECT toto WHERE toto.a % 1'), + array('SELECT toto WHERE toto.a like \'arg\''), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""'), + array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\''), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\''), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3'), + //array('SELECT A WHERE A.a MATCHES toto'), + ); + } +} From 7d502fae23f5ae7d83ff383c86884920de99622e Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 3 Sep 2018 15:18:19 +0200 Subject: [PATCH 019/113] =?UTF-8?q?N=C2=B0931:=20TagSet=20:=20OQL=20Parse/?= =?UTF-8?q?Normalize=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/core/OQLTest.php | 359 ++++++++++++++++++++++++++++-------------- test/testlist.inc.php | 322 ------------------------------------- 2 files changed, 244 insertions(+), 437 deletions(-) diff --git a/test/core/OQLTest.php b/test/core/OQLTest.php index 51cd5dd2a4..6d1861967e 100644 --- a/test/core/OQLTest.php +++ b/test/core/OQLTest.php @@ -19,122 +19,251 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase; */ class OQLTest extends ItopDataTestCase { - /** - * @dataProvider GoodQueryProvider - * - * @param $sQuery - * - * @throws \OQLException - */ - public function testGoodQuery($sQuery) - { - $this->debug($sQuery); - $oOql = new \OqlInterpreter($sQuery); - $oQuery = $oOql->ParseQuery(); - static::assertInstanceOf('OqlQuery', $oQuery); - } + /** + * @dataProvider GoodQueryProvider + * + * @param $sQuery + * + * @throws \OQLException + */ + public function testGoodQueryParser($sQuery) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oQuery = $oOql->ParseQuery(); + static::assertInstanceOf('OqlQuery', $oQuery); + } - public function GoodQueryProvider() - { - return array( - array('SELECT toto'), - array('SELECT toto WHERE toto.a = 1'), - array('SELECT toto WHERE toto.a = -1'), - array('SELECT toto WHERE toto.a = (1-1)'), - array('SELECT toto WHERE toto.a = (-1+3)'), - array('SELECT toto WHERE toto.a = (3+-1)'), - array('SELECT toto WHERE toto.a = (3--1)'), - array('SELECT toto WHERE toto.a = 0xC'), - array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''), - array('SELECT toto WHERE toto.a = :myparameter'), - array('SELECT toto WHERE toto.a IN (:param1)'), - array('SELECT toto WHERE toto.a IN (:param1, :param2)'), - array('SELECT toto WHERE toto.a=1'), - array('SELECT toto WHERE toto.a = "1"'), - array('SELECT toto WHERE toto.a & 1'), - array('SELECT toto WHERE toto.a | 1'), - array('SELECT toto WHERE toto.a ^ 1'), - array('SELECT toto WHERE toto.a << 1'), - array('SELECT toto WHERE toto.a >> 1'), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'), - array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''), - array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''), - array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'), - array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''), - array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'), - array('SELECT toto WHERE toto.a NOT LIKE "\\""'), - array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'), - array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'), - array('SELECT toto WHERE toto.a NOT LIKE ""'), - array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'), - array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'), - array('SELECT Device JOIN Site ON Device.site = Site.id'), - array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), - array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''), - array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'), - array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'), - array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'), - array('SELECT A FROM A'), - array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'), - array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), - array('SELECT A UNION SELECT B'), - array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'), - array('SELECT A UNION SELECT B UNION SELECT C'), - array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), - array('SELECT Ticket WHERE tagfield MATCHES \'salad\''), - ); - } + public function GoodQueryProvider() + { + return array( + array('SELECT toto'), + array('SELECT toto WHERE toto.a = 1'), + array('SELECT toto WHERE toto.a = -1'), + array('SELECT toto WHERE toto.a = (1-1)'), + array('SELECT toto WHERE toto.a = (-1+3)'), + array('SELECT toto WHERE toto.a = (3+-1)'), + array('SELECT toto WHERE toto.a = (3--1)'), + array('SELECT toto WHERE toto.a = 0xC'), + array('SELECT toto WHERE toto.a = \'AXDVFS0xCZ32\''), + array('SELECT toto WHERE toto.a = :myparameter'), + array('SELECT toto WHERE toto.a IN (:param1)'), + array('SELECT toto WHERE toto.a IN (:param1, :param2)'), + array('SELECT toto WHERE toto.a=1'), + array('SELECT toto WHERE toto.a = "1"'), + array('SELECT toto WHERE toto.a & 1'), + array('SELECT toto WHERE toto.a | 1'), + array('SELECT toto WHERE toto.a ^ 1'), + array('SELECT toto WHERE toto.a << 1'), + array('SELECT toto WHERE toto.a >> 1'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s it"'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s \\"it\\""'), + array('SELECT toto WHERE toto.a NOT LIKE \'That"s it\''), + array('SELECT toto WHERE toto.a NOT LIKE \'That\\\'s it\''), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\\\ truc"'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\\\ truc\''), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\""'), + array('SELECT toto WHERE toto.a NOT LIKE "\\"\\\\"'), + array('SELECT toto WHERE toto.a NOT LIKE "\\\\\\""'), + array('SELECT toto WHERE toto.a NOT LIKE ""'), + array('SELECT toto WHERE toto.a NOT LIKE "blah" AND toto.b LIKE "foo"'), + array('SELECT toto WHERE toto.a = 1 AND toto.b LIKE "x" AND toto.f >= 12345'), + array('SELECT Device JOIN Site ON Device.site = Site.id'), + array('SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT UserRightsMatrixClassGrant WHERE UserRightsMatrixClassGrant.class = \'lnkContactRealObject\' AND UserRightsMatrixClassGrant.action = \'modify\' AND UserRightsMatrixClassGrant.login = \'Denis\''), + array('SELECT A WHERE A.col1 = \'lit1\' AND A.col2 = \'lit2\' AND A.col3 = \'lit3\''), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = 123 AND B.col1 = \'aa\') OR (A.col3 = \'zzz\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 = B.col2 AND B.col1 = A.col2) OR (A.col3 = \'\' AND B.col4 > 100)'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + B.col2 * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 + (B.col2 * B.col1) = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2'), + array('SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 & B.col2) = A.col2'), + array('SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"'), + array('SELECT A FROM A'), + array('SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A, B,C FROM A JOIN B ON A.myB = B.id'), + array('SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB BELOW B.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON B.myA BELOW A.id WHERE A.col1 = 2'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW STRICT B.id WHERE A.col1 = 2 AND B.id = 3'), + array('SELECT A UNION SELECT B'), + array('SELECT A WHERE A.b = "sdf" UNION SELECT B WHERE B.a = "sfde"'), + array('SELECT A UNION SELECT B UNION SELECT C'), + array('SELECT A UNION SELECT B UNION SELECT C UNION SELECT D'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id NOT BELOW B.id WHERE A.col1 = 2 AND B.id = 3 UNION SELECT Device JOIN Site ON Device.site = Site.id JOIN Country ON Site.location = Country.id'), + array('SELECT Person AS B WHERE B.name LIKE \'%A%\''), + array('SELECT Server WHERE name REGEXP \'dbserver[0-9]+\''), + array('SELECT Server WHERE name REGEXP \'^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$\''), + array('SELECT Change AS ch WHERE ch.start_date >= \'2009-12-31\' AND ch.end_date <= \'2010-01-01\''), + array('SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON(\'10.22.32.224\') AND INET_ATON(dev.managementip) < INET_ATON(\'10.22.32.255\')'), + array('SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1'), + array('SELECT PhysicalInterface AS if JOIN DatacenterDevice AS dev ON if.connectableci_id = dev.id WHERE dev.status = \'production\' AND dev.organization_name = \'Demo\''), + array('SELECT Ticket AS t WHERE t.agent_id = :current_contact_id'), + array('SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != \'closed\''), + array('SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)'), + array('SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = \'new\''), + array('SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)'), + array('SELECT Ticket WHERE tagfield MATCHES \'salad\''), + ); + } - /** - * @dataProvider BadQueryProvider - * - * @param $sQuery - * - * @throws \OQLException - * - * @expectedException \Exception - */ - public function testBadQuery($sQuery) - { - $this->debug($sQuery); - $oOql = new \OqlInterpreter($sQuery); - $oOql->ParseQuery(); - static::fail(); - } + /** + * @dataProvider BadQueryProvider + * + * @param $sQuery + * @param $sExpectedExceptionClass + * + */ + public function testBadQueryParser($sQuery, $sExpectedExceptionClass) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $sExceptionClass = ''; + try + { + $oOql->ParseQuery(); + } + catch (\Exception $e) + { + $sExceptionClass = get_class($e); + } - public function BadQueryProvider() - { - return array( - array('SELECT toto WHERE toto.a = (3++1)'), - array('SELECT toto WHHHERE toto.a = "1"'), - array('SELECT toto WHERE toto.a == "1"'), - array('SELECT toto WHERE toto.a % 1'), - array('SELECT toto WHERE toto.a like \'arg\''), - array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""'), - array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\''), - array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"'), - array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\''), - array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3'), - //array('SELECT A WHERE A.a MATCHES toto'), - ); - } + static::assertEquals($sExpectedExceptionClass, $sExceptionClass); + } + + public function BadQueryProvider() + { + return array( + array('SELECT toto WHERE toto.a = (3++1)', 'OQLParserException'), + array('SELECT toto WHHHERE toto.a = "1"', 'OQLParserException'), + array('SELECT toto WHERE toto.a == "1"', 'OQLParserException'), + array('SELECT toto WHERE toto.a % 1', 'Exception'), + array('SELECT toto WHERE toto.a like \'arg\'', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE "That\'s "it""', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE \'That\'s it\'', 'OQLParserException'), + array('SELECT toto WHERE toto.a NOT LIKE "blah \\ truc"', 'Exception'), + array('SELECT toto WHERE toto.a NOT LIKE \'blah \\ truc\'', 'Exception'), + array('SELECT A JOIN B ON A.myB = B.id JOIN C ON C.parent_id = B.id WHERE A.col1 BELOW 2 AND B.id = 3', 'OQLParserException'), + ); + } + + /** + * @dataProvider TypeErrorQueryProvider + * + * @param $sQuery + * + * @expectedException \TypeError + * + * @throws \OQLException + */ + public function testTypeErrorQueryParser($sQuery) + { + $this->debug($sQuery); + $oOql = new \OqlInterpreter($sQuery); + $oOql->ParseQuery(); + } + + public function TypeErrorQueryProvider() + { + return array( + array('SELECT A WHERE A.a MATCHES toto'), + ); + } + + + /** + * Needs actual datamodel + * + * @dataProvider QueryNormalizationProvider + * + * @param $sQuery + * @param $sExpectedExceptionClass + * + */ + public function testQueryNormalization($sQuery, $sExpectedExceptionClass) + { + $this->debug($sQuery); + $sExceptionClass = ''; + try + { + $oSearch = \DBObjectSearch::FromOQL($sQuery); + static::assertInstanceOf('DBObjectSearch', $oSearch); + } + catch (\Exception $e) + { + $sExceptionClass = get_class($e); + } + + static::assertEquals($sExpectedExceptionClass, $sExceptionClass); + } + + + public function QueryNormalizationProvider() + { + return array( + array('SELECT Contact', ''), + array('SELECT Contact WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE name = "foo"', ''), + array('SELECT Contact AS c WHERE nom_de_famille = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE c.name = "foo"', ''), + array('SELECT Contact AS c WHERE Contact.name = "foo"', 'OqlNormalizeException'), + array('SELECT Contact AS c WHERE x.name = "foo"', 'OqlNormalizeException'), + + array('SELECT Organization AS child JOIN Organization AS root ON child.parent_id BELOW root.id', ''), + array('SELECT Organization AS root JOIN Organization AS child ON child.parent_id BELOW root.id', ''), + + array('SELECT RelationProfessionnelle', 'UnknownClassOqlException'), + array('SELECT RelationProfessionnelle AS c WHERE name = "foo"', 'UnknownClassOqlException'), + + // The first query is the base query altered only in one place in the subsequent queries + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', ''), + array('SELECT Person AS p JOIN lnkXXXXXXXXXXXX AS lnk ON lnk.person_id = p.id WHERE p.name LIKE "foo"', 'UnknownClassOqlException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON person_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.role = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.team_id = p.id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id BELOW p.id WHERE p.name LIKE "bar"', ''), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.org_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), // inverted the JOIN spec + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE name LIKE "foo"', ''), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE x.name LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE p.eman LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE eman LIKE "foo"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON lnk.person_id = p.id WHERE id = 1', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN lnkPersonToTeam AS lnk ON p.id = lnk.person_id WHERE p.name LIKE "foo"', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', ''), + array('SELECT Person AS p JOIN Organization AS o ON p.location_id = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), + array('SELECT Person AS p JOIN Organization AS o ON p.name = o.id WHERE p.name LIKE "foo" AND o.name LIKE "land"', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Organization AS o ON p.org_id = o.id JOIN Person AS p ON p.org_id = o.id', 'OqlNormalizeException'), + array('SELECT Person JOIN Organization AS o ON Person.org_id = o.id JOIN Person ON Person.org_id = o.id', 'OqlNormalizeException'), + + array('SELECT Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT Person AS p JOIN Location AS l ON p.location_id BELOW l.id', 'OqlNormalizeException'), + + array('SELECT Person FROM Person JOIN Location ON Person.location_id = Location.id', ''), + array('SELECT p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT l, p FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT p, l FROM Person AS p JOIN Location AS l ON p.location_id = l.id', ''), + array('SELECT foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), + array('SELECT p, foo FROM Person AS p JOIN Location AS l ON p.location_id = l.id', 'OqlNormalizeException'), + + // Joins based on AttributeObjectKey + // + array('SELECT Attachment AS a JOIN UserRequest AS r ON a.item_id = r.id', ''), + array('SELECT UserRequest AS r JOIN Attachment AS a ON a.item_id = r.id', ''), + ); + } } diff --git a/test/testlist.inc.php b/test/testlist.inc.php index a3bb15d2fe..88519843a0 100644 --- a/test/testlist.inc.php +++ b/test/testlist.inc.php @@ -123,328 +123,6 @@ class TestSQLQuery extends TestScenarioOnDB } } -class TestOQLParser extends TestFunction -{ - static public function GetName() {return 'Check OQL parsing';} - static public function GetDescription() {return 'Attempts a series of queries, and in particular those with a bad syntax';} - - protected function CheckQuery($sQuery, $bIsCorrectQuery) - { - $oOql = new OqlInterpreter($sQuery); - try - { - $oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery - self::DumpVariable($oTrash); - } - catch (OQLException $OqlException) - { - if ($bIsCorrectQuery) - { - echo "

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

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

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

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

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

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

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

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

Testing query: $sQuery ($sIsOk)

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

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

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

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

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

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

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

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

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

Testing query: $sQuery ($sIsOk)

\n"; - try - { - $bRet = $this->TestQuery($sQuery, $bIsCorrectQuery); - } - catch(Exception $e) - { - $this->m_aErrors[] = $e->getMessage(); - $bRet = false; - } - if (!$bRet) $iErrors++; - } - - return ($iErrors == 0); - } -} - class TestCSVParser extends TestFunction { static public function GetName() {return 'Check CSV parsing';} From 98e5eaa4e01a72b7195deaeba95c94a7e226436e Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 4 Sep 2018 11:06:53 +0200 Subject: [PATCH 020/113] =?UTF-8?q?N=C2=B0931:=20Add=20Delta=20management?= =?UTF-8?q?=20in=20ormTagSet=20(with=20unit=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/attributedef.class.inc.php | 18160 +++++++++++++++--------------- core/ormtagset.class.inc.php | 123 +- test/core/ormTagSetTest.php | 39 + 3 files changed, 9230 insertions(+), 9092 deletions(-) diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index 75103971b3..252c5b8614 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1,9080 +1,9080 @@ - - - -/** - * Typology for the attributes - * - * @copyright Copyright (C) 2010-2018 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 - */ - - -require_once('MyHelpers.class.inc.php'); -require_once('ormdocument.class.inc.php'); -require_once('ormstopwatch.class.inc.php'); -require_once('ormpassword.class.inc.php'); -require_once('ormcaselog.class.inc.php'); -require_once('ormlinkset.class.inc.php'); -require_once('ormtagset.class.inc.php'); -require_once('htmlsanitizer.class.inc.php'); -require_once(APPROOT.'sources/autoload.php'); -require_once('customfieldshandler.class.inc.php'); -require_once('ormcustomfieldsvalue.class.inc.php'); -require_once('datetimeformat.class.inc.php'); -// This should be changed to a use when we go full-namespace -require_once(APPROOT . 'sources/form/validator/validator.class.inc.php'); -require_once(APPROOT . 'sources/form/validator/notemptyextkeyvalidator.class.inc.php'); - -/** - * MissingColumnException - sent if an attribute is being created but the column is missing in the row - * - * @package iTopORM - */ -class MissingColumnException extends Exception -{} - -/** - * add some description here... - * - * @package iTopORM - */ -define('EXTKEY_RELATIVE', 1); - -/** - * add some description here... - * - * @package iTopORM - */ -define('EXTKEY_ABSOLUTE', 2); - -/** - * Propagation of the deletion through an external key - ask the user to delete the referencing object - * - * @package iTopORM - */ -define('DEL_MANUAL', 1); - -/** - * Propagation of the deletion through an external key - ask the user to delete the referencing object - * - * @package iTopORM - */ -define('DEL_AUTO', 2); -/** - * Fully silent delete... not yet implemented - */ -define('DEL_SILENT', 2); -/** - * For HierarchicalKeys only: move all the children up one level automatically - */ -define('DEL_MOVEUP', 3); - - -/** - * For Link sets: tracking_level - * - * @package iTopORM - */ -define('ATTRIBUTE_TRACKING_NONE', 0); // Do not track changes of the attribute -define('ATTRIBUTE_TRACKING_ALL', 3); // Do track all changes of the attribute -define('LINKSET_TRACKING_NONE', 0); // Do not track changes in the link set -define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items -define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items -define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items - -define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from inside this object -define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object -define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu -define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place -define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place - - -/** - * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) - * - * @package iTopORM - */ -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; - - const INDEX_LENGTH = 95; - - public function GetType() - { - return Dict::S('Core:'.get_class($this)); - } - public function GetTypeDesc() - { - return Dict::S('Core:'.get_class($this).'+'); - } - - 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!'; - public function Get($sParamName) {return $this->m_aParams[$sParamName];} - - public function GetIndexLength() { - $iMaxLength = $this->GetMaxSize(); - if (is_null($iMaxLength)) - { - return null; - } - if ($iMaxLength > static::INDEX_LENGTH) - { - return static::INDEX_LENGTH; - } - return $iMaxLength; - } - - public function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} - - protected function GetOptional($sParamName, $default) - { - if (array_key_exists($sParamName, $this->m_aParams)) - { - return $this->m_aParams[$sParamName]; - } - else - { - return $default; - } - } - - /** - * AttributeDefinition constructor. - * - * @param string $sCode - * @param array $aParams - * - * @throws \Exception - */ - public function __construct($sCode, $aParams) - { - $this->m_sCode = $sCode; - $this->m_aParams = $aParams; - $this->ConsistencyCheck(); - } - - public function GetParams() - { - return $this->m_aParams; - } - - public function HasParam($sParam) - { - return array_key_exists($sParam, $this->m_aParams); - } - - public function SetHostClass($sHostClass) - { - $this->m_sHostClass = $sHostClass; - } - public function GetHostClass() - { - return $this->m_sHostClass; - } - - /** - * @return array - * - * @throws \CoreException - */ - public function ListSubItems() - { - $aSubItems = array(); - foreach(MetaModel::ListAttributeDefs($this->m_sHostClass) as $sAttCode => $oAttDef) - { - if ($oAttDef instanceof AttributeSubItem) - { - if ($oAttDef->Get('target_attcode') == $this->m_sCode) - { - $aSubItems[$sAttCode] = $oAttDef; - } - } - } - return $aSubItems; - } - - // Note: I could factorize this code with the parameter management made for the AttributeDef class - // to be overloaded - static public function ListExpectedParams() - { - return array(); - } - - /** - * @throws \Exception - */ - private function ConsistencyCheck() - { - // Check that any mandatory param has been specified - // - $aExpectedParams = $this->ListExpectedParams(); - foreach($aExpectedParams as $sParamName) - { - if (!array_key_exists($sParamName, $this->m_aParams)) - { - $aBacktrace = debug_backtrace(); - $sTargetClass = $aBacktrace[2]["class"]; - $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"]; - throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)"); - } - } - } - - /** - * Check the validity of the given value - * - * @param \DBObject $oHostObject - * @param $value Object error if any, null otherwise - * - * @return bool - */ - public function CheckValue(DBObject $oHostObject, $value) - { - // later: factorize here the cases implemented into DBObject - return true; - } - - // table, key field, name field - - /** - * @return string - * @deprecated never used - */ - public function ListDBJoins() - { - return ""; - // e.g: return array("Site", "infrid", "name"); - } - - public function GetFinalAttDef() - { - return $this; - } - - /** - * Deprecated - use IsBasedOnDBColumns instead - * @return bool - */ - public function IsDirectField() {return static::IsBasedOnDBColumns();} - - /** - * Returns true if the attribute value is built after DB columns - * @return bool - */ - static public function IsBasedOnDBColumns() {return false;} - /** - * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) - * @return bool - */ - static public function IsBasedOnOQLExpression() {return false;} - /** - * Returns true if the attribute value can be shown as a string - * @return bool - */ - static public function IsScalar() {return false;} - /** - * Returns true if the attribute value is a set of related objects (1-N or N-N) - * @return bool - */ - static public function IsLinkSet() {return false;} - - /** - * @param int $iType - * - * @return bool true if the attribute is an external key, either directly (RELATIVE to the host class), or indirectly (ABSOLUTELY) - */ - public function IsExternalKey($iType = EXTKEY_RELATIVE) - { - return false; - } - /** - * @return bool true if the attribute value is an external key, pointing to the host class - */ - static public function IsHierarchicalKey() {return false;} - /** - * @return bool true if the attribute value is stored on an object pointed to be an external key - */ - static public function IsExternalField() {return false;} - /** - * @return bool true if the attribute can be written (by essence : metamodel field option) - * @see \DBObject::IsAttributeReadOnlyForCurrentState() for a specific object instance (depending on its workflow) - */ - public function IsWritable() {return false;} - /** - * @return bool true if the attribute has been added automatically by the framework - */ - public function IsMagic() {return $this->GetOptional('magic', false);} - /** - * @return bool true if the attribute value is kept in the loaded object (in memory) - */ - static public function LoadInObject() {return true;} - /** - * @return bool true if the attribute value comes from the database in one way or another - */ - static public function LoadFromDB() {return true;} - /** - * @return bool true if the attribute should be loaded anytime (in addition to the column selected by the user) - */ - public function AlwaysLoadInTables() {return $this->GetOptional('always_load_in_tables', false);} - - /** - * @param \DBObject $oHostObject - * - * @return mixed Must return the value if LoadInObject returns false - */ - public function GetValue($oHostObject) - { - return null; - } - - /** - * Returns true if the attribute must not be stored if its current value is "null" (Cf. IsNull()) - * @return bool - */ - public function IsNullAllowed() {return true;} - /** - * Returns the attribute code (identifies the attribute in the host class) - * @return string - */ - public function GetCode() {return $this->m_sCode;} - - /** - * Find the corresponding "link" attribute on the target class, if any - * @return null | AttributeDefinition - */ - public function GetMirrorLinkAttribute() {return null;} - - /** - * Helper to browse the hierarchy of classes, searching for a label - * - * @param string $sDictEntrySuffix - * @param string $sDefault - * @param bool $bUserLanguageOnly - * - * @return string - * @throws \Exception - */ - protected function SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly) - { - $sLabel = Dict::S('Class:'.$this->m_sHostClass.$sDictEntrySuffix, '', $bUserLanguageOnly); - if (strlen($sLabel) == 0) - { - // Nothing found: go higher in the hierarchy (if possible) - // - $sLabel = $sDefault; - $sParentClass = MetaModel::GetParentClass($this->m_sHostClass); - if ($sParentClass) - { - if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) - { - $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode); - $sLabel = $oAttDef->SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly); - } - } - } - return $sLabel; - } - - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetLabel($sDefault = null) - { - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, null, true /*user lang*/); - if (is_null($sLabel)) - { - // If no default value is specified, let's define the most relevant one for developping purposes - if (is_null($sDefault)) - { - $sDefault = str_replace('_', ' ', $this->m_sCode); - } - // Browse the hierarchy again, accepting default (english) translations - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, $sDefault, false); - } - return $sLabel; - } - - /** - * To be overloaded for localized enums - * - * @param string $sValue - * - * @return string label corresponding to the given value (in plain text) - */ - public function GetValueLabel($sValue) - { - return $sValue; - } - - /** - * Get the value from a given string (plain text, CSV import) - * - * @param string $sProposedValue - * @param bool $bLocalizedValue - * @param string $sSepItem - * @param string $sSepAttribute - * @param string $sSepValue - * @param string $sAttributeQualifier - * - * @return mixed null if no match could be found - */ - public function MakeValueFromString( - $sProposedValue, - $bLocalizedValue = false, - $sSepItem = null, - $sSepAttribute = null, - $sSepValue = null, - $sAttributeQualifier = null - ) - { - return $this->MakeRealValue($sProposedValue, null); - } - - /** - * Parses a search string coming from user input - * @param string $sSearchString - * @return string - */ - public function ParseSearchString($sSearchString) - { - return $sSearchString; - } - - /** - * @return string - * - * @throws \Exception - */ - public function GetLabel_Obsolete() - { - // Written for compatibility with a data model written prior to version 0.9.1 - if (array_key_exists('label', $this->m_aParams)) - { - return $this->m_aParams['label']; - } - else - { - return $this->GetLabel(); - } - } - - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetDescription($sDefault = null) - { - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', null, true /*user lang*/); - if (is_null($sLabel)) - { - // If no default value is specified, let's define the most relevant one for developping purposes - if (is_null($sDefault)) - { - $sDefault = ''; - } - // Browse the hierarchy again, accepting default (english) translations - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', $sDefault, false); - } - return $sLabel; - } - - /** - * @param string|null $sDefault - * - * @return string - * - * @throws \Exception - */ - public function GetHelpOnEdition($sDefault = null) - { - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', null, true /*user lang*/); - if (is_null($sLabel)) - { - // If no default value is specified, let's define the most relevant one for developping purposes - if (is_null($sDefault)) - { - $sDefault = ''; - } - // Browse the hierarchy again, accepting default (english) translations - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', $sDefault, false); - } - return $sLabel; - } - - public function GetHelpOnSmartSearch() - { - $aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this)); - foreach ($aParents as $sClass) - { - $sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-'); - if ($sHelp != '-missing-') - { - return $sHelp; - } - } - return ''; - } - - /** - * @return string - * - * @throws \Exception - */ - public function GetDescription_Obsolete() - { - // Written for compatibility with a data model written prior to version 0.9.1 - if (array_key_exists('description', $this->m_aParams)) - { - return $this->m_aParams['description']; - } - else - { - return $this->GetDescription(); - } - } - - public function GetTrackingLevel() - { - return $this->GetOptional('tracking_level', ATTRIBUTE_TRACKING_ALL); - } - - /** - * @return \ValueSetObjects - */ - public function GetValuesDef() {return null;} - - public function GetPrerequisiteAttributes($sClass = null) - { - return array(); - } - - public function GetNullValue() {return null;} - - public function IsNull($proposedValue) - { - return is_null($proposedValue); - } - - /** - * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! - * - * @param $proposedValue - * @param $oHostObj - * - * @return mixed - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - return $proposedValue; - } - - public function Equals($val1, $val2) {return ($val1 == $val2);} - - /** - * @param string $sPrefix - * - * @return array suffix/expression pairs (1 in most of the cases), for READING (Select) - */ - public function GetSQLExpressions($sPrefix = '') - { - return array(); - } - - /** - * @param array $aCols - * @param string $sPrefix - * - * @return mixed a value out of suffix/value pairs, for SELECT result interpretation - */ - public function FromSQLToValue($aCols, $sPrefix = '') - { - return null; - } - - /** - * @param bool $bFullSpec - * - * @return array column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) - * @see \CMDBSource::GetFieldSpec() - */ - public function GetSQLColumns($bFullSpec = false) - { - return array(); - } - - /** - * @param $value - * - * @return array column/value pairs (1 in most of the cases), for WRITING (Insert, Update) - */ - public function GetSQLValues($value) - { - return array(); - } - public function RequiresIndex() {return false;} - - public function RequiresFullTextIndex() - { - return false; - } - public function CopyOnAllTables() {return false;} - - public function GetOrderBySQLExpressions($sClassAlias) - { - // Note: This is the responsibility of this function to place backticks around column aliases - return array('`'.$sClassAlias.$this->GetCode().'`'); - } - - public function GetOrderByHint() - { - return ''; - } - - // Import - differs slightly from SQL input, but identical in most cases - // - public function GetImportColumns() {return $this->GetSQLColumns();} - public function FromImportToValue($aCols, $sPrefix = '') - { - $aValues = array(); - foreach ($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr) - { - // This is working, based on the assumption that importable fields - // are not computed fields => the expression is the name of a column - $aValues[$sPrefix.$sAlias] = $aCols[$sExpr]; - } - return $this->FromSQLToValue($aValues, $sPrefix); - } - - public function GetValidationPattern() - { - return ''; - } - - public function CheckFormat($value) - { - return true; - } - - public function GetMaxSize() - { - return null; - } - - /** - * @return mixed|null - * @deprecated never used - */ - public function MakeValue() - { - $sComputeFunc = $this->Get("compute_func"); - if (empty($sComputeFunc)) return null; - - return call_user_func($sComputeFunc); - } - - abstract public function GetDefaultValue(DBObject $oHostObject = null); - - // - // To be overloaded in subclasses - // - - abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description" - abstract public function GetBasicFilterLooseOperator(); // returns an "opCode" - //abstract protected GetBasicFilterHTMLInput(); - abstract public function GetBasicFilterSQLExpr($sOpCode, $value); - - public function GetFilterDefinitions() - { - return array(); - } - - public function GetEditValue($sValue, $oHostObj = null) - { - return (string)$sValue; - } - - /** - * For fields containing a potential markup, return the value without this markup - * - * @param string $sValue - * @param \DBObject $oHostObj - * - * @return string - */ - public function GetAsPlainText($sValue, $oHostObj = null) - { - return (string) $this->GetEditValue($sValue, $oHostObj); - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param $value - * - * @return string - */ - public function GetForJSON($value) - { - // In most of the cases, that will be the expected behavior... - return $this->GetEditValue($value); - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - * - * @param $json - * - * @return mixed - */ - public function FromJSONToValue($json) - { - // Passthrough in most of the cases - return $json; - } - - /** - * Override to display the value in the GUI - * - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - */ - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - return Str::pure2html((string)$sValue); - } - - /** - * Override to export the value in XML - * - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return mixed - */ - public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) - { - return Str::pure2xml((string)$sValue); - } - - /** - * Override to escape the value when read by DBObject::GetAsCSV() - * - * @param string $sValue - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return string - */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - return (string)$sValue; - } - - /** - * Override to differentiate a value displayed in the UI or in the history - * - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - */ - public function GetAsHTMLForHistory($sValue, $oHostObject = null, $bLocalize = true) - { - return $this->GetAsHTML($sValue, $oHostObject, $bLocalize); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\StringField'; - } - - /** - * Override to specify Field class - * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is - * passed, MakeFormField behave more like a Prepare. - * - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\Field $oFormField - * - * @return null - * @throws \CoreException - * @throws \Exception - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - // This is a fallback in case the AttributeDefinition subclass has no overloading of this function. - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - //$oFormField->SetReadOnly(true); - } - - $oFormField->SetLabel($this->GetLabel()); - - // Attributes flags - // - Retrieving flags for the current object - if ($oObject->IsNew()) - { - $iFlags = $oObject->GetInitialStateAttributeFlags($this->GetCode()); - } - else - { - $iFlags = $oObject->GetAttributeFlags($this->GetCode()); - } - - // - Comparing flags - if ($this->IsWritable() && (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY))) - { - $oFormField->SetMandatory(true); - } - if ((!$oObject->IsNew() || !$oFormField->GetMandatory()) && (($iFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY)) - { - $oFormField->SetReadOnly(true); - } - - // CurrentValue - $oFormField->SetCurrentValue($oObject->Get($this->GetCode())); - - // Validation pattern - if ($this->GetValidationPattern() !== '') - { - $oFormField->AddValidator(new \Combodo\iTop\Form\Validator\Validator($this->GetValidationPattern())); - } - - return $oFormField; - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Plain text (unlocalized) representation', - 'html' => 'HTML representation', - 'label' => 'Localized representation', - 'text' => 'Plain text representation (without any markup)', - ); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param mixed $value The current value of the field - * @param string $sVerb The verb specifying the representation of the value - * @param \DBObject $oHostObject - * @param bool $bLocalize Whether or not to localize the value - * - * @return mixed|null|string - * - * @throws \Exception - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - if ($this->IsScalar()) - { - switch ($sVerb) - { - case '': - return $value; - - case 'html': - return $this->GetAsHtml($value, $oHostObject, $bLocalize); - - case 'label': - return $this->GetEditValue($value); - - case 'text': - return $this->GetAsPlainText($value); - break; - - default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - } - return null; - } - - /** - * @param array $aArgs - * @param string $sContains - * - * @return array|null - * @throws \CoreException - * @throws \OQLException - */ - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - $oValSetDef = $this->GetValuesDef(); - if (!$oValSetDef) return null; - return $oValSetDef->GetValues($aArgs, $sContains); - } - - /** - * Explain the change of the attribute (history) - * - * @param string $sOldValue - * @param string $sNewValue - * @param string $sLabel - * - * @return string - * @throws \ArchivedObjectException - * @throws \CoreException - * @throws \DictExceptionMissingString - * @throws \OQLException - * @throws \Exception - */ - public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null) - { - if (is_null($sLabel)) - { - $sLabel = $this->GetLabel(); - } - - $sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue); - $sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue); - - if($this->IsExternalKey()) - { - /** @var \AttributeExternalKey $this */ - $sTargetClass = $this->GetTargetClass(); - $sOldValueHtml = (int)$sOldValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sOldValue) : null; - $sNewValueHtml = (int)$sNewValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sNewValue) : null; - } - if ( (($this->GetType() == 'String') || ($this->GetType() == 'Text')) && - (strlen($sNewValue) > strlen($sOldValue)) ) - { - // Check if some text was not appended to the field - if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end - { - $sDelta = $this->GetAsHTML(substr($sNewValue, strlen($sOldValue))); - $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); - } - else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning - { - $sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue))); - $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); - } - else - { - if (strlen($sOldValue) == 0) - { - $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); - } - else - { - if (is_null($sNewValue)) - { - $sNewValueHtml = Dict::S('UI:UndefinedObject'); - } - $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); - } - } - } - else - { - if (strlen($sOldValue) == 0) - { - $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); - } - else - { - if (is_null($sNewValue)) - { - $sNewValueHtml = Dict::S('UI:UndefinedObject'); - } - $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); - } - } - return $sResult; - } - - - /** - * Parses a string to find some smart search patterns and build the corresponding search/OQL condition - * Each derived class is reponsible for defining and processing their own smart patterns, the base class - * does nothing special, and just calls the default (loose) operator - * - * @param string $sSearchText The search string to analyze for smart patterns - * @param \FieldExpression $oField - * @param array $aParams Values of the query parameters - * - * @return \Expression The search condition to be added (AND) to the current search - * - * @throws \CoreException - */ - public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams) - { - $sParamName = $oField->GetParent().'_'.$oField->GetName(); - $oRightExpr = new VariableExpression($sParamName); - $sOperator = $this->GetBasicFilterLooseOperator(); - switch ($sOperator) - { - case 'Contains': - $aParams[$sParamName] = "%$sSearchText%"; - $sSQLOperator = 'LIKE'; - break; - - default: - $sSQLOperator = $sOperator; - $aParams[$sParamName] = $sSearchText; - } - $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); - return $oNewCondition; - } - - /** - * Tells if an attribute is part of the unique fingerprint of the object (used for comparing two objects) - * All attributes which value is not based on a value from the object itself (like ExternalFields or LinkedSet) - * must be excluded from the object's signature - * @return boolean - */ - public function IsPartOfFingerprint() - { - return true; - } - - /** - * The part of the current attribute in the object's signature, for the supplied value - * @param mixed $value The value of this attribute for the object - * @return string The "signature" for this field/attribute - */ - public function Fingerprint($value) - { - return (string)$value; - } -} - -/** - * Set of objects directly linked to an object, and being part of its definition - * - * @package iTopORM - */ -class AttributeLinkedSet extends AttributeDefinition -{ - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); - } - - public function GetEditClass() {return "LinkedSet";} - - public function IsWritable() {return true;} - static public function IsLinkSet() {return true;} - public function IsIndirect() {return false;} - - public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} - - /** - * @param \DBObject|null $oHostObject - * - * @return \ormLinkSet - * - * @throws \Exception - * @throws \CoreException - * @throws \CoreWarning - */ - public function GetDefaultValue(DBObject $oHostObject = null) - { - $sLinkClass = $this->GetLinkedClass(); - $sExtKeyToMe = $this->GetExtKeyToMe(); - - // The class to target is not the current class, because if this is a derived class, - // it may differ from the target class, then things start to become confusing - /** @var \AttributeExternalKey $oRemoteExtKeyAtt */ - $oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe); - $sMyClass = $oRemoteExtKeyAtt->GetTargetClass(); - - $oMyselfSearch = new DBObjectSearch($sMyClass); - if ($oHostObject !== null) - { - $oMyselfSearch->AddCondition('id', $oHostObject->GetKey(), '='); - } - - $oLinkSearch = new DBObjectSearch($sLinkClass); - $oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe); - if ($this->IsIndirect()) - { - // Join the remote class so that the archive flag will be taken into account - /** @var \AttributeLinkedSetIndirect $this */ - $sExtKeyToRemote = $this->GetExtKeyToRemote(); - /** @var \AttributeExternalKey $oExtKeyToRemote */ - $oExtKeyToRemote = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToRemote); - $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); - if (MetaModel::IsArchivable($sRemoteClass)) - { - $oRemoteSearch = new DBObjectSearch($sRemoteClass); - /** @var \AttributeLinkedSetIndirect $this */ - $oLinkSearch->AddCondition_PointingTo($oRemoteSearch, $this->GetExtKeyToRemote()); - } - } - $oLinks = new DBObjectSet($oLinkSearch); - $oLinkSet = new ormLinkSet($this->GetHostClass(), $this->GetCode(), $oLinks); - return $oLinkSet; - } - - public function GetTrackingLevel() - { - return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default')); - } - - public function GetEditMode() - { - return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS); - } - - public function GetLinkedClass() {return $this->Get('linked_class');} - public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} - - public function GetBasicFilterOperators() {return array();} - public function GetBasicFilterLooseOperator() {return '';} - public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} - - /** - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string|null - * - * @throws \CoreException - */ - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (is_object($sValue) && ($sValue instanceof ormLinkSet)) - { - $sValue->Rewind(); - $aItems = array(); - while ($oObj = $sValue->Fetch()) - { - // Show only relevant information (hide the external key to the current object) - $aAttributes = array(); - foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef) - { - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; - $sAttValue = $oObj->GetAsHTML($sAttCode); - if (strlen($sAttValue) > 0) - { - $aAttributes[] = $sAttValue; - } - } - $sAttributes = implode(', ', $aAttributes); - $aItems[] = $sAttributes; - } - return implode('
', $aItems); - } - return null; - } - - /** - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - * - * @throws \CoreException - */ - public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) - { - if (is_object($sValue) && ($sValue instanceof ormLinkSet)) - { - $sValue->Rewind(); - $sRes = "\n"; - while ($oObj = $sValue->Fetch()) - { - $sObjClass = get_class($oObj); - $sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n"; - // Show only relevant information (hide the external key to the current object) - foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) - { - if ($sAttCode == 'finalclass') - { - if ($sObjClass == $this->GetLinkedClass()) - { - // Simplify the output if the exact class could be determined implicitely - continue; - } - } - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) - { - /** @var \AttributeExternalField $oAttDef */ - if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe()) continue; - /** @var AttributeExternalField $oAttDef */ - if ($oAttDef->IsFriendlyName()) continue; - } - if ($oAttDef instanceof AttributeFriendlyName) continue; - if (!$oAttDef->IsScalar()) continue; - $sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize); - $sRes .= "<$sAttCode>$sAttValue\n"; - } - $sRes .= "\n"; - } - $sRes .= "\n"; - } - else - { - $sRes = ''; - } - return $sRes; - } - - /** - * @param $sValue - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return mixed|string - * @throws \CoreException - */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); - $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); - $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); - $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier'); - - if (is_object($sValue) && ($sValue instanceof ormLinkSet)) - { - $sValue->Rewind(); - $aItems = array(); - while ($oObj = $sValue->Fetch()) - { - $sObjClass = get_class($oObj); - // Show only relevant information (hide the external key to the current object) - $aAttributes = array(); - foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) - { - if ($sAttCode == 'finalclass') - { - if ($sObjClass == $this->GetLinkedClass()) - { - // Simplify the output if the exact class could be determined implicitely - continue; - } - } - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; - if (!$oAttDef->IsBasedOnDBColumns()) continue; - if (!$oAttDef->IsScalar()) continue; - $sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize); - if (strlen($sAttValue) > 0) - { - $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, $sAttCode.$sSepValue.$sAttValue); - $aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier; - } - } - $sAttributes = implode($sSepAttribute, $aAttributes); - $aItems[] = $sAttributes; - } - $sRes = implode($sSepItem, $aItems); - } - else - { - $sRes = ''; - } - $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); - $sRes = $sTextQualifier.$sRes.$sTextQualifier; - return $sRes; - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Plain text (unlocalized) representation', - 'html' => 'HTML representation (unordered list)', - ); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param mixed $value The current value of the field - * @param string $sVerb The verb specifying the representation of the value - * @param DBObject $oHostObject The object - * @param bool $bLocalize Whether or not to localize the value - * - * @return string - * @throws \Exception - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - $sRemoteName = $this->IsIndirect() ? - /** @var \AttributeLinkedSetIndirect $this */ - $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname'; - - $oLinkSet = clone $value; // Workaround/Safety net for Trac #887 - $iLimit = MetaModel::GetConfig()->Get('max_linkset_output'); - $iCount = 0; - $aNames = array(); - foreach($oLinkSet as $oItem) - { - if (($iLimit > 0) && ($iCount == $iLimit)) - { - $iTotal = $oLinkSet->Count(); - $aNames[] = '... '.Dict::Format('UI:TruncatedResults', $iCount, $iTotal); - break; - } - $aNames[] = $oItem->Get($sRemoteName); - $iCount++; - } - - switch($sVerb) - { - case '': - return implode("\n", $aNames); - - case 'html': - return '
  • '.implode("
  • ", $aNames).'
'; - - default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - } - - public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never - - public function GetImportColumns() - { - $aColumns = array(); - $aColumns[$this->GetCode()] = 'TEXT'; - return $aColumns; - } - - /** - * @param string $sProposedValue - * @param bool $bLocalizedValue - * @param string $sSepItem - * @param string $sSepAttribute - * @param string $sSepValue - * @param string $sAttributeQualifier - * - * @return \DBObjectSet|mixed - * @throws \CSVParserException - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \MissingQueryArgument - * @throws \MySQLException - * @throws \MySQLHasGoneAwayException - * @throws \Exception - */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - if (is_null($sSepItem) || empty($sSepItem)) - { - $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); - } - if (is_null($sSepAttribute) || empty($sSepAttribute)) - { - $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); - } - if (is_null($sSepValue) || empty($sSepValue)) - { - $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); - } - if (is_null($sAttributeQualifier) || empty($sAttributeQualifier)) - { - $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier'); - } - - $sTargetClass = $this->Get('linked_class'); - - $sInput = str_replace($sSepItem, "\n", $sProposedValue); - $oCSVParser = new CSVParser($sInput, $sSepAttribute, $sAttributeQualifier); - - $aInput = $oCSVParser->ToArray(0 /* do not skip lines */); - - $aLinks = array(); - foreach($aInput as $aRow) - { - // 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value - $aExtKeys = array(); - $aValues = array(); - foreach($aRow as $sCell) - { - $iSepPos = strpos($sCell, $sSepValue); - if ($iSepPos === false) - { - // Houston... - throw new CoreException('Wrong format for link attribute specification', array('value' => $sCell)); - } - - $sAttCode = trim(substr($sCell, 0, $iSepPos)); - $sValue = substr($sCell, $iSepPos + strlen($sSepValue)); - - if (preg_match('/^(.+)->(.+)$/', $sAttCode, $aMatches)) - { - $sKeyAttCode = $aMatches[1]; - $sRemoteAttCode = $aMatches[2]; - $aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue; - if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode)) - { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sKeyAttCode)); - } - /** @var \AttributeExternalKey $oKeyAttDef */ - $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); - $sRemoteClass = $oKeyAttDef->GetTargetClass(); - if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode)) - { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode)); - } - } - else - { - if(!MetaModel::IsValidAttCode($sTargetClass, $sAttCode)) - { - throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode)); - } - $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode); - $aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); - } - } - - // 2nd - Instanciate the object and set the value - if (isset($aValues['finalclass'])) - { - $sLinkClass = $aValues['finalclass']; - if (!is_subclass_of($sLinkClass, $sTargetClass)) - { - throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); - } - } - elseif (MetaModel::IsAbstract($sTargetClass)) - { - throw new CoreException('Missing finalclass for link attribute specification'); - } - else - { - $sLinkClass = $sTargetClass; - } - - $oLink = MetaModel::NewObject($sLinkClass); - foreach ($aValues as $sAttCode => $sValue) - { - $oLink->Set($sAttCode, $sValue); - } - - // 3rd - Set external keys from search conditions - foreach ($aExtKeys as $sKeyAttCode => $aReconciliation) - { - $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); - $sKeyClass = $oKeyAttDef->GetTargetClass(); - $oExtKeyFilter = new DBObjectSearch($sKeyClass); - $aReconciliationDesc = array(); - foreach($aReconciliation as $sRemoteAttCode => $sValue) - { - $oExtKeyFilter->AddCondition($sRemoteAttCode, $sValue, '='); - $aReconciliationDesc[] = "$sRemoteAttCode=$sValue"; - } - $oExtKeySet = new DBObjectSet($oExtKeyFilter); - switch($oExtKeySet->Count()) - { - case 0: - $sReconciliationDesc = implode(', ', $aReconciliationDesc); - throw new CoreException("Found no match", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); - break; - case 1: - $oRemoteObj = $oExtKeySet->Fetch(); - $oLink->Set($sKeyAttCode, $oRemoteObj->GetKey()); - break; - default: - $sReconciliationDesc = implode(', ', $aReconciliationDesc); - throw new CoreException("Found several matches", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); - // Found several matches, ambiguous - } - } - - // Check (roughly) if such a link is valid - $aErrors = array(); - foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - /** @var \AttributeExternalKey $oAttDef */ - if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) - { - continue; // Don't check the key to self - } - } - - if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) - { - $aErrors[] = $sAttCode; - } - } - if (count($aErrors) > 0) - { - throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors)); - } - - $aLinks[] = $oLink; - } - $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); - return $oSet; - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param \ormLinkSet $value - * - * @return array - * @throws \CoreException - */ - public function GetForJSON($value) - { - $aRet = array(); - if (is_object($value) && ($value instanceof ormLinkSet)) - { - $value->Rewind(); - while ($oObj = $value->Fetch()) - { - $sObjClass = get_class($oObj); - // Show only relevant information (hide the external key to the current object) - $aAttributes = array(); - foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) - { - if ($sAttCode == 'finalclass') - { - if ($sObjClass == $this->GetLinkedClass()) - { - // Simplify the output if the exact class could be determined implicitely - continue; - } - } - if ($sAttCode == $this->GetExtKeyToMe()) continue; - if ($oAttDef->IsExternalField()) continue; - if (!$oAttDef->IsBasedOnDBColumns()) continue; - if (!$oAttDef->IsScalar()) continue; - $attValue = $oObj->Get($sAttCode); - $aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue); - } - $aRet[] = $aAttributes; - } - } - return $aRet; - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - * - * @param $json - * - * @return \DBObjectSet - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \Exception - */ - public function FromJSONToValue($json) - { - $sTargetClass = $this->Get('linked_class'); - - $aLinks = array(); - foreach($json as $aValues) - { - if (isset($aValues['finalclass'])) - { - $sLinkClass = $aValues['finalclass']; - if (!is_subclass_of($sLinkClass, $sTargetClass)) - { - throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); - } - } - elseif (MetaModel::IsAbstract($sTargetClass)) - { - throw new CoreException('Missing finalclass for link attribute specification'); - } - else - { - $sLinkClass = $sTargetClass; - } - - $oLink = MetaModel::NewObject($sLinkClass); - foreach ($aValues as $sAttCode => $sValue) - { - $oLink->Set($sAttCode, $sValue); - } - - // Check (roughly) if such a link is valid - $aErrors = array(); - foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - /** @var AttributeExternalKey $oAttDef */ - if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) - { - continue; // Don't check the key to self - } - } - - if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) - { - $aErrors[] = $sAttCode; - } - } - if (count($aErrors) > 0) - { - throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors)); - } - - $aLinks[] = $oLink; - } - $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); - return $oSet; - } - - /** - * @param $proposedValue - * @param $oHostObj - * - * @return mixed - * @throws \Exception - */ - public function MakeRealValue($proposedValue, $oHostObj){ - if($proposedValue === null) - { - $sLinkedClass = $this->GetLinkedClass(); - $aLinkedObjectsArray = array(); - $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); - - return new ormLinkSet( - get_class($oHostObj), - $this->GetCode(), - $oSet - ); - } - - return $proposedValue; - } - - /** - * @param ormLinkSet $val1 - * @param ormLinkSet $val2 - * @return bool - */ - public function Equals($val1, $val2) - { - if ($val1 === $val2) - { - $bAreEquivalent = true; - } - else - { - $bAreEquivalent = ($val2->HasDelta() === false); - } - return $bAreEquivalent; - } - - /** - * Find the corresponding "link" attribute on the target class, if any - * - * @return null | AttributeDefinition - * @throws \Exception - */ - public function GetMirrorLinkAttribute() - { - $oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe()); - return $oRemoteAtt; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\LinkedSetField'; - } - - /** - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\LinkedSetField $oFormField - * - * @return \Combodo\iTop\Form\Field\LinkedSetField - * @throws \CoreException - * @throws \DictExceptionMissingString - * @throws \Exception - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - // Setting target class - if (!$this->IsIndirect()) - { - $sTargetClass = $this->GetLinkedClass(); - } - else - { - /** @var \AttributeExternalKey $oRemoteAttDef */ - /** @var \AttributeLinkedSetIndirect $this */ - $oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); - $sTargetClass = $oRemoteAttDef->GetTargetClass(); - - /** @var \AttributeLinkedSetIndirect $this */ - $oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote()); - } - $oFormField->SetTargetClass($sTargetClass); - $oFormField->SetIndirect($this->IsIndirect()); - // Setting attcodes to display - $aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list')); - // - Adding friendlyname attribute to the list is not already in it - $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass); - if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) - { - $aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay); - } - // - Adding attribute labels - $aAttributesToDisplay = array(); - foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) - { - $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay); - $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel(); - } - $oFormField->SetAttributesToDisplay($aAttributesToDisplay); - - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - public function IsPartOfFingerprint() { return false; } -} - -/** - * Set of objects linked to an object (n-n), and being part of its definition - * - * @package iTopORM - */ -class AttributeLinkedSetIndirect extends AttributeLinkedSet -{ - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote")); - } - - public function IsIndirect() - { - return true; - } - - public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); } - public function GetEditClass() {return "LinkedSet";} - public function DuplicatesAllowed() {return $this->GetOptional("duplicates", false);} // The same object may be linked several times... or not... - - public function GetTrackingLevel() - { - return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default')); - } - - /** - * Find the corresponding "link" attribute on the target class, if any - * @return null | AttributeDefinition - * @throws \CoreException - */ - public function GetMirrorLinkAttribute() - { - $oRet = null; - /** @var \AttributeExternalKey $oExtKeyToRemote */ - $oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); - $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) - { - if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) continue; - if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) continue; - if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) continue; - if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) continue; - $oRet = $oRemoteAttDef; - break; - } - return $oRet; - } -} - -/** - * Abstract class implementing default filters for a DB column - * - * @package iTopORM - */ -class AttributeDBFieldVoid extends AttributeDefinition -{ - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); - } - - // To be overriden, used in GetSQLColumns - protected function GetSQLCol($bFullSpec = false) - { - return 'VARCHAR(255)' - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - protected function GetSQLColSpec() - { - $default = $this->ScalarToSQL($this->GetDefaultValue()); - if (is_null($default)) - { - $sRet = ''; - } - else - { - if (is_numeric($default)) - { - // Though it is a string in PHP, it will be considered as a numeric value in MySQL - // Then it must not be quoted here, to preserve the compatibility with the value returned by CMDBSource::GetFieldSpec - $sRet = " DEFAULT $default"; - } - else - { - $sRet = " DEFAULT ".CMDBSource::Quote($default); - } - } - return $sRet; - } - - public function GetEditClass() {return "String";} - - public function GetValuesDef() {return $this->Get("allowed_values");} - public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} - - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return !$this->IsMagic();} - public function GetSQLExpr() - { - return $this->Get("sql"); - } - - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} - public function IsNullAllowed() {return false;} - - // - protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - - public function GetSQLExpressions($sPrefix = '') - { - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $this->Get("sql"); - return $aColumns; - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - $value = $this->MakeRealValue($aCols[$sPrefix.''], null); - return $value; - } - public function GetSQLValues($value) - { - $aValues = array(); - $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); - return $aValues; - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec); - return $aColumns; - } - - public function GetFilterDefinitions() - { - return array($this->GetCode() => new FilterFromAttribute($this)); - } - - public function GetBasicFilterOperators() - { - return array("="=>"equals", "!="=>"differs from"); - } - public function GetBasicFilterLooseOperator() - { - return "="; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '=': - default: - return $this->GetSQLExpr()." = $sQValue"; - } - } -} - -/** - * Base class for all kind of DB attributes, with the exception of external keys - * - * @package iTopORM - */ -class AttributeDBField extends AttributeDBFieldVoid -{ - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); - } - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue($this->Get("default_value"), $oHostObject);} - public function IsNullAllowed() {return $this->Get("is_null_allowed");} -} - -/** - * Map an integer column to an attribute - * - * @package iTopORM - */ -class AttributeInteger extends AttributeDBField -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');} - - public function GetValidationPattern() - { - return "^[0-9]+$"; - } - - public function GetBasicFilterOperators() - { - return array( - "!="=>"differs from", - "="=>"equals", - ">"=>"greater (strict) than", - ">="=>"greater than", - "<"=>"less (strict) than", - "<="=>"less than", - "in"=>"in" - ); - } - public function GetBasicFilterLooseOperator() - { - // Unless we implement an "equals approximately..." or "same order of magnitude" - return "="; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '>': - return $this->GetSQLExpr()." > $sQValue"; - break; - case '>=': - return $this->GetSQLExpr()." >= $sQValue"; - break; - case '<': - return $this->GetSQLExpr()." < $sQValue"; - break; - case '<=': - return $this->GetSQLExpr()." <= $sQValue"; - break; - case 'in': - if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; - break; - - case '=': - default: - return $this->GetSQLExpr()." = \"$value\""; - } - } - - public function GetNullValue() - { - return null; - } - public function IsNull($proposedValue) - { - return is_null($proposedValue); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; // 0 is transformed into '' ! - return (int)$proposedValue; - } - - public function ScalarToSQL($value) - { - assert(is_numeric($value) || is_null($value)); - return $value; // supposed to be an int - } -} - -/** - * An external key for which the class is defined as the value of another attribute - * - * @package iTopORM - */ -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')); - } - - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} - - public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} - public function IsNullAllowed() - { - return $this->Get("is_null_allowed"); - } - - - public function GetBasicFilterOperators() - { - return parent::GetBasicFilterOperators(); - } - public function GetBasicFilterLooseOperator() - { - return parent::GetBasicFilterLooseOperator(); - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } - - public function GetNullValue() - { - return 0; - } - - public function IsNull($proposedValue) - { - return ($proposedValue == 0); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return 0; - if ($proposedValue === '') return 0; - if (MetaModel::IsValidObject($proposedValue)) { - /** @var \DBObject $proposedValue */ - return $proposedValue->GetKey(); - } - return (int)$proposedValue; - } -} - -/** - * Display an integer between 0 and 100 as a percentage / horizontal bar graph - * - * @package iTopORM - */ -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... - $iValue = (int)$sValue; - if ($iValue > 100) - { - $iValue = 100; - } - else if ($iValue < 0) - { - $iValue = 0; - } - if ($iValue > 90) - { - $sColor = "#cc3300"; - } - else if ($iValue > 50) - { - $sColor = "#cccc00"; - } - else - { - $sColor = "#33cc00"; - } - $iPercentWidth = ($iWidth * $iValue) / 100; - return "
 
 $sValue %"; - } -} - -/** - * Map a decimal value column (suitable for financial computations) to an attribute - * internally in PHP such numbers are represented as string. Should you want to perform - * a calculation on them, it is recommended to use the BC Math functions in order to - * retain the precision - * - * @package iTopORM - */ -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 */)); - } - - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) - { - return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - public function GetValidationPattern() - { - $iNbDigits = $this->Get('digits'); - $iPrecision = $this->Get('decimals'); - $iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below - return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$"; - } - - public function GetBasicFilterOperators() - { - return array( - "!="=>"differs from", - "="=>"equals", - ">"=>"greater (strict) than", - ">="=>"greater than", - "<"=>"less (strict) than", - "<="=>"less than", - "in"=>"in" - ); - } - public function GetBasicFilterLooseOperator() - { - // Unless we implement an "equals approximately..." or "same order of magnitude" - return "="; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '>': - return $this->GetSQLExpr()." > $sQValue"; - break; - case '>=': - return $this->GetSQLExpr()." >= $sQValue"; - break; - case '<': - return $this->GetSQLExpr()." < $sQValue"; - break; - case '<=': - return $this->GetSQLExpr()." <= $sQValue"; - break; - case 'in': - if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); - return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; - break; - - case '=': - default: - return $this->GetSQLExpr()." = \"$value\""; - } - } - - public function GetNullValue() - { - return null; - } - public function IsNull($proposedValue) - { - return is_null($proposedValue); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; - return (string)$proposedValue; - } - - public function ScalarToSQL($value) - { - assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value)); - return $value; // null or string - } -} - -/** - * Map a boolean column to an attribute - * - * @package iTopORM - */ -class AttributeBoolean extends AttributeInteger -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "Integer";} - protected function GetSQLCol($bFullSpec = false) {return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');} - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return null; - if ($proposedValue === '') return null; - if ((int)$proposedValue) return true; - return false; - } - - public function ScalarToSQL($value) - { - if ($value) return 1; - return 0; - } - - public function GetValueLabel($bValue) - { - if (is_null($bValue)) - { - $sLabel = Dict::S('Core:'.get_class($this).'/Value:null'); - } - else - { - $sValue = $bValue ? 'yes' : 'no'; - $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue); - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, true /*user lang*/); - } - return $sLabel; - } - - public function GetValueDescription($bValue) - { - if (is_null($bValue)) - { - $sDescription = Dict::S('Core:'.get_class($this).'/Value:null+'); - } - else - { - $sValue = $bValue ? 'yes' : 'no'; - $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue.'+'); - $sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault, true /*user lang*/); - } - return $sDescription; - } - - public function GetAsHTML($bValue, $oHostObject = null, $bLocalize = true) - { - if (is_null($bValue)) - { - $sRes = ''; - } - elseif ($bLocalize) - { - $sLabel = $this->GetValueLabel($bValue); - $sDescription = $this->GetValueDescription($bValue); - // later, we could imagine a detailed description in the title - $sRes = "".parent::GetAsHtml($sLabel).""; - } - else - { - $sRes = $bValue ? 'yes' : 'no'; - } - return $sRes; - } - - public function GetAsXML($bValue, $oHostObject = null, $bLocalize = true) - { - if (is_null($bValue)) - { - $sFinalValue = ''; - } - elseif ($bLocalize) - { - $sFinalValue = $this->GetValueLabel($bValue); - } - else - { - $sFinalValue = $bValue ? 'yes' : 'no'; - } - $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); - return $sRes; - } - - public function GetAsCSV($bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (is_null($bValue)) - { - $sFinalValue = ''; - } - elseif ($bLocalize) - { - $sFinalValue = $this->GetValueLabel($bValue); - } - else - { - $sFinalValue = $bValue ? 'yes' : 'no'; - } - $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); - return $sRes; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\SelectField'; - } - - /** - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\SelectField $oFormField - * - * @return \Combodo\iTop\Form\Field\SelectField - * @throws \CoreException - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - $oFormField->SetChoices(array('yes' => $this->GetValueLabel(true), 'no' => $this->GetValueLabel(false))); - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - public function GetEditValue($value, $oHostObj = null) - { - if (is_null($value)) - { - return ''; - } - else - { - return $this->GetValueLabel($value); - } - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param $value - * - * @return bool - */ - public function GetForJSON($value) - { - return (bool)$value; - } - - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - $sInput = strtolower(trim($sProposedValue)); - if ($bLocalizedValue) - { - switch ($sInput) - { - case '1': // backward compatibility - case $this->GetValueLabel(true): - $value = true; - break; - case '0': // backward compatibility - case 'no': - case $this->GetValueLabel(false): - $value = false; - break; - default: - $value = null; - } - } - else - { - switch ($sInput) - { - case '1': // backward compatibility - case 'yes': - $value = true; - break; - case '0': // backward compatibility - case 'no': - $value = false; - break; - default: - $value = null; - } - } - return $value; - } -} - -/** - * Map a varchar column (size < ?) to an attribute - * - * @package iTopORM - */ -class AttributeString extends AttributeDBField -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "String";} - - protected function GetSQLCol($bFullSpec = false) - { - return 'VARCHAR(255)' - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - public function GetValidationPattern() - { - $sPattern = $this->GetOptional('validation_pattern', ''); - if (empty($sPattern)) - { - return parent::GetValidationPattern(); - } - else - { - return $sPattern; - } - } - - public function CheckFormat($value) - { - $sRegExp = $this->GetValidationPattern(); - if (empty($sRegExp)) - { - return true; - } - else - { - $sRegExp = str_replace('/', '\\/', $sRegExp); - return preg_match("/$sRegExp/", $value); - } - } - - public function GetMaxSize() - { - return 255; - } - - public function GetBasicFilterOperators() - { - return array( - "="=>"equals", - "!="=>"differs from", - "Like"=>"equals (no case)", - "NotLike"=>"differs from (no case)", - "Contains"=>"contains", - "Begins with"=>"begins with", - "Finishes with"=>"finishes with" - ); - } - public function GetBasicFilterLooseOperator() - { - return "Contains"; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '=': - case '!=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'Begins with': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%"); - case 'Finishes with': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value"); - case 'Contains': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); - case 'NotLike': - return $this->GetSQLExpr()." NOT LIKE $sQValue"; - case 'Like': - default: - return $this->GetSQLExpr()." LIKE $sQValue"; - } - } - - public function GetNullValue() - { - return ''; - } - - public function IsNull($proposedValue) - { - return ($proposedValue == ''); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return ''; - return (string)$proposedValue; - } - - public function ScalarToSQL($value) - { - if (!is_string($value) && !is_null($value)) - { - throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); - } - return $value; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - } - - public function GetDisplayStyle() - { - return $this->GetOptional('display_style', 'select'); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\StringField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - -} - -/** - * An attibute that matches an object class - * - * @package iTopORM - */ -class AttributeClass extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM; - - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("class_category", "more_values")); - } - - public function __construct($sCode, $aParams) - { - $this->m_sCode = $sCode; - $aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']); - parent::__construct($sCode, $aParams); - } - - public function GetDefaultValue(DBObject $oHostObject = null) - { - $sDefault = parent::GetDefaultValue($oHostObject); - if (!$this->IsNullAllowed() && $this->IsNull($sDefault)) - { - // For this kind of attribute specifying null as default value - // is authorized even if null is not allowed - - // Pick the first one... - $aClasses = $this->GetAllowedValues(); - $sDefault = key($aClasses); - } - return $sDefault; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (empty($sValue)) return ''; - return MetaModel::GetName($sValue); - } - - public function RequiresIndex() - { - return true; - } - - public function GetBasicFilterLooseOperator() - { - return '='; - } - -} - -/** - * An attibute that matches one of the language codes availables in the dictionnary - * - * @package iTopORM - */ -class AttributeApplicationLanguage extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - } - - public function __construct($sCode, $aParams) - { - $this->m_sCode = $sCode; - $aAvailableLanguages = Dict::GetLanguages(); - $aLanguageCodes = array(); - foreach($aAvailableLanguages as $sLangCode => $aInfo) - { - $aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')'; - } - $aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes); - parent::__construct($sCode, $aParams); - } - - public function RequiresIndex() - { - return true; - } - - public function GetBasicFilterLooseOperator() - { - return '='; - } -} - -/** - * The attribute dedicated to the finalclass automatic attribute - * - * @package iTopORM - */ -class AttributeFinalClass extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - public $m_sValue; - - public function __construct($sCode, $aParams) - { - $this->m_sCode = $sCode; - $aParams["allowed_values"] = null; - parent::__construct($sCode, $aParams); - - $this->m_sValue = $this->Get("default_value"); - } - - public function IsWritable() - { - return false; - } - public function IsMagic() - { - return true; - } - - public function RequiresIndex() - { - return true; - } - - public function SetFixedValue($sValue) - { - $this->m_sValue = $sValue; - } - public function GetDefaultValue(DBObject $oHostObject = null) - { - return $this->m_sValue; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (empty($sValue)) return ''; - if ($bLocalize) - { - return MetaModel::GetName($sValue); - } - else - { - return $sValue; - } - } - - /** - * An enum can be localized - * - * @param string $sProposedValue - * @param bool $bLocalizedValue - * @param string $sSepItem - * @param string $sSepAttribute - * @param string $sSepValue - * @param string $sAttributeQualifier - * - * @return mixed|null|string - * @throws \CoreException - * @throws \OQLException - */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - if ($bLocalizedValue) - { - // Lookup for the value matching the input - // - $sFoundValue = null; - $aRawValues = self::GetAllowedValues(); - if (!is_null($aRawValues)) - { - foreach ($aRawValues as $sKey => $sValue) - { - if ($sProposedValue == $sValue) - { - $sFoundValue = $sKey; - break; - } - } - } - if (is_null($sFoundValue)) - { - return null; - } - return $this->MakeRealValue($sFoundValue, null); - } - else - { - return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); - } - } - - - // Because this is sometimes used to get a localized/string version of an attribute... - public function GetEditValue($sValue, $oHostObj = null) - { - if (empty($sValue)) return ''; - return MetaModel::GetName($sValue); - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param $value - * - * @return string - */ - public function GetForJSON($value) - { - // JSON values are NOT localized - return $value; - } - - /** - * @param $value - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return string - * @throws \CoreException - * @throws \DictExceptionMissingString - */ - public function GetAsCSV( - $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false - ) - { - if ($bLocalize && $value != '') - { - $sRawValue = MetaModel::GetName($value); - } - else - { - $sRawValue = $value; - } - return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText); - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if (empty($value)) return ''; - if ($bLocalize) - { - $sRawValue = MetaModel::GetName($value); - } - else - { - $sRawValue = $value; - } - return Str::pure2xml($sRawValue); - } - - public function GetBasicFilterLooseOperator() - { - return '='; - } - - public function GetValueLabel($sValue) - { - if (empty($sValue)) return ''; - return MetaModel::GetName($sValue); - } - - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - $aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL); - $aLocalizedValues = array(); - foreach ($aRawValues as $sClass) - { - $aLocalizedValues[$sClass] = MetaModel::GetName($sClass); - } - return $aLocalizedValues; - } -} - - -/** - * Map a varchar column (size < ?) to an attribute that must never be shown to the user - * - * @package iTopORM - */ -class AttributePassword extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "Password";} - - protected function GetSQLCol($bFullSpec = false) - { - return "VARCHAR(64)" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - public function GetMaxSize() - { - return 64; - } - - public function GetFilterDefinitions() - { - // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists) - // not allowed to search on passwords! - return array(); - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (strlen($sValue) == 0) - { - return ''; - } - else - { - return '******'; - } - } - - public function IsPartOfFingerprint() { return false; } // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt' -} - -/** - * Map a text column (size < 255) to an attribute that is encrypted in the database - * The encryption is based on a key set per iTop instance. Thus if you export your - * database (in SQL) to someone else without providing the key at the same time - * the encrypted fields will remain encrypted - * - * @package iTopORM - */ -class AttributeEncryptedString extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - - static $sKey = null; // Encryption key used for all encrypted fields - static $sLibrary = null; // Encryption library used for all encrypted fields - public function __construct($sCode, $aParams) - { - parent::__construct($sCode, $aParams); - if (self::$sKey == null) - { - self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); - } - if(self::$sLibrary == null) - { - self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); - } - } - /** - * When the attribute definitions are stored in APC cache: - * 1) The static class variable $sKey is NOT serialized - * 2) The object's constructor is NOT called upon wakeup - * 3) mcrypt may crash the server if passed an empty key !! - * - * So let's restore the key (if needed) when waking up - **/ - public function __wakeup() - { - if (self::$sKey == null) - { - self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); - } - if(self::$sLibrary == null) - { - self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); - } - } - - - protected function GetSQLCol($bFullSpec = false) {return "TINYBLOB";} - - public function GetMaxSize() - { - return 255; - } - - public function GetFilterDefinitions() - { - // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists) - // not allowed to search on encrypted fields ! - return array(); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return null; - return (string)$proposedValue; - } - - /** - * Decrypt the value when reading from the database - * - * @param array $aCols - * @param string $sPrefix - * - * @return string - * @throws \Exception - */ - public function FromSQLToValue($aCols, $sPrefix = '') - { - $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); - $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); - return $sValue; - } - - /** - * Encrypt the value before storing it in the database - * - * @param $value - * - * @return array - * @throws \Exception - */ - public function GetSQLValues($value) - { - $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); - $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); - - $aValues = array(); - $aValues[$this->Get("sql")] = $encryptedValue; - return $aValues; - } -} - - -// Wiki formatting - experimental -// -// [[:]] -// Example: [[Server:db1.tnut.com]] -define('WIKI_OBJECT_REGEXP', '/\[\[(.+):(.+)\]\]/U'); - - -/** - * Map a text column (size > ?) to an attribute - * - * @package iTopORM - */ -class AttributeText extends AttributeString -{ - public function GetEditClass() {return ($this->GetFormat() == 'text') ? 'Text' : "HTML";} - - protected function GetSQLCol($bFullSpec = false) - { - return "TEXT".CMDBSource::GetSqlStringColumnDefinition(); - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec); - if ($this->GetOptional('format', null) != null ) - { - // Add the extra column only if the property 'format' is specified for the attribute - $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::GetSqlStringColumnDefinition(); - if ($bFullSpec) - { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records - } - } - return $aColumns; - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->Get('sql'); - } - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $sPrefix; - if ($this->GetOptional('format', null) != null ) - { - // Add the extra column only if the property 'format' is specified for the attribute - $aColumns['_format'] = $sPrefix.'_format'; - } - return $aColumns; - } - - public function GetMaxSize() - { - // Is there a way to know the current limitation for mysql? - // See mysql_field_len() - return 65535; - } - - static public function RenderWikiHtml($sText, $bWikiOnly = false) - { - if (!$bWikiOnly) - { - $sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i'; - if (preg_match_all($sPattern, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */)) - { - $i = count($aAllMatches); - // Replace the URLs by an actual hyperlink ... - // Let's do it backwards so that the initial positions are not modified by the replacement - // This works if the matches are captured: in the order they occur in the string AND - // with their offset (i.e. position) inside the string - while($i > 0) - { - $i--; - $sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern - $iPos = $aAllMatches[$i][0][1]; // Position of the main pattern - $sText = substr_replace($sText, "$sUrl", $iPos, strlen($sUrl)); - - } - } - } - if (preg_match_all(WIKI_OBJECT_REGEXP, $sText, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) - { - $sClass = trim($aMatches[1]); - $sName = trim($aMatches[2]); - - if (MetaModel::IsValidClass($sClass)) - { - $oObj = MetaModel::GetObjectByName($sClass, $sName, false /* MustBeFound */); - if (is_object($oObj)) - { - // Propose a std link to the object - $sText = str_replace($aMatches[0], $oObj->GetHyperlink(), $sText); - } - else - { - // Propose a std link to the object - $sClassLabel = MetaModel::GetName($sClass); - $sText = str_replace($aMatches[0], "$sClassLabel:$sName", $sText); - // Later: propose a link to create a new object - // Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name - //$sText = preg_replace('/\[\[(.+):(.+)\]\]/', ''.$sName.'', $sText); - } - } - } - } - return $sText; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - $aStyles = array(); - if ($this->GetWidth() != '') - { - $aStyles[] = 'width:'.$this->GetWidth(); - } - if ($this->GetHeight() != '') - { - $aStyles[] = 'height:'.$this->GetHeight(); - } - $sStyle = ''; - if (count($aStyles) > 0) - { - $aStyles[] = 'overflow:auto'; - $sStyle = 'style="'.implode(';', $aStyles).'"'; - } - - if ($this->GetFormat() == 'text') - { - $sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize); - $sValue = self::RenderWikiHtml($sValue); - return "
".str_replace("\n", "
\n", $sValue).'
'; - } - else - { - $sValue = self::RenderWikiHtml($sValue, true /* wiki only */); - return "
".InlineImage::FixUrls($sValue).'
'; - } - - } - - public function GetEditValue($sValue, $oHostObj = null) - { - if ($this->GetFormat() == 'text') - { - if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) - { - $sClass = $aMatches[1]; - $sName = $aMatches[2]; - - if (MetaModel::IsValidClass($sClass)) - { - $sClassLabel = MetaModel::GetName($sClass); - $sValue = str_replace($aMatches[0], "[[$sClassLabel:$sName]]", $sValue); - } - } - } - } - else - { - $sValue = str_replace('&', '&', $sValue); - } - return $sValue; - } - - /** - * For fields containing a potential markup, return the value without this markup - * - * @param string $sValue - * @param \DBObject $oHostObj - * - * @return string - */ - public function GetAsPlainText($sValue, $oHostObj = null) - { - if ($this->GetFormat() == 'html') - { - return (string) utils::HtmlToText($this->GetEditValue($sValue, $oHostObj)); - } - else - { - return parent::GetAsPlainText($sValue, $oHostObj); - } - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - $sValue = $proposedValue; - switch ($this->GetFormat()) - { - case 'html': - if (($sValue !== null) && ($sValue !== '')) - { - $sValue = HTMLSanitizer::Sanitize($sValue); - } - break; - - case 'text': - default: - if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) - { - foreach($aAllMatches as $iPos => $aMatches) - { - $sClassLabel = $aMatches[1]; - $sName = $aMatches[2]; - - if (!MetaModel::IsValidClass($sClassLabel)) - { - $sClass = MetaModel::GetClassFromLabel($sClassLabel); - if ($sClass) - { - $sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue); - } - } - } - } - } - return $sValue; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - return Str::pure2xml($value); - } - - public function GetWidth() - { - return $this->GetOptional('width', ''); - } - - public function GetHeight() - { - return $this->GetOptional('height', ''); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\TextAreaField'; - } - - /** - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\TextAreaField $oFormField - * - * @return \Combodo\iTop\Form\Field\TextAreaField - * @throws \CoreException - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - /** @var \Combodo\iTop\Form\Field\TextAreaField $oFormField */ - $oFormField = new $sFormFieldClass($this->GetCode(), null, $oObject); - $oFormField->SetFormat($this->GetFormat()); - } - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - /** - * The actual formatting of the field: either text (=plain text) or html (= text with HTML markup) - * @return string - */ - public function GetFormat() - { - return $this->GetOptional('format', 'text'); - } - - /** - * Read the value from the row returned by the SQL query and transorms it to the appropriate - * internal format (either text or html) - * - * @see AttributeDBFieldVoid::FromSQLToValue() - * - * @param array $aCols - * @param string $sPrefix - * - * @return string - */ - public function FromSQLToValue($aCols, $sPrefix = '') - { - $value = $aCols[$sPrefix.'']; - if ($this->GetOptional('format', null) != null ) - { - // Read from the extra column only if the property 'format' is specified for the attribute - $sFormat = $aCols[$sPrefix.'_format']; - } - else - { - $sFormat = $this->GetFormat(); - } - - switch($sFormat) - { - case 'text': - if ($this->GetFormat() == 'html') - { - $value = utils::TextToHtml($value); - } - break; - - case 'html': - if ($this->GetFormat() == 'text') - { - $value = utils::HtmlToText($value); - } - else - { - $value = InlineImage::FixUrls((string)$value); - } - break; - - default: - // unknown format ?? - } - return $value; - } - - public function GetSQLValues($value) - { - $aValues = array(); - $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); - if ($this->GetOptional('format', null) != null ) - { - // Add the extra column only if the property 'format' is specified for the attribute - $aValues[$this->Get("sql").'_format'] = $this->GetFormat(); - } - return $aValues; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - switch($this->GetFormat()) - { - case 'html': - if ($bConvertToPlainText) - { - $sValue = utils::HtmlToText((string)$sValue); - } - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - break; - - case 'text': - default: - return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); - } - } -} - -/** - * Map a log to an attribute - * - * @package iTopORM - */ -class AttributeLongText extends AttributeText -{ - protected function GetSQLCol($bFullSpec = false) - { - return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition(); - } - - public function GetMaxSize() - { - // Is there a way to know the current limitation for mysql? - // See mysql_field_len() - return 65535*1024; // Limited... still 64 Mb! - } -} - -/** - * An attibute that stores a case log (i.e journal) - * - * @package iTopORM - */ -class AttributeCaseLog extends AttributeLongText -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - - public function GetNullValue() - { - return ''; - } - - public function IsNull($proposedValue) - { - if (!($proposedValue instanceof ormCaseLog)) - { - return ($proposedValue == ''); - } - return ($proposedValue->GetText() == ''); - } - - public function ScalarToSQL($value) - { - if (!is_string($value) && !is_null($value)) - { - throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass())); - } - return $value; - } - public function GetEditClass() {return "CaseLog";} - - public function GetEditValue($sValue, $oHostObj = null) - { - if (!($sValue instanceOf ormCaseLog)) - { - return ''; - } - return $sValue->GetModifiedEntry(); - } - - /** - * For fields containing a potential markup, return the value without this markup - * - * @param mixed $value - * @param \DBObject $oHostObj - * - * @return string - */ - public function GetAsPlainText($value, $oHostObj = null) - { - if ($value instanceOf ormCaseLog) - { - /** ormCaseLog $value */ - return $value->GetAsPlainText(); - } - else - { - return (string) $value; - } - } - - public function GetDefaultValue(DBObject $oHostObject = null) {return new ormCaseLog();} - public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());} - - - /** - * Facilitate things: allow the user to Set the value from a string - * - * @param $proposedValue - * @param \DBObject $oHostObj - * - * @return mixed|null|\ormCaseLog|string - * @throws \Exception - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - if ($proposedValue instanceof ormCaseLog) - { - // Passthrough - $ret = clone $proposedValue; - } - else - { - // Append the new value if an instance of the object is supplied - // - $oPreviousLog = null; - if ($oHostObj != null) - { - $oPreviousLog = $oHostObj->Get($this->GetCode()); - if (!is_object($oPreviousLog)) - { - $oPreviousLog = $oHostObj->GetOriginal($this->GetCode());; - } - - } - if (is_object($oPreviousLog)) - { - $oCaseLog = clone($oPreviousLog); - } - else - { - $oCaseLog = new ormCaseLog(); - } - - if ($proposedValue instanceof stdClass) - { - $oCaseLog->AddLogEntryFromJSON($proposedValue); - } - else - { - if (strlen($proposedValue) > 0) - { - $oCaseLog->AddLogEntry($proposedValue); - } - } - $ret = $oCaseLog; - } - return $ret; - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->Get('sql'); - } - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $sPrefix; - $aColumns['_index'] = $sPrefix.'_index'; - return $aColumns; - } - - /** - * @param array $aCols - * @param string $sPrefix - * - * @return \ormCaseLog - * @throws \MissingColumnException - */ - public function FromSQLToValue($aCols, $sPrefix = '') - { - if (!array_key_exists($sPrefix, $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } - $sLog = $aCols[$sPrefix]; - - if (isset($aCols[$sPrefix.'_index'])) - { - $sIndex = $aCols[$sPrefix.'_index']; - } - else - { - // For backward compatibility, allow the current state to be: 1 log, no index - $sIndex = ''; - } - - if (strlen($sIndex) > 0) - { - $aIndex = unserialize($sIndex); - $value = new ormCaseLog($sLog, $aIndex); - } - else - { - $value = new ormCaseLog($sLog); - } - return $value; - } - - public function GetSQLValues($value) - { - if (!($value instanceOf ormCaseLog)) - { - $value = new ormCaseLog(''); - } - $aValues = array(); - $aValues[$this->GetCode()] = $value->GetText(); - $aValues[$this->GetCode().'_index'] = serialize($value->GetIndex()); - - return $aValues; - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb) - .CMDBSource::GetSqlStringColumnDefinition(); - $aColumns[$this->GetCode().'_index'] = 'BLOB'; - return $aColumns; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if ($value instanceOf ormCaseLog) - { - $sContent = $value->GetAsHTML(null, false, array(__class__, 'RenderWikiHtml')); - } - else - { - $sContent = ''; - } - $aStyles = array(); - if ($this->GetWidth() != '') - { - $aStyles[] = 'width:'.$this->GetWidth(); - } - if ($this->GetHeight() != '') - { - $aStyles[] = 'height:'.$this->GetHeight(); - } - $sStyle = ''; - if (count($aStyles) > 0) - { - $sStyle = 'style="'.implode(';', $aStyles).'"'; - } - return "
".$sContent.'
'; } - - - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if ($value instanceOf ormCaseLog) - { - return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); - } - else - { - return ''; - } - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if ($value instanceOf ormCaseLog) - { - return parent::GetAsXML($value->GetText(), $oHostObject, $bLocalize); - } - else - { - return ''; - } - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Plain text representation of all the log entries', - 'head' => 'Plain text representation of the latest entry', - 'head_html' => 'HTML representation of the latest entry', - 'html' => 'HTML representation of all the log entries', - ); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param $value mixed The current value of the field - * @param $sVerb string The verb specifying the representation of the value - * @param $oHostObject DBObject The object - * @param $bLocalize bool Whether or not to localize the value - * - * @return mixed - * @throws \Exception - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - switch($sVerb) - { - case '': - return $value->GetText(true); - - case 'head': - return $value->GetLatestEntry('text'); - - case 'head_html': - return $value->GetLatestEntry('html'); - - case 'html': - return $value->GetAsEmailHtml(); - - default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ - public function GetForJSON($value) - { - return $value->GetForJSON(); - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - */ - public function FromJSONToValue($json) - { - if (is_string($json)) - { - // Will be correctly handled in MakeRealValue - $ret = $json; - } - else - { - if (isset($json->add_item)) - { - // Will be correctly handled in MakeRealValue - $ret = $json->add_item; - if (!isset($ret->message)) - { - throw new Exception("Missing mandatory entry: 'message'"); - } - } - else - { - $ret = ormCaseLog::FromJSON($json); - } - } - return $ret; - } - - public function Fingerprint($value) - { - $sFingerprint = ''; - if ($value instanceOf ormCaseLog) - { - $sFingerprint = $value->GetText(); - } - return $sFingerprint; - } - - /** - * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) - * @return string - */ - public function GetFormat() - { - return $this->GetOptional('format', 'html'); // default format for case logs is now HTML - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\CaseLogField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - // First we call the parent so the field is build - $oFormField = parent::MakeFormField($oObject, $oFormField); - // Then only we set the value - $oFormField->SetCurrentValue($this->GetEditValue($oObject->Get($this->GetCode()))); - // And we set the entries - $oFormField->SetEntries($oObject->Get($this->GetCode())->GetAsArray()); - - return $oFormField; - } -} - -/** - * Map a text column (size > ?), containing HTML code, to an attribute - * - * @package iTopORM - */ -class AttributeHTML extends AttributeLongText -{ - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->Get('sql')] = $this->GetSQLCol(); - if ($this->GetOptional('format', null) != null ) - { - // Add the extra column only if the property 'format' is specified for the attribute - $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; - if ($bFullSpec) - { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records - } - } - return $aColumns; - } - - /** - * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) - * @return string - */ - public function GetFormat() - { - return $this->GetOptional('format', 'html'); // Defaults to HTML - } -} - -/** - * Specialization of a string: email - * - * @package iTopORM - */ -class AttributeEmailAddress extends AttributeString -{ - public function GetValidationPattern() - { - return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('email_validation_pattern').'$'); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\EmailField'; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (empty($sValue)) return ''; - - $sUrlDecorationClass = utils::GetConfig()->Get('email_decoration_class'); - - return ''.parent::GetAsHTML($sValue).''; - } -} - -/** - * Specialization of a string: IP address - * - * @package iTopORM - */ -class AttributeIPAddress extends AttributeString -{ - public function GetValidationPattern() - { - $sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])'; - return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$"; - } - - public function GetOrderBySQLExpressions($sClassAlias) - { - // Note: This is the responsibility of this function to place backticks around column aliases - return array('INET_ATON(`'.$sClassAlias.$this->GetCode().'`)'); - } -} - -/** - * Specialization of a string: phone number - * - * @package iTopORM - */ -class AttributePhoneNumber extends AttributeString -{ - public function GetValidationPattern() - { - return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('phone_number_validation_pattern').'$'); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\PhoneField'; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if (empty($sValue)) return ''; - - $sUrlDecorationClass = utils::GetConfig()->Get('phone_number_decoration_class'); - $sUrlPattern = utils::GetConfig()->Get('phone_number_url_pattern'); - $sUrl = sprintf($sUrlPattern, $sValue); - - return ''.parent::GetAsHTML($sValue).''; - } -} - -/** - * Specialization of a string: OQL expression - * - * @package iTopORM - */ -class AttributeOQL extends AttributeText -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - - public function GetEditClass() {return "OQLExpression";} -} - -/** - * Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$) - * - * @package iTopORM - */ -class AttributeTemplateString extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; -} - -/** - * Specialization of a text: template (contains iTop placeholders like $current_contact_id$ or $this->name$) - * - * @package iTopORM - */ -class AttributeTemplateText extends AttributeText -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; -} - -/** - * Specialization of a HTML: template (contains iTop placeholders like $current_contact_id$ or $this->name$) - * - * @package iTopORM - */ -class AttributeTemplateHTML extends AttributeText -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->Get('sql')] = $this->GetSQLCol(); - if ($this->GetOptional('format', null) != null ) - { - // Add the extra column only if the property 'format' is specified for the attribute - $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; - if ($bFullSpec) - { - $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records - } - } - return $aColumns; - } - - /** - * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) - * @return string - */ - public function GetFormat() - { - return $this->GetOptional('format', 'html'); // Defaults to HTML - } -} - - -/** - * Map a enum column to an attribute - * - * @package iTopORM - */ -class AttributeEnum extends AttributeString -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM; - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "String";} - protected function GetSQLCol($bFullSpec = false) - { - $oValDef = $this->GetValuesDef(); - if ($oValDef) - { - $aValues = CMDBSource::Quote(array_keys($oValDef->GetValues(array(), "")), true); - } - else - { - $aValues = array(); - } - if (count($aValues) > 0) - { - // The syntax used here do matters - // In particular, I had to remove unnecessary spaces to - // make sure that this string will match the field type returned by the DB - // (used to perform a comparison between the current DB format and the data model) - return "ENUM(".implode(",", $aValues).")" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - else - { - return "VARCHAR(255)" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax! - } - } - - protected function GetSQLColSpec() - { - $default = $this->ScalarToSQL($this->GetDefaultValue()); - if (is_null($default)) - { - $sRet = ''; - } - else - { - // ENUMs values are strings so the default value must be a string as well, - // otherwise MySQL interprets the number as the zero-based index of the value in the list (i.e. the nth value in the list) - $sRet = " DEFAULT ".CMDBSource::Quote($default); - } - return $sRet; - } - - public function ScalarToSQL($value) - { - // Note: for strings, the null value is an empty string and it is recorded as such in the DB - // but that wasn't working for enums, because '' is NOT one of the allowed values - // that's why a null value must be forced to a real null - $value = parent::ScalarToSQL($value); - if ($this->IsNull($value)) - { - return null; - } - else - { - return $value; - } - } - - public function RequiresIndex() - { - return false; - } - - public function GetBasicFilterOperators() - { - return parent::GetBasicFilterOperators(); - } - public function GetBasicFilterLooseOperator() - { - return '='; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } - - public function GetValueLabel($sValue) - { - if (is_null($sValue)) - { - // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label - $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, Dict::S('Enum:Undefined')); - } - else - { - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/); - if (is_null($sLabel)) - { - $sDefault = str_replace('_', ' ', $sValue); - // Browse the hierarchy again, accepting default (english) translations - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false); - } - } - return $sLabel; - } - - public function GetValueDescription($sValue) - { - if (is_null($sValue)) - { - // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label - $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined')); - } - else - { - $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', '', true /* user language only */); - if (strlen($sDescription) == 0) - { - $sParentClass = MetaModel::GetParentClass($this->m_sHostClass); - if ($sParentClass) - { - if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) - { - $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode); - $sDescription = $oAttDef->GetValueDescription($sValue); - } - } - } - } - return $sDescription; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - if ($bLocalize) - { - $sLabel = $this->GetValueLabel($sValue); - $sDescription = $this->GetValueDescription($sValue); - // later, we could imagine a detailed description in the title - $sRes = "".parent::GetAsHtml($sLabel).""; - } - else - { - $sRes = parent::GetAsHtml($sValue, $oHostObject, $bLocalize); - } - return $sRes; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if (is_null($value)) - { - $sFinalValue = ''; - } - elseif ($bLocalize) - { - $sFinalValue = $this->GetValueLabel($value); - } - else - { - $sFinalValue = $value; - } - $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); - return $sRes; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (is_null($sValue)) - { - $sFinalValue = ''; - } - elseif ($bLocalize) - { - $sFinalValue = $this->GetValueLabel($sValue); - } - else - { - $sFinalValue = $sValue; - } - $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); - return $sRes; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\SelectField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - $oFormField->SetChoices($this->GetAllowedValues($oObject->ToArgsForQuery())); - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - public function GetEditValue($sValue, $oHostObj = null) - { - if (is_null($sValue)) - { - return ''; - } - else - { - return $this->GetValueLabel($sValue); - } - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ - public function GetForJSON($value) - { - return $value; - } - - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - $aRawValues = parent::GetAllowedValues($aArgs, $sContains); - if (is_null($aRawValues)) return null; - $aLocalizedValues = array(); - foreach ($aRawValues as $sKey => $sValue) - { - $aLocalizedValues[$sKey] = $this->GetValueLabel($sKey); - } - return $aLocalizedValues; - } - - public function GetMaxSize() - { - return null; - } - - /** - * An enum can be localized - */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - if ($bLocalizedValue) - { - // Lookup for the value matching the input - // - $sFoundValue = null; - $aRawValues = parent::GetAllowedValues(); - if (!is_null($aRawValues)) - { - foreach ($aRawValues as $sKey => $sValue) - { - $sRefValue = $this->GetValueLabel($sKey); - if ($sProposedValue == $sRefValue) - { - $sFoundValue = $sKey; - break; - } - } - } - if (is_null($sFoundValue)) - { - return null; - } - return $this->MakeRealValue($sFoundValue, null); - } - else - { - return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); - } - } - - /** - * Processes the input value to align it with the values supported - * by this type of attribute. In this case: turns empty strings into nulls - * @param mixed $proposedValue The value to be set for the attribute - * @return mixed The actual value that will be set - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - if ($proposedValue == '') return null; - return parent::MakeRealValue($proposedValue, $oHostObj); - } - - public function GetOrderByHint() - { - $aValues = $this->GetAllowedValues(); - - return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues)); - } -} - -/** - * A meta enum is an aggregation of enum from subclasses into an enum of a base class - * It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to - * have a superstatus available on the root classe(s) - * - * @package iTopORM - */ -class AttributeMetaEnum extends AttributeEnum -{ - static public function ListExpectedParams() - { - return array('allowed_values', 'sql', 'default_value', 'mapping'); - } - - public function IsNullAllowed() - { - return false; // Well... this actually depends on the mapping - } - - public function IsWritable() - { - return false; - } - - public function RequiresIndex() - { - return true; - } - - public function GetPrerequisiteAttributes($sClass = null) - { - if (is_null($sClass)) - { - $sClass = $this->GetHostClass(); - } - $aMappingData = $this->GetMapRule($sClass); - if ($aMappingData == null) - { - $aRet = array(); - } - else - { - $aRet = array($aMappingData['attcode']); - } - return $aRet; - } - - /** - * Overload the standard so as to leave the data unsorted - * - * @param array $aArgs - * @param string $sContains - * @return array|null - */ - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - $oValSetDef = $this->GetValuesDef(); - if (!$oValSetDef) return null; - $aRawValues = $oValSetDef->GetValueList(); - - if (is_null($aRawValues)) return null; - $aLocalizedValues = array(); - foreach ($aRawValues as $sKey => $sValue) - { - $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey)); - } - return $aLocalizedValues; - } - - /** - * Returns the meta value for the given object. - * See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes - * - * @param $oObject - * @return mixed - * @throws Exception - */ - public function MapValue($oObject) - { - $aMappingData = $this->GetMapRule(get_class($oObject)); - if ($aMappingData == null) - { - $sRet = $this->GetDefaultValue(); - } - else - { - $sAttCode = $aMappingData['attcode']; - $value = $oObject->Get($sAttCode); - if (array_key_exists($value, $aMappingData['values'])) - { - $sRet = $aMappingData['values'][$value]; - } - elseif ($this->GetDefaultValue() != '') - { - $sRet = $this->GetDefaultValue(); - } - else - { - throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()).'::'.$this->GetCode()); - } - } - return $sRet; - } - - public function GetMapRule($sClass) - { - $aMappings = $this->Get('mapping'); - if (array_key_exists($sClass, $aMappings)) - { - $aMappingData = $aMappings[$sClass]; - } - else - { - $sParent = MetaModel::GetParentClass($sClass); - if (is_null($sParent)) - { - $aMappingData = null; - } - else - { - $aMappingData = $this->GetMapRule($sParent); - } - } - - return $aMappingData; - } -} -/** - * Map a date+time column to an attribute - * - * @package iTopORM - */ -class AttributeDateTime extends AttributeDBField -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE_TIME; - - static $oFormat = null; - - /** - * - * @return DateTimeFormat - */ - static public function GetFormat() - { - if (self::$oFormat == null) - { - static::LoadFormatFromConfig(); - } - return self::$oFormat; - } - - /** - * Load the 3 settings: date format, time format and data_time format from the configuration - */ - protected static function LoadFormatFromConfig() - { - $aFormats = MetaModel::GetConfig()->Get('date_and_time_format'); - $sLang = Dict::GetUserLanguage(); - $sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d'); - $sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s'); - $sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time'); - - $sFullFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat); - - self::SetFormat(new DateTimeFormat($sFullFormat)); - AttributeDate::SetFormat(new DateTimeFormat($sDateFormat)); - } - - /** - * Returns the format string used for the date & time stored in memory - * @return string - */ - static public function GetInternalFormat() - { - return 'Y-m-d H:i:s'; - } - - /** - * Returns the format string used for the date & time written to MySQL - * @return string - */ - static public function GetSQLFormat() - { - return 'Y-m-d H:i:s'; - } - - static public function SetFormat(DateTimeFormat $oDateTimeFormat) - { - self::$oFormat = $oDateTimeFormat; - } - - static public function GetSQLTimeFormat() - { - return 'H:i:s'; - } - - /** - * Parses a search string coming from user input - * @param string $sSearchString - * @return string - */ - public function ParseSearchString($sSearchString) - { - try - { - $oDateTime = $this->GetFormat()->Parse($sSearchString); - $sSearchString = $oDateTime->format($this->GetInternalFormat()); - } - catch(Exception $e) - { - $sFormatString = '!'.(string)AttributeDate::GetFormat(); // BEWARE: ! is needed to set non-parsed fields to zero !!! - $oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString); - if ($oDateTime !== false) - { - $sSearchString = $oDateTime->format($this->GetInternalFormat()); - } - } - return $sSearchString; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\DateTimeField'; - } - - /** - * Override to specify Field class - * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - $oFormField->SetPHPDateTimeFormat((string) $this->GetFormat()); - $oFormField->SetJSDateTimeFormat($this->GetFormat()->ToMomentJS()); - - $oFormField = parent::MakeFormField($oObject, $oFormField); - - // After call to the parent as it sets the current value - $oFormField->SetCurrentValue($this->GetFormat()->Format($oObject->Get($this->GetCode()))); - - return $oFormField; - } - - /** - * @inheritdoc - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Formatted representation', - 'raw' => 'Not formatted representation', - ); - } - - /** - * @inheritdoc - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - switch ($sVerb) - { - case '': - case 'text': - return static::GetFormat()->format($value); - break; - case 'html': - // Note: Not passing formatted value as the method will format it. - return $this->GetAsHTML($value); - break; - case 'raw': - return $value; - break; - default: - return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize); - break; - } - } - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "DateTime";} - - - public function GetEditValue($sValue, $oHostObj = null) - { - return (string)static::GetFormat()->format($sValue); - } - public function GetValueLabel($sValue, $oHostObj = null) - { - return (string)static::GetFormat()->format($sValue); - } - - protected function GetSQLCol($bFullSpec = false) {return "DATETIME";} - - public function GetImportColumns() - { - // Allow an empty string to be a valid value (synonym for "reset") - $aColumns = array(); - $aColumns[$this->GetCode()] = 'VARCHAR(19)'; - return $aColumns; - } - - public static function GetAsUnixSeconds($value) - { - $oDeadlineDateTime = new DateTime($value); - $iUnixSeconds = $oDeadlineDateTime->format('U'); - return $iUnixSeconds; - } - - public function GetDefaultValue(DBObject $oHostObject = null) - { - // null value will be replaced by the current date, if not already set, in DoComputeValues - return $this->GetNullValue(); - } - - public function GetValidationPattern() - { - return static::GetFormat()->ToRegExpr(); - } - - public function GetBasicFilterOperators() - { - return array( - "="=>"equals", - "!="=>"differs from", - "<"=>"before", - "<="=>"before", - ">"=>"after (strictly)", - ">="=>"after", - "SameDay"=>"same day (strip time)", - "SameMonth"=>"same year/month", - "SameYear"=>"same year", - "Today"=>"today", - ">|"=>"after today + N days", - "<|"=>"before today + N days", - "=|"=>"equals today + N days", - ); - } - public function GetBasicFilterLooseOperator() - { - // Unless we implement a "same xxx, depending on given precision" ! - return "="; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - - switch ($sOpCode) - { - case '=': - case '!=': - case '<': - case '<=': - case '>': - case '>=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'SameDay': - return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)"; - case 'SameMonth': - return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; - case 'SameYear': - return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; - case 'Today': - return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; - case '>|': - return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - case '<|': - return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - case '=|': - return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; - default: - return $this->GetSQLExpr()." = $sQValue"; - } - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) - { - return null; - } - if (is_string($proposedValue) && ($proposedValue == "") && $this->IsNullAllowed()) - { - return null; - } - if (!is_numeric($proposedValue)) - { - // Check the format - try - { - $oFormat = new DateTimeFormat($this->GetInternalFormat()); - $oFormat->Parse($proposedValue); - } - catch (Exception $e) - { - throw new Exception('Wrong format for date attribute '.$this->GetCode().', expecting "'.$this->GetInternalFormat().'" and got "'.$proposedValue.'"'); - } - - return $proposedValue; - } - - return date(static::GetInternalFormat(), $proposedValue); - } - - public function ScalarToSQL($value) - { - if (empty($value)) - { - return null; - } - return $value; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - return Str::pure2html(static::GetFormat()->format($value)); - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - return Str::pure2xml($value); - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00')) - { - return ''; - } - else if ((string)static::GetFormat() !== static::GetInternalFormat()) - { - // Format conversion - $oDate = new DateTime($sValue); - if ($oDate !== false) - { - $sValue = static::GetFormat()->format($oDate); - } - } - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - } - - /** - * Parses a string to find some smart search patterns and build the corresponding search/OQL condition - * Each derived class is reponsible for defining and processing their own smart patterns, the base class - * does nothing special, and just calls the default (loose) operator - * - * @param string $sSearchText The search string to analyze for smart patterns - * @param FieldExpression $oField The FieldExpression representing the atttribute code in this OQL query - * @param array $aParams Values of the query parameters - * @param bool $bParseSearchString - * - * @return Expression The search condition to be added (AND) to the current search - * @throws \CoreException - */ - public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false) - { - // Possible smart patterns - $aPatterns = array( - 'between' => array('pattern' => '/^\[(.*),(.*)\]$/', 'operator' => 'n/a'), - 'greater than or equal' => array('pattern' => '/^>=(.*)$/', 'operator' => '>='), - 'greater than' => array('pattern' => '/^>(.*)$/', 'operator' => '>'), - 'less than or equal' => array('pattern' => '/^<=(.*)$/', 'operator' => '<='), - 'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'), - ); - - $sPatternFound = ''; - $aMatches = array(); - foreach($aPatterns as $sPatName => $sPattern) - { - if (preg_match($sPattern['pattern'], $sSearchText, $aMatches)) - { - $sPatternFound = $sPatName; - break; - } - } - - switch($sPatternFound) - { - case 'between': - - $sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1'; - $oRightExpr = new VariableExpression($sParamName1); - if ($bParseSearchString) - { - $aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]); - } - else - { - $aParams[$sParamName1] = $aMatches[1]; - } - $oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr); - - $sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2'; - $oRightExpr = new VariableExpression($sParamName2); - if ($bParseSearchString) - { - $aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]); - } - else - { - $aParams[$sParamName2] = $aMatches[2]; - } - $oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr); - - $oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2); - break; - - case 'greater than': - case 'greater than or equal': - case 'less than': - case 'less than or equal': - $sSQLOperator = $aPatterns[$sPatternFound]['operator']; - $sParamName = $oField->GetParent().'_'.$oField->GetName(); - $oRightExpr = new VariableExpression($sParamName); - if ($bParseSearchString) - { - $aParams[$sParamName] = $this->ParseSearchString($aMatches[1]); - } - else - { - $aParams[$sParamName] = $aMatches[1]; - } - $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); - - break; - - default: - $oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams); - - } - - return $oNewCondition; - } - - - public function GetHelpOnSmartSearch() - { - $sDict = parent::GetHelpOnSmartSearch(); - - $oFormat = static::GetFormat(); - $sExample = $oFormat->Format(new DateTime('2015-07-19 18:40:00')); - return vsprintf($sDict, array($oFormat->ToPlaceholder(), $sExample)); - } -} - -/** - * Store a duration as a number of seconds - * - * @package iTopORM - */ -class AttributeDuration extends AttributeInteger -{ - public function GetEditClass() {return "Duration";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11) UNSIGNED";} - - public function GetNullValue() {return '0';} - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return null; - if (!is_numeric($proposedValue)) return null; - if ( ((int)$proposedValue) < 0) return null; - - return (int)$proposedValue; - } - - public function ScalarToSQL($value) - { - if (is_null($value)) - { - return null; - } - return $value; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - return Str::pure2html(self::FormatDuration($value)); - } - - public static function FormatDuration($duration) - { - $aDuration = self::SplitDuration($duration); - - if ($duration < 60) - { - // Less than 1 min - $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']); - } - else if ($duration < 3600) - { - // less than 1 hour, display it in minutes/seconds - $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']); - } - else if ($duration < 86400) - { - // Less than 1 day, display it in hours/minutes/seconds - $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); - } - else - { - // more than 1 day, display it in days/hours/minutes/seconds - $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); - } - return $sResult; - } - - static function SplitDuration($duration) - { - $duration = (int) $duration; - $days = floor($duration / 86400); - $hours = floor(($duration - (86400*$days)) / 3600); - $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); - $seconds = ($duration % 60); // modulo - return array( 'days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds ); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\DurationField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); - - // Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition - $sAttCode = $this->GetCode(); - $oFormField->SetCurrentValue($oObject->Get($sAttCode)); - $oFormField->SetReadOnly(true); - - return $oFormField; - } - -} -/** - * Map a date+time column to an attribute - * - * @package iTopORM - */ -class AttributeDate extends AttributeDateTime -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE; - - static $oDateFormat = null; - - static public function GetFormat() - { - if (self::$oDateFormat == null) - { - AttributeDateTime::LoadFormatFromConfig(); - } - return self::$oDateFormat; - } - - static public function SetFormat(DateTimeFormat $oDateFormat) - { - self::$oDateFormat = $oDateFormat; - } - - /** - * Returns the format string used for the date & time stored in memory - * @return string - */ - static public function GetInternalFormat() - { - return 'Y-m-d'; - } - - /** - * Returns the format string used for the date & time written to MySQL - * @return string - */ - static public function GetSQLFormat() - { - return 'Y-m-d'; - } - - static public function ListExpectedParams() - { - return parent::ListExpectedParams(); - //return array_merge(parent::ListExpectedParams(), array()); - } - - public function GetEditClass() {return "Date";} - protected function GetSQLCol($bFullSpec = false) {return "DATE";} - public function GetImportColumns() - { - // Allow an empty string to be a valid value (synonym for "reset") - $aColumns = array(); - $aColumns[$this->GetCode()] = 'VARCHAR(10)'; - return $aColumns; - } - - - /** - * Override to specify Field class - * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - $oFormField = parent::MakeFormField($oObject, $oFormField); - $oFormField->SetDateOnly(true); - - return $oFormField; - } - -} - -/** - * A dead line stored as a date & time - * The only difference with the DateTime attribute is the display: - * relative to the current time - */ -class AttributeDeadline extends AttributeDateTime -{ - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - $sResult = self::FormatDeadline($value); - return $sResult; - } - - public static function FormatDeadline($value) - { - $sResult = ''; - if ($value !== null) - { - $iValue = AttributeDateTime::GetAsUnixSeconds($value); - $sDate = AttributeDateTime::GetFormat()->Format($value); - $difference = $iValue - time(); - - if ($difference >= 0) - { - $sDifference = self::FormatDuration($difference); - } - else - { - $sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference)); - } - $sFormat = MetaModel::GetConfig()->Get('deadline_format'); - $sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat); - } - - return $sResult; - } - - static function FormatDuration($duration) - { - $days = floor($duration / 86400); - $hours = floor(($duration - (86400*$days)) / 3600); - $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); - - if ($duration < 60) - { - // Less than 1 min - $sResult =Dict::S('UI:Deadline_LessThan1Min'); - } - else if ($duration < 3600) - { - // less than 1 hour, display it in minutes - $sResult =Dict::Format('UI:Deadline_Minutes', $minutes); - } - else if ($duration < 86400) - { - // Less that 1 day, display it in hours/minutes - $sResult =Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes); - } - else - { - // Less that 1 day, display it in hours/minutes - $sResult =Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes); - } - return $sResult; - } -} - -/** - * Map a foreign key to an attribute - * AttributeExternalKey and AttributeExternalField may be an external key - * the difference is that AttributeExternalKey corresponds to a column into the defined table - * where an AttributeExternalField corresponds to a column into another table (class) - * - * @package iTopORM - */ -class AttributeExternalKey extends AttributeDBFieldVoid -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; - - - /** - * Return the search widget type corresponding to this attribute - * - * @return string - */ - public function GetSearchType() - { - try - { - $oRemoteAtt = $this->GetFinalAttDef(); - $sTargetClass = $oRemoteAtt->GetTargetClass(); - if (MetaModel::IsHierarchicalClass($sTargetClass)) - { - return self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY; - } - return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; - } - catch (CoreException $e) - { - } - - return self::SEARCH_WIDGET_TYPE_RAW; - } - - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete")); - } - - public function GetEditClass() {return "ExtKey";} - protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} - public function RequiresIndex() - { - return true; - } - - public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} - public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} - public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} - public function GetKeyAttCode() {return $this->GetCode();} - public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); } - - - public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} - public function IsNullAllowed() - { - if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys')) - { - return true; - } - return $this->Get("is_null_allowed"); - } - - - public function GetBasicFilterOperators() - { - return parent::GetBasicFilterOperators(); - } - public function GetBasicFilterLooseOperator() - { - return parent::GetBasicFilterLooseOperator(); - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return parent::GetBasicFilterSQLExpr($sOpCode, $value); - } - - // overloaded here so that an ext key always have the answer to - // "what are your possible values?" - public function GetValuesDef() - { - $oValSetDef = $this->Get("allowed_values"); - if (!$oValSetDef) - { - // Let's propose every existing value - $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); - } - return $oValSetDef; - } - - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - //throw new Exception("GetAllowedValues on ext key has been deprecated"); - try - { - return parent::GetAllowedValues($aArgs, $sContains); - } - catch (Exception $e) - { - // Some required arguments could not be found, enlarge to any existing value - $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); - return $oValSetDef->GetValues($aArgs, $sContains); - } - } - - public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null) - { - $oValSetDef = $this->GetValuesDef(); - $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); - return $oSet; - } - - public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) - { - return DBObjectSearch::FromOQL($this->GetValuesDef()->GetFilterExpression()); - } - - public function GetDeletionPropagationOption() - { - return $this->Get("on_target_delete"); - } - - public function GetNullValue() - { - return 0; - } - - public function IsNull($proposedValue) - { - return ($proposedValue == 0); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return 0; - if ($proposedValue === '') return 0; - if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey(); - return (int)$proposedValue; - } - - public function GetMaximumComboLength() - { - return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length')); - } - - public function GetMinAutoCompleteChars() - { - return $this->GetOptional('min_autocomplete_chars', MetaModel::GetConfig()->Get('min_autocomplete_chars')); - } - - public function AllowTargetCreation() - { - return $this->GetOptional('allow_target_creation', MetaModel::GetConfig()->Get('allow_target_creation')); - } - - /** - * Find the corresponding "link" attribute on the target class, if any - * @return null | AttributeDefinition - * @throws \CoreException - */ - public function GetMirrorLinkAttribute() - { - $oRet = null; - $sRemoteClass = $this->GetTargetClass(); - foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) - { - if (!$oRemoteAttDef->IsLinkSet()) continue; - if (!is_subclass_of($this->GetHostClass(), $oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) continue; - if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) continue; - $oRet = $oRemoteAttDef; - break; - } - return $oRet; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\SelectObjectField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - // Setting params - $oFormField->SetMaximumComboLength($this->GetMaximumComboLength()); - $oFormField->SetMinAutoCompleteChars($this->GetMinAutoCompleteChars()); - $oFormField->SetHierarchical(MetaModel::IsHierarchicalClass($this->GetTargetClass())); - // Setting choices regarding the field dependencies - $aFieldDependencies = $this->GetPrerequisiteAttributes(); - if (!empty($aFieldDependencies)) - { - $oTmpAttDef = $this; - $oTmpField = $oFormField; - $oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject) - { - /** @var $oTmpField \Combodo\iTop\Form\Field\Field */ - /** @var $oTmpAttDef \AttributeDefinition */ - /** @var $oObject \DBObject */ - - // We set search object only if it has not already been set (overrided) - if ($oTmpField->GetSearch() === null) - { - $oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression()); - $oSearch->SetInternalParams(array('this' => $oObject)); - $oTmpField->SetSearch($oSearch); - } - }); - } - else - { - $oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression()); - $oSearch->SetInternalParams(array('this' => $oObject)); - $oFormField->SetSearch($oSearch); - } - - // If ExtKey is mandatory, we add a validator to ensure that the value 0 is not selected - if ($oObject->GetAttributeFlags($this->GetCode()) & OPT_ATT_MANDATORY) - { - $oFormField->AddValidator(new \Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator()); - } - - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - -} - -/** - * Special kind of External Key to manage a hierarchy of objects - */ -class AttributeHierarchicalKey extends AttributeExternalKey -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY; - - protected $m_sTargetClass; - - static public function ListExpectedParams() - { - $aParams = parent::ListExpectedParams(); - $idx = array_search('targetclass', $aParams); - unset($aParams[$idx]); - $idx = array_search('jointype', $aParams); - unset($aParams[$idx]); - return $aParams; // Later: mettre les bons parametres ici !! - } - - public function GetEditClass() {return "ExtKey";} - public function RequiresIndex() - { - return true; - } - - /* - * The target class is the class for which the attribute has been defined first - */ - public function SetHostClass($sHostClass) - { - if (!isset($this->m_sTargetClass)) - { - $this->m_sTargetClass = $sHostClass; - } - parent::SetHostClass($sHostClass); - } - - static public function IsHierarchicalKey() {return true;} - public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->m_sTargetClass;} - public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} - public function GetKeyAttCode() {return $this->GetCode();} - - public function GetBasicFilterOperators() - { - return parent::GetBasicFilterOperators(); - } - public function GetBasicFilterLooseOperator() - { - return parent::GetBasicFilterLooseOperator(); - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->GetCode()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); - $aColumns[$this->GetSQLLeft()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); - $aColumns[$this->GetSQLRight()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); - return $aColumns; - } - public function GetSQLRight() - { - return $this->GetCode().'_right'; - } - public function GetSQLLeft() - { - return $this->GetCode().'_left'; - } - - public function GetSQLValues($value) - { - if (!is_array($value)) - { - $aValues[$this->GetCode()] = $value; - } - else - { - $aValues = array(); - $aValues[$this->GetCode()] = $value[$this->GetCode()]; - $aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()]; - $aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()]; - } - return $aValues; - } - - public function GetAllowedValues($aArgs = array(), $sContains = '') - { - $oFilter = $this->GetHierachicalFilter($aArgs, $sContains); - if ($oFilter) - { - $oValSetDef = $this->GetValuesDef(); - $oValSetDef->AddCondition($oFilter); - return $oValSetDef->GetValues($aArgs, $sContains); - } - else - { - return parent::GetAllowedValues($aArgs, $sContains); - } - } - - public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null) - { - $oValSetDef = $this->GetValuesDef(); - $oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue); - if ($oFilter) - { - $oValSetDef->AddCondition($oFilter); - } - $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); - return $oSet; - } - - public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) - { - $oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue); - if ($oFilter) - { - return $oFilter; - } - return parent::GetAllowedValuesAsFilter($aArgs, $sContains, $iAdditionalValue); - } - - private function GetHierachicalFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) - { - if (array_key_exists('this', $aArgs)) - { - // Hierarchical keys have one more constraint: the "parent value" cannot be - // "under" themselves - $iRootId = $aArgs['this']->GetKey(); - if ($iRootId > 0) // ignore objects that do no exist in the database... - { - $sClass = $this->m_sTargetClass; - return DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId"); - } - } - return false; - } - - /** - * Find the corresponding "link" attribute on the target class, if any - * @return null | AttributeDefinition - */ - public function GetMirrorLinkAttribute() - { - return null; - } -} - -/** - * An attribute which corresponds to an external key (direct or indirect) - * - * @package iTopORM - */ -class AttributeExternalField extends AttributeDefinition -{ - /** - * Return the search widget type corresponding to this attribute - * - * @return string - * @throws \CoreException - */ - public function GetSearchType() - { - // Not necessary the external key is already present - if ($this->IsFriendlyName()) - { - return self::SEARCH_WIDGET_TYPE_RAW; - } - - try - { - $oRemoteAtt = $this->GetFinalAttDef(); - switch (true) - { - case ($oRemoteAtt instanceof AttributeString): - return self::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD; - case ($oRemoteAtt instanceof AttributeExternalKey): - return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; - } - } - catch (CoreException $e) - { - } - - return self::SEARCH_WIDGET_TYPE_RAW; - } - - - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode")); - } - - public function GetEditClass() {return "ExtField";} - - /** - * @return \AttributeDefinition - * @throws \CoreException - */ - public function GetFinalAttDef() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetFinalAttDef(); - } - - protected function GetSQLCol($bFullSpec = false) - { - // throw new CoreException("external attribute: does it make any sense to request its type ?"); - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetSQLCol($bFullSpec); - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - return array('' => $this->GetCode()); // Warning: Use GetCode() since AttributeExternalField does not have any 'sql' property - } - else - { - return $sPrefix; - } - } - - public function GetLabel($sDefault = null) - { - if ($this->IsFriendlyName()) - { - $sKeyAttCode = $this->Get("extkey_attcode"); - $oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode); - $sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode); - } - else - { - $sLabel = parent::GetLabel(''); - if (strlen($sLabel) == 0) - { - $oRemoteAtt = $this->GetExtAttDef(); - $sLabel = $oRemoteAtt->GetLabel($this->m_sCode); - } - } - 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(''); - if (strlen($sLabel) == 0) - { - $oRemoteAtt = $this->GetExtAttDef(); - $sLabel = $oRemoteAtt->GetDescription(''); - } - return $sLabel; - } - public function GetHelpOnEdition($sDefault = null) - { - $sLabel = parent::GetHelpOnEdition(''); - if (strlen($sLabel) == 0) - { - $oRemoteAtt = $this->GetExtAttDef(); - $sLabel = $oRemoteAtt->GetHelpOnEdition(''); - } - return $sLabel; - } - - public function IsExternalKey($iType = EXTKEY_RELATIVE) - { - switch($iType) - { - case EXTKEY_ABSOLUTE: - // see further - $oRemoteAtt = $this->GetExtAttDef(); - return $oRemoteAtt->IsExternalKey($iType); - - case EXTKEY_RELATIVE: - return false; - - default: - throw new CoreException("Unexpected value for argument iType: '$iType'"); - } - } - - /** - * @return bool - * @throws \CoreException - */ - public function IsFriendlyName() - { - $oRemoteAtt = $this->GetExtAttDef(); - if ($oRemoteAtt instanceof AttributeExternalField) - { - $bRet = $oRemoteAtt->IsFriendlyName(); - } - elseif ($oRemoteAtt instanceof AttributeFriendlyName) - { - $bRet = true; - } - else - { - $bRet = false; - } - return $bRet; - } - - public function GetTargetClass($iType = EXTKEY_RELATIVE) - { - return $this->GetKeyAttDef($iType)->GetTargetClass(); - } - - static public function IsExternalField() {return true;} - - public function GetKeyAttCode() - { - return $this->Get("extkey_attcode"); - } - - public function GetExtAttCode() - { - return $this->Get("target_attcode"); - } - - /** - * @param int $iType - * - * @return \AttributeExternalKey - * @throws \CoreException - * @throws \Exception - */ - public function GetKeyAttDef($iType = EXTKEY_RELATIVE) - { - switch($iType) - { - case EXTKEY_ABSOLUTE: - // see further - /** @var \AttributeExternalKey $oRemoteAtt */ - $oRemoteAtt = $this->GetExtAttDef(); - if ($oRemoteAtt->IsExternalField()) - { - return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE); - } - else if ($oRemoteAtt->IsExternalKey()) - { - return $oRemoteAtt; - } - return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! - - case EXTKEY_RELATIVE: - /** @var \AttributeExternalKey $oAttDef */ - $oAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); - return $oAttDef; - - default: - throw new CoreException("Unexpected value for argument iType: '$iType'"); - } - } - - public function GetPrerequisiteAttributes($sClass = null) - { - return array($this->Get("extkey_attcode")); - } - - - /** - * @return \AttributeExternalField - * @throws \CoreException - * @throws \Exception - */ - public function GetExtAttDef() - { - $oKeyAttDef = $this->GetKeyAttDef(); - /** @var \AttributeExternalField $oExtAttDef */ - $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode")); - if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode")); - return $oExtAttDef; - } - - /** - * @return mixed - * @throws \CoreException - */ - public function GetSQLExpr() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetSQLExpr(); - } - - public function GetDefaultValue(DBObject $oHostObject = null) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetDefaultValue(); - } - public function IsNullAllowed() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->IsNullAllowed(); - } - - static public function IsScalar() - { - return true; - } - - public function GetFilterDefinitions() - { - return array($this->GetCode() => new FilterFromAttribute($this)); - } - - public function GetBasicFilterOperators() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterOperators(); - } - public function GetBasicFilterLooseOperator() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterLooseOperator(); - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); - } - - public function GetNullValue() - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetNullValue(); - } - - public function IsNull($proposedValue) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->IsNull($proposedValue); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj); - } - - public function ScalarToSQL($value) - { - // This one could be used in case of filtering only - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->ScalarToSQL($value); - } - - - // Do not overload GetSQLExpression here because this is handled in the joins - //public function GetSQLExpressions($sPrefix = '') {return array();} - - // Here, we get the data... - public function FromSQLToValue($aCols, $sPrefix = '') - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->FromSQLToValue($aCols, $sPrefix); - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetAsHTML($value, null, $bLocalize); - } - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetAsXML($value, null, $bLocalize); - } - public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $oExtAttDef = $this->GetExtAttDef(); - return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\LabelField'; - } - - /** - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\Field $oFormField - * - * @return null - * @throws \CoreException - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - // Retrieving AttDef from the remote attribute - $oRemoteAttDef = $this->GetExtAttDef(); - - if ($oFormField === null) - { - // ExternalField's FormField are actually based on the FormField from the target attribute. - // Except for the AttributeExternalKey because we have no OQL and stuff - if($oRemoteAttDef instanceof AttributeExternalKey) - { - $sFormFieldClass = static::GetFormFieldClass(); - } - else - { - $sFormFieldClass = $oRemoteAttDef::GetFormFieldClass(); - } - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); - - // Manually setting for remote ExternalKey, otherwise, the id would be displayed. - if($oRemoteAttDef instanceof AttributeExternalKey) - { - $oFormField->SetCurrentValue($oObject->Get($this->GetCode().'_friendlyname')); - } - - // Readonly field because we can't update external fields - $oFormField->SetReadOnly(true); - - return $oFormField; - } - - public function IsPartOfFingerprint() - { - return false; - } - -} - - -/** - * Multi value list of tags - * - * @see TagSetFieldData - * @since 2.6 N°931 tag fields - */ -class AttributeTagSet extends AttributeDBFieldVoid -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("is_null_allowed")); - } - public function GetDefaultValue(DBObject $oHostObject = null) {return null;} - public function IsNullAllowed() {return $this->Get("is_null_allowed");} - - public function GetEditClass() { - return "String"; - } - - public function GetEditValue($value, $oHostObj = null) - { - if (empty($value)) - { - return ''; - } - if ($value instanceof ormTagSet) - { - $aValues = $value->GetValue(); - return implode(' ', $aValues); - } - return ''; - } - - protected function GetSQLCol($bFullSpec = false) - { - return 'VARCHAR(255)' - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - public function GetMaxSize() - { - return 255; - } - - public function RequiresIndex() {return true;} - - public function RequiresFullTextIndex() {return true;} - - public function Equals($val1, $val2) { - if (($val1 instanceof ormTagSet) && ($val2 instanceof ormTagSet)) - { - return $val1->Equals($val2); - } - return ($val1 == $val2); - } - - /** - * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! - * - * @param $proposedValue - * @param $oHostObj - * - * @return mixed - * @throws \Exception - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); - if (is_string($proposedValue) && !empty($proposedValue)) - { - $aTagCodes = explode(' ', "$proposedValue"); - $oTagSet->SetValue($aTagCodes); - } - elseif ($proposedValue instanceof ormTagSet) - { - $oTagSet = $proposedValue; - } - return $oTagSet; - } - - /** - * Get the value from a given string (plain text, CSV import) - * - * @param string $sProposedValue - * @param bool $bLocalizedValue - * @param string $sSepItem - * @param string $sSepAttribute - * @param string $sSepValue - * @param string $sAttributeQualifier - * - * @return mixed null if no match could be found - * @throws \Exception - */ - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - // TODO $bLocalizedValue - return $this->MakeRealValue($sProposedValue, null); - } - - public function GetNullValue() { - return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); - } - - public function IsNull($proposedValue) - { - /** @var \ormTagSet $proposedValue */ - return count($proposedValue->GetValue()) == 0; - } - - /** - * To be overloaded for localized enums - * - * @param $sValue - * - * @return string label corresponding to the given value (in plain text) - */ - public function GetValueLabel($sValue) - { - // TODO - return $sValue; - } - - /** - * @param $value - * - * @return string - * @throws \CoreWarning - */ - public function ScalarToSQL($value) - { - if (empty($value)) - { - return ''; - } - if ($value instanceof ormTagSet) - { - $aValues = $value->GetValue(); - return implode(' ', $aValues); - } - throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); - } - - /** - * @param $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string|null - * - * @throws \CoreException - * @throws \Exception - */ - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if ($value instanceof ormTagSet) - { - if ($bLocalize) - { - $aValues = $value->GetLabel(); - } - else - { - $aValues = $value->GetValue(); - } - if (empty($aValues)) - { - return ''; - } - return ''.implode('', $aValues).''; - } - if (is_string($value)) - { - try - { - $oValue = $this->MakeRealValue($value, $oHostObject); - return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); - } - catch (Exception $e) - { - // unknown tags are present display the code instead - } - $aTagCodes = explode(' ', $value); - $aValues = array(); - $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); - foreach ($aTagCodes as $sTagCode) - { - try - { - $oTagSet->AddTag($sTagCode); - } - catch (Exception $e) - { - $aValues[] = $sTagCode; - } - } - $sHTML = ''; - if (!empty($aValues)) - { - $sHTML .= ''.implode('', $aValues).''; - } - $aValues = $oTagSet->GetLabel(); - if (!empty($aValues)) - { - $sHTML .= ''.implode('', $aValues).''; - } - - return $sHTML; - } - return parent::GetAsHTML($value, $oHostObject, $bLocalize); - - } - - /** - * @param $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - * - */ - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if (is_object($value) && ($value instanceof ormTagSet)) - { - $sRes = "\n"; - if ($bLocalize) - { - $aValues = $value->GetLabel(); - } - else - { - $aValues = $value->GetValue(); - } if (!empty($aValuess)) - { - $sRes .= ''.implode('', $aValues).''; - } - $sRes .= "\n"; - } - else - { - $sRes = ''; - } - return $sRes; - } - - /** - * @param $value - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return mixed|string - * @throws \CoreException - */ - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (is_object($value) && ($value instanceof ormTagSet)) - { - if ($bLocalize) - { - $aValues = $value->GetLabel(); - } - else - { - $aValues = $value->GetValue(); - } - $sRes = implode('|', $aValues); - } - else - { - $sRes = ''; - } - return $sRes; - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - return array( - '' => 'Plain text (unlocalized) representation', - 'html' => 'HTML representation (unordered list)', - ); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param mixed $value The current value of the field - * @param string $sVerb The verb specifying the representation of the value - * @param DBObject $oHostObject The object - * @param bool $bLocalize Whether or not to localize the value - * - * @return string - * @throws \Exception - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - if (is_object($value) && ($value instanceof ormTagSet)) - { - if ($bLocalize) - { - $aValues = $value->GetLabel(); - } - else - { - $aValues = $value->GetValue(); - } - - switch ($sVerb) - { - case '': - return implode(' ', $aValues); - - case 'html': - return '
  • '.implode("
  • ", $aValues).'
'; - - default: - throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - } - throw new CoreUnexpectedValue("Bad value '$value' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param \ormTagSet $value - * - * @return array - * @throws \CoreException - */ - public function GetForJSON($value) - { - $aRet = array(); - if (is_object($value) && ($value instanceof ormTagSet)) - { - $aRet = $value->GetValue(); - } - return $aRet; - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - * - * @param $json - * - * @return \ormTagSet - * @throws \CoreException - * @throws \CoreUnexpectedValue - * @throws \Exception - */ - public function FromJSONToValue($json) - { - $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode()); - $oSet->SetValue($json); - return $oSet; - } - - /** - * The part of the current attribute in the object's signature, for the supplied value - * @param mixed $value The value of this attribute for the object - * @return string The "signature" for this field/attribute - */ - public function Fingerprint($value) - { - if ($value instanceof ormTagSet) - { - $aValues = $value->GetValue(); - return implode(' ', $aValues); - } - return parent::Fingerprint($value); - } - -} - -/** - * Map a varchar column to an URL (formats the ouput in HMTL) - * - * @package iTopORM - */ -class AttributeURL extends AttributeString -{ - static public function ListExpectedParams() - { - //return parent::ListExpectedParams(); - return array_merge(parent::ListExpectedParams(), array("target")); - } - - protected function GetSQLCol($bFullSpec = false) - { - return "VARCHAR(2048)" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - public function GetMaxSize() - { - return 2048; - } - - public function GetEditClass() {return "String";} - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - $sTarget = $this->Get("target"); - if (empty($sTarget)) $sTarget = "_blank"; - $sLabel = Str::pure2html($sValue); - if (strlen($sLabel) > 128) - { - // Truncate the length to 128 characters, by removing the middle - $sLabel = substr($sLabel, 0, 100).'.....'.substr($sLabel, -20); - } - return "$sLabel"; - } - - public function GetValidationPattern() - { - return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('url_validation_pattern').'$'); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\UrlField'; - } - - /** - * @param \DBObject $oObject - * @param \Combodo\iTop\Form\Field\UrlField $oFormField - * - * @return null - * @throws \CoreException - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); - - $oFormField->SetTarget($this->Get('target')); - - return $oFormField; - } -} - -/** - * A blob is an ormDocument, it is stored as several columns in the database - * - * @package iTopORM - */ -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")); - } - - public function GetEditClass() {return "Document";} - - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return "";} - public function IsNullAllowed(DBObject $oHostObject = null) {return $this->GetOptional("is_null_allowed", false);} - - public function GetEditValue($sValue, $oHostObj = null) - { - return ''; - } - - /** - * Users can provide the document from an URL (including an URL on iTop itself) - * for CSV import. Administrators can even provide the path to a local file - * {@inheritDoc} - * @see AttributeDefinition::MakeRealValue() - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - if ($proposedValue === null) return null; - - if (is_object($proposedValue)) - { - $proposedValue = clone $proposedValue; - } - else - { - try - { - // Read the file from iTop, an URL (or the local file system - for admins only) - $proposedValue = Utils::FileGetContentsAndMIMEType($proposedValue); - } - catch(Exception $e) - { - IssueLog::Warning(get_class($this)."::MakeRealValue - ".$e->getMessage()); - // Not a real document !! store is as text !!! (This was the default behavior before) - $proposedValue = new ormDocument($e->getMessage()." \n".$proposedValue, 'text/plain'); - } - } - return $proposedValue; - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->GetCode(); - } - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $sPrefix.'_mimetype'; - $aColumns['_data'] = $sPrefix.'_data'; - $aColumns['_filename'] = $sPrefix.'_filename'; - return $aColumns; - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - if (!array_key_exists($sPrefix, $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } - $sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; - - if (!array_key_exists($sPrefix.'_data', $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}"); - } - $data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null; - - if (!array_key_exists($sPrefix.'_filename', $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}"); - } - $sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : ''; - - $value = new ormDocument($data, $sMimeType, $sFileName); - return $value; - } - - public function GetSQLValues($value) - { - // #@# Optimization: do not load blobs anytime - // As per mySQL doc, selecting blob columns will prevent mySQL from - // using memory in case a temporary table has to be created - // (temporary tables created on disk) - // We will have to remove the blobs from the list of attributes when doing the select - // then the use of Get() should finalize the load - if ($value instanceOf ormDocument && !$value->IsEmpty()) - { - $aValues = array(); - $aValues[$this->GetCode().'_data'] = $value->GetData(); - $aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType(); - $aValues[$this->GetCode().'_filename'] = $value->GetFileName(); - } - else - { - $aValues = array(); - $aValues[$this->GetCode().'_data'] = ''; - $aValues[$this->GetCode().'_mimetype'] = ''; - $aValues[$this->GetCode().'_filename'] = ''; - } - return $aValues; - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb) - $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); - $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); - return $aColumns; - } - - public function GetFilterDefinitions() - { - return array(); - } - - public function GetBasicFilterOperators() - { - return array(); - } - public function GetBasicFilterLooseOperator() - { - return '='; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return 'true'; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if (is_object($value)) - { - return $value->GetAsHTML(); - } - return ''; - } - - /** - * @param string $sValue - * @param string $sSeparator - * @param string $sTextQualifier - * @param \DBObject $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return string - */ - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $sAttCode = $this->GetCode(); - if ($sValue instanceof ormDocument && !$sValue->IsEmpty()) - { - return $sValue->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $sAttCode); - } - return ''; // Not exportable in CSV ! - } - - /** - * @param $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return mixed|string - */ - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - $sRet = ''; - if (is_object($value)) - { - if (!$value->IsEmpty()) - { - $sRet = ''.$value->GetMimeType().''; - $sRet .= ''.$value->GetFileName().''; - $sRet .= ''.base64_encode($value->GetData()).''; - } - } - return $sRet; - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - */ - public function GetForJSON($value) - { - if ($value instanceOf ormDocument) - { - $aValues = array(); - $aValues['data'] = base64_encode($value->GetData()); - $aValues['mimetype'] = $value->GetMimeType(); - $aValues['filename'] = $value->GetFileName(); - } - else - { - $aValues = null; - } - return $aValues; - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - */ - public function FromJSONToValue($json) - { - if (isset($json->data)) - { - $data = base64_decode($json->data); - $value = new ormDocument($data, $json->mimetype, $json->filename); - } - else - { - $value = null; - } - return $value; - } - - public function Fingerprint($value) - { - $sFingerprint = ''; - if ($value instanceOf ormDocument) - { - $sFingerprint = md5($value->GetData()); - } - return $sFingerprint; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\BlobField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - // Note: As of today we want this field to always be read-only - $oFormField->SetReadOnly(true); - - // Generating urls - $value = $oObject->Get($this->GetCode()); - $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - -} - -/** - * An image is a specific type of document, it is stored as several columns in the database - * - * @package iTopORM - */ -class AttributeImage extends AttributeBlob -{ - public function GetEditClass() {return "Image";} - - /** - * {@inheritDoc} - * @see AttributeBlob::MakeRealValue() - */ - public function MakeRealValue($proposedValue, $oHostObj) - { - $oDoc = parent::MakeRealValue($proposedValue, $oHostObj); - // The validation of the MIME Type is done by CheckFormat below - return $oDoc; - } - - /** - * Check that the supplied ormDocument actually contains an image - * {@inheritDoc} - * @see AttributeDefinition::CheckFormat() - */ - public function CheckFormat($value) - { - if ($value instanceof ormDocument && !$value->IsEmpty()) - { - return ($value->GetMainMimeType() == 'image'); - } - return true; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - $iMaxWidthPx = $this->Get('display_max_width').'px'; - $iMaxHeightPx = $this->Get('display_max_height').'px'; - $sUrl = $this->Get('default_image'); - $sRet = ($sUrl !== null) ? '' : ''; - if (is_object($value) && !$value->IsEmpty()) - { - if ($oHostObject->IsNew() || ($oHostObject->IsModified() && (array_key_exists($this->GetCode(), $oHostObject->ListChanges())))) - { - // If the object is modified (or not yet stored in the database) we must serve the content of the image directly inline - // otherwise (if we just give an URL) the browser will be given the wrong content... and may cache it - $sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData()); - } - else - { - $sUrl = $value->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode()); - } - $sRet = ''; - } - return '
'.$sRet.'
'; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\ImageField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - - parent::MakeFormField($oObject, $oFormField); - - // Generating urls - $value = $oObject->Get($this->GetCode()); - if (is_object($value) && !$value->IsEmpty()) - { - $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); - } - else - { - $oFormField->SetDownloadUrl($this->Get('default_image')); - $oFormField->SetDisplayUrl($this->Get('default_image')); - } - - return $oFormField; - } -} -/** - * A stop watch is an ormStopWatch object, it is stored as several columns in the database - * - * @package iTopORM - */ -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 - return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds")); - } - - public function GetEditClass() {return "StopWatch";} - - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->NewStopWatch();} - - /** - * @param \ormStopWatch $value - * @param \DBObject $oHostObj - * - * @return string - */ - public function GetEditValue($value, $oHostObj = null) - { - return $value->GetTimeSpent(); - } - - public function GetStates() - { - return $this->Get('states'); - } - - public function AlwaysLoadInTables() - { - // Each and every stop watch is accessed for computing the highlight code (DBObject::GetHighlightCode()) - return true; - } - - /** - * Construct a brand new (but configured) stop watch - */ - public function NewStopWatch() - { - $oSW = new ormStopWatch(); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $oSW->DefineThreshold($iThreshold); - } - return $oSW; - } - - // Facilitate things: allow the user to Set the value from a string - public function MakeRealValue($proposedValue, $oHostObj) - { - if (!$proposedValue instanceof ormStopWatch) - { - return $this->NewStopWatch(); - } - return $proposedValue; - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->GetCode(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !! - } - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $sPrefix.'_timespent'; - $aColumns['_started'] = $sPrefix.'_started'; - $aColumns['_laststart'] = $sPrefix.'_laststart'; - $aColumns['_stopped'] = $sPrefix.'_stopped'; - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = '_'.$iThreshold; - $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline'; - $aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed'; - $aColumns[$sThPrefix.'_triggered'] = $sPrefix.$sThPrefix.'_triggered'; - $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun'; - } - return $aColumns; - } - - public static function DateToSeconds($sDate) - { - if (is_null($sDate)) - { - return null; - } - $oDateTime = new DateTime($sDate); - $iSeconds = $oDateTime->format('U'); - return $iSeconds; - } - - public static function SecondsToDate($iSeconds) - { - if (is_null($iSeconds)) - { - return null; - } - return date("Y-m-d H:i:s", $iSeconds); - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped'); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = '_'.$iThreshold; - $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline'; - $aExpectedCols[] = $sPrefix.$sThPrefix.'_passed'; - $aExpectedCols[] = $sPrefix.$sThPrefix.'_triggered'; - $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun'; - } - foreach ($aExpectedCols as $sExpectedCol) - { - if (!array_key_exists($sExpectedCol, $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}"); - } - } - - $value = new ormStopWatch( - $aCols[$sPrefix], - self::DateToSeconds($aCols[$sPrefix.'_started']), - self::DateToSeconds($aCols[$sPrefix.'_laststart']), - self::DateToSeconds($aCols[$sPrefix.'_stopped']) - ); - - foreach ($this->ListThresholds() as $iThreshold => $aDefinition) - { - $sThPrefix = '_'.$iThreshold; - $value->DefineThreshold( - $iThreshold, - self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']), - (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1), - (bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1), - $aCols[$sPrefix.$sThPrefix.'_overrun'], - array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null - ); - } - - return $value; - } - - public function GetSQLValues($value) - { - if ($value instanceOf ormStopWatch) - { - $aValues = array(); - $aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent(); - $aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate()); - $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate()); - $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate()); - - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sPrefix = $this->GetCode().'_'.$iThreshold; - $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold)); - $aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0'; - $aValues[$sPrefix.'_triggered'] = $value->IsThresholdTriggered($iThreshold) ? '1' : '0'; - $aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold); - } - } - else - { - $aValues = array(); - $aValues[$this->GetCode().'_timespent'] = ''; - $aValues[$this->GetCode().'_started'] = ''; - $aValues[$this->GetCode().'_laststart'] = ''; - $aValues[$this->GetCode().'_stopped'] = ''; - } - return $aValues; - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED'; - $aColumns[$this->GetCode().'_started'] = 'DATETIME'; - $aColumns[$this->GetCode().'_laststart'] = 'DATETIME'; - $aColumns[$this->GetCode().'_stopped'] = 'DATETIME'; - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sPrefix = $this->GetCode().'_'.$iThreshold; - $aColumns[$sPrefix.'_deadline'] = 'DATETIME'; - $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) UNSIGNED'; - $aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)'; - $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED'; - } - return $aColumns; - } - - public function GetFilterDefinitions() - { - $aRes = array( - $this->GetCode() => new FilterFromAttribute($this), - $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'), - $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'), - $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped') - ); - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sPrefix = $this->GetCode().'_'.$iThreshold; - $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline'); - $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed'); - $aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered'); - $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun'); - } - return $aRes; - } - - public function GetBasicFilterOperators() - { - return array(); - } - public function GetBasicFilterLooseOperator() - { - return '='; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return 'true'; - } - - /** - * @param \ormStopWatch $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - */ - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if (is_object($value)) - { - return $value->GetAsHTML($this, $oHostObject); - } - return ''; - } - - /** - * @param ormStopWatch $value - * @param string $sSeparator - * @param string $sTextQualifier - * @param null $oHostObject - * @param bool $bLocalize - * @param bool $bConvertToPlainText - * - * @return string - */ - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - return $value->GetTimeSpent(); - } - - /** - * @param \ormStopWatch $value - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return mixed - */ - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - return $value->GetTimeSpent(); - } - - public function ListThresholds() - { - return $this->Get('thresholds'); - } - - public function Fingerprint($value) - { - $sFingerprint = ''; - if (is_object($value)) - { - $sFingerprint = $value->GetAsHTML($this); - } - return $sFingerprint; - } - - /** - * To expose internal values: Declare an attribute AttributeSubItem - * and implement the GetSubItemXXXX verbs - * - * @param string $sItemCode - * - * @return array - * @throws \CoreException - */ - public function GetSubItemSQLExpression($sItemCode) - { - $sPrefix = $this->GetCode(); - switch($sItemCode) - { - case 'timespent': - return array('' => $sPrefix.'_timespent'); - case 'started': - return array('' => $sPrefix.'_started'); - case 'laststart': - return array('' => $sPrefix.'_laststart'); - case 'stopped': - return array('' => $sPrefix.'_stopped'); - } - - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - return array('' => $sPrefix.'_'.$iThreshold.'_deadline'); - case 'passed': - return array('' => $sPrefix.'_'.$iThreshold.'_passed'); - case 'triggered': - return array('' => $sPrefix.'_'.$iThreshold.'_triggered'); - case 'overrun': - return array('' => $sPrefix.'_'.$iThreshold.'_overrun'); - } - } - } - throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); - } - - /** - * @param string $sItemCode - * @param \ormStopWatch $value - * @param \DBObject $oHostObject - * - * @return mixed - * @throws \CoreException - */ - public function GetSubItemValue($sItemCode, $value, $oHostObject = null) - { - $oStopWatch = $value; - switch($sItemCode) - { - case 'timespent': - return $oStopWatch->GetTimeSpent(); - case 'started': - return $oStopWatch->GetStartDate(); - case 'laststart': - return $oStopWatch->GetLastStartDate(); - case 'stopped': - return $oStopWatch->GetStopDate(); - } - - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - return $oStopWatch->GetThresholdDate($iThreshold); - case 'passed': - return $oStopWatch->IsThresholdPassed($iThreshold); - case 'triggered': - return $oStopWatch->IsThresholdTriggered($iThreshold); - case 'overrun': - return $oStopWatch->GetOverrun($iThreshold); - } - } - } - - throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); - } - - protected function GetBooleanLabel($bValue) - { - $sDictKey = $bValue ? 'yes' : 'no'; - return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey); - } - - public function GetSubItemAsHTMLForHistory($sItemCode, $sValue) - { - $sHtml = null; - switch($sItemCode) - { - case 'timespent': - $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null; - break; - case 'started': - case 'laststart': - case 'stopped': - $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; - break; - case 'passed': - $sHtml = $this->GetBooleanLabel((int)$sValue); - break; - case 'triggered': - $sHtml = $this->GetBooleanLabel((int)$sValue); - break; - case 'overrun': - $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : ''; - } - } - } - } - return $sHtml; - } - - public function GetSubItemAsPlainText($sItemCode, $value) - { - $sRet = $value; - - switch ($sItemCode) - { - case 'timespent': - $sRet = AttributeDuration::FormatDuration($value); - break; - case 'started': - case 'laststart': - case 'stopped': - if (is_null($value)) - { - $sRet = ''; // Undefined - } - else - { - $oDateTime = new DateTime(); - $oDateTime->setTimestamp($value); - $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sRet = $oDateTimeFormat->Format($oDateTime); - } - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold . '_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch ($sThresholdCode) - { - case 'deadline': - if ($value) - { - $sDate = date(AttributeDateTime::GetInternalFormat(), $value); - $sRet = AttributeDeadline::FormatDeadline($sDate); - } - else - { - $sRet = ''; - } - break; - case 'passed': - case 'triggered': - $sRet = $this->GetBooleanLabel($value); - break; - case 'overrun': - $sRet = AttributeDuration::FormatDuration($value); - break; - } - } - } - } - return $sRet; - } - - public function GetSubItemAsHTML($sItemCode, $value) - { - $sHtml = $value; - - switch ($sItemCode) - { - case 'timespent': - $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); - break; - case 'started': - case 'laststart': - case 'stopped': - if (is_null($value)) - { - $sHtml = ''; // Undefined - } - else - { - $oDateTime = new DateTime(); - $oDateTime->setTimestamp($value); - $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sHtml = Str::pure2html($oDateTimeFormat->Format($oDateTime)); - } - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold . '_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch ($sThresholdCode) - { - case 'deadline': - if ($value) - { - $sDate = date(AttributeDateTime::GetInternalFormat(), $value); - $sHtml = Str::pure2html(AttributeDeadline::FormatDeadline($sDate)); - } - else - { - $sHtml = ''; - } - break; - case 'passed': - case 'triggered': - $sHtml = $this->GetBooleanLabel($value); - break; - case 'overrun': - $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); - break; - } - } - } - } - return $sHtml; - } - - public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false) - { - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$value); - $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; - - switch($sItemCode) - { - case 'timespent': - $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; - break; - case 'started': - case 'laststart': - case 'stopped': - if ($value !== null) - { - $oDateTime = new DateTime(); - $oDateTime->setTimestamp($value); - $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; - } - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - if ($value != '') - { - $oDateTime = new DateTime(); - $oDateTime->setTimestamp($value); - $oDateTimeFormat = AttributeDateTime::GetFormat(); - $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; - } - break; - - case 'passed': - case 'triggered': - $sRet = $sTextQualifier . $this->GetBooleanLabel($value) . $sTextQualifier; - break; - - case 'overrun': - $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; - break; - } - } - } - } - return $sRet; - } - - public function GetSubItemAsXML($sItemCode, $value) - { - $sRet = Str::pure2xml((string)$value); - - switch($sItemCode) - { - case 'timespent': - case 'started': - case 'laststart': - case 'stopped': - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - break; - - case 'passed': - case 'triggered': - $sRet = $this->GetBooleanLabel($value); - break; - - case 'overrun': - break; - } - } - } - } - return $sRet; - } - - /** - * Implemented for the HTML spreadsheet format! - * - * @param string $sItemCode - * @param \ormStopWatch $value - * - * @return false|string - */ - public function GetSubItemAsEditValue($sItemCode, $value) - { - $sRet = $value; - - switch($sItemCode) - { - case 'timespent': - break; - - case 'started': - case 'laststart': - case 'stopped': - if (is_null($value)) - { - $sRet = ''; // Undefined - } - else - { - $sRet = date((string)AttributeDateTime::GetFormat(), $value); - } - break; - - default: - foreach ($this->ListThresholds() as $iThreshold => $aFoo) - { - $sThPrefix = $iThreshold.'_'; - if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) - { - // The current threshold is concerned - $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); - switch($sThresholdCode) - { - case 'deadline': - if ($value) - { - $sRet = date((string)AttributeDateTime::GetFormat(), $value); - } - else - { - $sRet = ''; - } - break; - case 'passed': - case 'triggered': - $sRet = $this->GetBooleanLabel($value); - break; - case 'overrun': - break; - } - } - } - } - return $sRet; - } -} - -/** - * View of a subvalue of another attribute - * If an attribute implements the verbs GetSubItem.... then it can expose - * internal values, each of them being an attribute and therefore they - * can be displayed at different times in the object lifecycle, and used for - * reporting (as a condition in OQL, or as an additional column in an export) - * Known usages: Stop Watches can expose threshold statuses - */ -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')); - } - - public function GetParentAttCode() {return $this->Get("target_attcode");} - - /** - * Helper : get the attribute definition to which the execution will be forwarded - */ - public function GetTargetAttDef() - { - $sClass = $this->GetHostClass(); - $oParentAttDef = MetaModel::GetAttributeDef($sClass, $this->Get('target_attcode')); - return $oParentAttDef; - } - - public function GetEditClass() {return "";} - - public function GetValuesDef() {return null;} - - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return false;} - public function GetDefaultValue(DBObject $oHostObject = null) {return null;} -// public function IsNullAllowed() {return false;} - - static public function LoadInObject() {return false;} // if this verb returns false, then GetValue must be implemented - - /** - * Used by DBOBject::Get() - * - * @param \DBObject $oHostObject - * - * @return \AttributeSubItem - * @throws \CoreException - */ - public function GetValue($oHostObject) - { - /** @var \AttributeStopWatch $oParent */ - $oParent = $this->GetTargetAttDef(); - $parentValue = $oHostObject->GetStrict($oParent->GetCode()); - $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject); - return $res; - } - - // -// protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) - - public function FromSQLToValue($aCols, $sPrefix = '') - { - } - - public function GetSQLColumns($bFullSpec = false) - { - return array(); - } - - public function GetFilterDefinitions() - { - return array($this->GetCode() => new FilterFromAttribute($this)); - } - - public function GetBasicFilterOperators() - { - return array(); - } - public function GetBasicFilterLooseOperator() - { - return "="; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '!=': - return $this->GetSQLExpr()." != $sQValue"; - break; - case '=': - default: - return $this->GetSQLExpr()." = $sQValue"; - } - } - - public function GetSQLExpressions($sPrefix = '') - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemSQLExpression($this->Get('item_code')); - return $res; - } - - public function GetAsPlainText($value, $oHostObject = null, $bLocalize = true) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsPlainText($this->Get('item_code'), $value); - return $res; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsHTML($this->Get('item_code'), $value); - return $res; - } - - public function GetAsHTMLForHistory($value, $oHostObject = null, $bLocalize = true) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value); - return $res; - } - - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, $bConvertToPlainText); - return $res; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsXML($this->Get('item_code'), $value); - return $res; - } - - /** - * As of now, this function must be implemented to have the value in spreadsheet format - */ - public function GetEditValue($value, $oHostObj = null) - { - $oParent = $this->GetTargetAttDef(); - $res = $oParent->GetSubItemAsEditValue($this->Get('item_code'), $value); - return $res; - } - - public function IsPartOfFingerprint() - { - return false; - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\LabelField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - parent::MakeFormField($oObject, $oFormField); - - // Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition - $sAttCode = $this->GetCode(); - $oFormField->SetCurrentValue(html_entity_decode($oObject->GetAsHTML($sAttCode), ENT_QUOTES, 'UTF-8')); - $oFormField->SetReadOnly(true); - - return $oFormField; - } - -} - -/** - * One way encrypted (hashed) password - */ -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")); - } - - public function GetEditClass() {return "One Way Password";} - - static public function IsBasedOnDBColumns() {return true;} - static public function IsScalar() {return true;} - public function IsWritable() {return true;} - public function GetDefaultValue(DBObject $oHostObject = null) {return "";} - public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} - - // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted) - public function MakeRealValue($proposedValue, $oHostObj) - { - $oPassword = $proposedValue; - if (is_object($oPassword)) - { - $oPassword = clone $proposedValue; - } - else - { - $oPassword = new ormPassword('', ''); - $oPassword->SetPassword($proposedValue); - } - return $oPassword; - } - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->GetCode(); // Warning: AttributeOneWayPassword does not have any sql property so code = sql ! - } - $aColumns = array(); - // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix - $aColumns[''] = $sPrefix.'_hash'; - $aColumns['_salt'] = $sPrefix.'_salt'; - return $aColumns; - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - if (!array_key_exists($sPrefix, $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } - $hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; - - if (!array_key_exists($sPrefix.'_salt', $aCols)) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}"); - } - $sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : ''; - - $value = new ormPassword($hashed, $sSalt); - return $value; - } - - public function GetSQLValues($value) - { - // #@# Optimization: do not load blobs anytime - // As per mySQL doc, selecting blob columns will prevent mySQL from - // using memory in case a temporary table has to be created - // (temporary tables created on disk) - // We will have to remove the blobs from the list of attributes when doing the select - // then the use of Get() should finalize the load - if ($value instanceOf ormPassword) - { - $aValues = array(); - $aValues[$this->GetCode().'_hash'] = $value->GetHash(); - $aValues[$this->GetCode().'_salt'] = $value->GetSalt(); - } - else - { - $aValues = array(); - $aValues[$this->GetCode().'_hash'] = ''; - $aValues[$this->GetCode().'_salt'] = ''; - } - return $aValues; - } - - public function GetSQLColumns($bFullSpec = false) - { - $aColumns = array(); - $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB'; - $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB'; - return $aColumns; - } - - public function GetImportColumns() - { - $aColumns = array(); - $aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::GetSqlStringColumnDefinition(); - return $aColumns; - } - - public function FromImportToValue($aCols, $sPrefix = '') - { - if (!isset($aCols[$sPrefix])) - { - $sAvailable = implode(', ', array_keys($aCols)); - throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); - } - $sClearPwd = $aCols[$sPrefix]; - - $oPassword = new ormPassword('', ''); - $oPassword->SetPassword($sClearPwd); - return $oPassword; - } - - public function GetFilterDefinitions() - { - return array(); - // still not working... see later... - } - - public function GetBasicFilterOperators() - { - return array(); - } - public function GetBasicFilterLooseOperator() - { - return '='; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - return 'true'; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if (is_object($value)) - { - return $value->GetAsHTML(); - } - return ''; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - return ''; // Not exportable in CSV - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - return ''; // Not exportable in XML - } - - public function GetValueLabel($sValue, $oHostObj = null) - { - // Don't display anything in "group by" reports - return '*****'; - } - -} - -// Indexed array having two dimensions -class AttributeTable extends AttributeDBField -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - - public function GetEditClass() {return "Table";} - - protected function GetSQLCol($bFullSpec = false) - { - return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition(); - } - - public function GetMaxSize() - { - return null; - } - - public function GetNullValue() - { - return array(); - } - - public function IsNull($proposedValue) - { - return (count($proposedValue) == 0); - } - - public function GetEditValue($sValue, $oHostObj = null) - { - return ''; - } - - // Facilitate things: allow the user to Set the value from a string - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) - { - return array(); - } - else if (!is_array($proposedValue)) - { - return array(0 => array(0 => $proposedValue)); - } - return $proposedValue; - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - try - { - $value = @unserialize($aCols[$sPrefix.'']); - if ($value === false) - { - $value = $this->MakeRealValue($aCols[$sPrefix.''], null); - } - } - catch(Exception $e) - { - $value = $this->MakeRealValue($aCols[$sPrefix.''], null); - } - - return $value; - } - - public function GetSQLValues($value) - { - $aValues = array(); - $aValues[$this->Get("sql")] = serialize($value); - return $aValues; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if (!is_array($value)) - { - throw new CoreException('Expecting an array', array('found' => get_class($value))); - } - if (count($value) == 0) - { - return ""; - } - - $sRes = ""; - $sRes .= ""; - foreach($value as $iRow => $aRawData) - { - $sRes .= ""; - foreach ($aRawData as $iCol => $cell) - { - // Note: avoid the warning in case the cell is made of an array - $sCell = @Str::pure2html((string)$cell); - $sCell = str_replace("\n", "
\n", $sCell); - $sRes .= ""; - } - $sRes .= ""; - } - $sRes .= ""; - $sRes .= "
$sCell
"; - return $sRes; - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - // Not implemented - return ''; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if (!is_array($value) || count($value) == 0) - { - return ""; - } - - $sRes = ""; - foreach($value as $iRow => $aRawData) - { - $sRes .= ""; - foreach ($aRawData as $iCol => $cell) - { - $sCell = Str::pure2xml((string)$cell); - $sRes .= "$sCell"; - } - $sRes .= ""; - } - return $sRes; - } -} - -// The PHP value is a hash array, it is stored as a TEXT column -class AttributePropertySet extends AttributeTable -{ - public function GetEditClass() {return "PropertySet";} - - // Facilitate things: allow the user to Set the value from a string - public function MakeRealValue($proposedValue, $oHostObj) - { - if (!is_array($proposedValue)) - { - return array('?' => (string)$proposedValue); - } - return $proposedValue; - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - if (!is_array($value)) - { - throw new CoreException('Expecting an array', array('found' => get_class($value))); - } - if (count($value) == 0) - { - return ""; - } - - $sRes = ""; - $sRes .= ""; - foreach($value as $sProperty => $sValue) - { - if ($sProperty == 'auth_pwd') - { - $sValue = '*****'; - } - $sRes .= ""; - $sCell = str_replace("\n", "
\n", Str::pure2html((string)$sValue)); - $sRes .= ""; - $sRes .= ""; - } - $sRes .= ""; - $sRes .= "
$sProperty$sCell
"; - return $sRes; - } - - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - if (!is_array($value) || count($value) == 0) - { - return ""; - } - - $aRes = array(); - foreach($value as $sProperty => $sValue) - { - if ($sProperty == 'auth_pwd') - { - $sValue = '*****'; - } - $sFrom = array(',', '='); - $sTo = array('\,', '\='); - $aRes[] = $sProperty.'='.str_replace($sFrom, $sTo, (string)$sValue); - } - $sRaw = implode(',', $aRes); - - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, $sRaw); - return $sTextQualifier.$sEscaped.$sTextQualifier; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - if (!is_array($value) || count($value) == 0) - { - return ""; - } - - $sRes = ""; - foreach($value as $sProperty => $sValue) - { - if ($sProperty == 'auth_pwd') - { - $sValue = '*****'; - } - $sRes .= ""; - $sRes .= Str::pure2xml((string)$sValue); - $sRes .= ""; - } - return $sRes; - } -} - -/** - * The attribute dedicated to the friendly name automatic attribute (not written) - * - * @package iTopORM - */ - -/** - * The attribute dedicated to the friendly name automatic attribute (not written) - * - * @package iTopORM - */ -class AttributeFriendlyName extends AttributeDefinition -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; - public $m_sValue; - - public function __construct($sCode) - { - $this->m_sCode = $sCode; - $aParams = array(); - $aParams["default_value"] = ''; - parent::__construct($sCode, $aParams); - - $this->m_sValue = $this->Get("default_value"); - } - - - public function GetEditClass() {return "";} - - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} - - static public function IsScalar() {return true;} - public function IsNullAllowed() {return false;} - - public function GetSQLExpressions($sPrefix = '') - { - if ($sPrefix == '') - { - $sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property - } - return array('' => $sPrefix); - } - - static public function IsBasedOnOQLExpression() {return true;} - public function GetOQLExpression() - { - return MetaModel::GetNameExpression($this->GetHostClass()); - } - - public function GetLabel($sDefault = null) - { - $sLabel = parent::GetLabel(''); - if (strlen($sLabel) == 0) - { - $sLabel = Dict::S('Core:FriendlyName-Label'); - } - return $sLabel; - } - public function GetDescription($sDefault = null) - { - $sLabel = parent::GetDescription(''); - if (strlen($sLabel) == 0) - { - $sLabel = Dict::S('Core:FriendlyName-Description'); - } - return $sLabel; - } - - public function FromSQLToValue($aCols, $sPrefix = '') - { - $sValue = $aCols[$sPrefix]; - return $sValue; - } - - public function IsWritable() - { - return false; - } - public function IsMagic() - { - return true; - } - - static public function IsBasedOnDBColumns() - { - return false; - } - - public function SetFixedValue($sValue) - { - $this->m_sValue = $sValue; - } - public function GetDefaultValue(DBObject $oHostObject = null) - { - return $this->m_sValue; - } - - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - return Str::pure2html((string)$sValue); - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - } - - static function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\StringField'; - } - - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - } - $oFormField->SetReadOnly(true); - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - // Do not display friendly names in the history of change - public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null) - { - return ''; - } - - public function GetFilterDefinitions() - { - return array($this->GetCode() => new FilterFromAttribute($this)); - } - - public function GetBasicFilterOperators() - { - return array("="=>"equals", "!="=>"differs from"); - } - - public function GetBasicFilterLooseOperator() - { - return "Contains"; - } - - public function GetBasicFilterSQLExpr($sOpCode, $value) - { - $sQValue = CMDBSource::Quote($value); - switch ($sOpCode) - { - case '=': - case '!=': - return $this->GetSQLExpr()." $sOpCode $sQValue"; - case 'Contains': - return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); - case 'NotLike': - return $this->GetSQLExpr()." NOT LIKE $sQValue"; - case 'Like': - default: - return $this->GetSQLExpr()." LIKE $sQValue"; - } - } - - public function IsPartOfFingerprint() { return false; } -} - -/** - * Holds the setting for the redundancy on a specific relation - * Its value is a string, containing either: - * - 'disabled' - * - 'n', where n is a positive integer value giving the minimum count of items upstream - * - 'n%', where n is a positive integer value, giving the minimum as a percentage of the total count of items upstream - * - * @package iTopORM - */ -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'); - } - - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return array();} - - public function GetEditClass() {return "RedundancySetting";} - protected function GetSQLCol($bFullSpec = false) - { - return "VARCHAR(20)" - .CMDBSource::GetSqlStringColumnDefinition() - .($bFullSpec ? $this->GetSQLColSpec() : ''); - } - - - public function GetValidationPattern() - { - return "^[0-9]{1,3}|[0-9]{1,2}%|disabled$"; - } - - public function GetMaxSize() - { - return 20; - } - - public function GetDefaultValue(DBObject $oHostObject = null) - { - $sRet = 'disabled'; - if ($this->Get('enabled')) - { - if ($this->Get('min_up_type') == 'count') - { - $sRet = (string) $this->Get('min_up'); - } - else // percent - { - $sRet = $this->Get('min_up').'%'; - } - } - return $sRet; - } - - public function IsNullAllowed() - { - return false; - } - - public function GetNullValue() - { - return ''; - } - - public function IsNull($proposedValue) - { - return ($proposedValue == ''); - } - - public function MakeRealValue($proposedValue, $oHostObj) - { - if (is_null($proposedValue)) return ''; - return (string)$proposedValue; - } - - public function ScalarToSQL($value) - { - if (!is_string($value)) - { - throw new CoreException('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); - } - return $value; - } - - public function GetRelationQueryData() - { - foreach (MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'), false) as $sDummy => $aQueryInfo) - { - if ($aQueryInfo['sFromClass'] == $this->Get('from_class')) - { - if ($aQueryInfo['sNeighbour'] == $this->Get('neighbour_id')) - { - return $aQueryInfo; - } - } - } - return array(); - } - - /** - * Find the user option label - * - * @param string $sUserOption possible values : disabled|cout|percent - * @param string $sDefault - * - * @return string - * @throws \Exception - */ - public function GetUserOptionFormat($sUserOption, $sDefault = null) - { - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, null, true /*user lang*/); - if (is_null($sLabel)) - { - // If no default value is specified, let's define the most relevant one for developping purposes - if (is_null($sDefault)) - { - $sDefault = str_replace('_', ' ', $this->m_sCode.':'.$sUserOption.'(%1$s)'); - } - // Browse the hierarchy again, accepting default (english) translations - $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, $sDefault, false); - } - return $sLabel; - } - - /** - * Override to display the value in the GUI - * - * @param string $sValue - * @param \DBObject $oHostObject - * @param bool $bLocalize - * - * @return string - * @throws \CoreException - * @throws \DictExceptionMissingString - */ - public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) - { - $sCurrentOption = $this->GetCurrentOption($sValue); - $sClass = $oHostObject ? get_class($oHostObject) : $this->m_sHostClass; - return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), MetaModel::GetName($sClass)); - } - - public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); - return $sTextQualifier.$sEscaped.$sTextQualifier; - } - - /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ - public function IsEnabled($sValue) - { - if ($this->get('enabled_mode') == 'fixed') - { - $bRet = $this->get('enabled'); - } - else - { - $bRet = ($sValue != 'disabled'); - } - return $bRet; - } - - /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ - public function GetMinUpType($sValue) - { - if ($this->get('min_up_mode') == 'fixed') - { - $sRet = $this->get('min_up_type'); - } - else - { - $sRet = 'count'; - if (substr(trim($sValue), -1, 1) == '%') - { - $sRet = 'percent'; - } - } - return $sRet; - } - - /** - * Helper to interpret the value, given the current settings and string representation of the attribute - */ - public function GetMinUpValue($sValue) - { - if ($this->get('min_up_mode') == 'fixed') - { - $iRet = (int) $this->Get('min_up'); - } - else - { - $sRefValue = $sValue; - if (substr(trim($sValue), -1, 1) == '%') - { - $sRefValue = substr(trim($sValue), 0, -1); - } - $iRet = (int) trim($sRefValue); - } - return $iRet; - } - - /** - * Helper to determine if the redundancy can be viewed/edited by the end-user - */ - public function IsVisible() - { - $bRet = false; - if ($this->Get('enabled_mode') == 'fixed') - { - $bRet = $this->Get('enabled'); - } - elseif ($this->Get('enabled_mode') == 'user') - { - $bRet = true; - } - return $bRet; - } - - public function IsWritable() - { - if (($this->Get('enabled_mode') == 'fixed') && ($this->Get('min_up_mode') == 'fixed')) - { - return false; - } - return true; - } - - /** - * Returns an HTML form that can be read by ReadValueFromPostedForm - */ - public function GetDisplayForm($sCurrentValue, $oPage, $bEditMode = false, $sFormPrefix = '') - { - $sRet = ''; - $aUserOptions = $this->GetUserOptions($sCurrentValue); - if (count($aUserOptions) < 2) - { - $bEditOption = false; - } - else - { - $bEditOption = $bEditMode; - } - $sCurrentOption = $this->GetCurrentOption($sCurrentValue); - foreach($aUserOptions as $sUserOption) - { - $bSelected = ($sUserOption == $sCurrentOption); - $sRet .= '
'; - $sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption, $bSelected); - $sRet .= '
'; - } - return $sRet; - } - - const USER_OPTION_DISABLED = 'disabled'; - const USER_OPTION_ENABLED_COUNT = 'count'; - const USER_OPTION_ENABLED_PERCENT = 'percent'; - - /** - * Depending on the xxx_mode parameters, build the list of options that are allowed to the end-user - */ - protected function GetUserOptions($sValue) - { - $aRet = array(); - if ($this->Get('enabled_mode') == 'user') - { - $aRet[] = self::USER_OPTION_DISABLED; - } - - if ($this->Get('min_up_mode') == 'user') - { - $aRet[] = self::USER_OPTION_ENABLED_COUNT; - $aRet[] = self::USER_OPTION_ENABLED_PERCENT; - } - else - { - if ($this->GetMinUpType($sValue) == 'count') - { - $aRet[] = self::USER_OPTION_ENABLED_COUNT; - } - else - { - $aRet[] = self::USER_OPTION_ENABLED_PERCENT; - } - } - return $aRet; - } - - /** - * Convert the string representation into one of the existing options - */ - protected function GetCurrentOption($sValue) - { - $sRet = self::USER_OPTION_DISABLED; - if ($this->IsEnabled($sValue)) - { - if ($this->GetMinUpType($sValue) == 'count') - { - $sRet = self::USER_OPTION_ENABLED_COUNT; - } - else - { - $sRet = self::USER_OPTION_ENABLED_PERCENT; - } - } - return $sRet; - } - - /** - * Display an option (form, or current value) - * - * @param string $sCurrentValue - * @param \WebPage $oPage - * @param string $sFormPrefix - * @param bool $bEditMode - * @param string $sUserOption - * @param bool $bSelected - * - * @return string - * @throws \CoreException - * @throws \DictExceptionMissingString - * @throws \Exception - */ - protected function GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true) - { - $sRet = ''; - - $iCurrentValue = $this->GetMinUpValue($sCurrentValue); - if ($bEditMode) - { - $sValue = null; - $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); - switch ($sUserOption) - { - case self::USER_OPTION_DISABLED: - $sValue = ''; // Empty placeholder - break; - - case self::USER_OPTION_ENABLED_COUNT: - if ($bEditMode) - { - $sName = $sHtmlNamesPrefix.'_min_up_count'; - $sEditValue = $bSelected ? $iCurrentValue : ''; - $sValue = ''; - // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) - $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); - } - else - { - $sValue = $iCurrentValue; - } - break; - - case self::USER_OPTION_ENABLED_PERCENT: - if ($bEditMode) - { - $sName = $sHtmlNamesPrefix.'_min_up_percent'; - $sEditValue = $bSelected ? $iCurrentValue : ''; - $sValue = ''; - // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) - $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); - } - else - { - $sValue = $iCurrentValue; - } - break; - } - $sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue, MetaModel::GetName($this->GetHostClass())); - - $sOptionName = $sHtmlNamesPrefix.'_user_option'; - $sOptionId = $sOptionName.'_'.$sUserOption; - $sChecked = $bSelected ? 'checked' : ''; - $sRet = ' '; - } - else - { - // Read-only: display only the currently selected option - if ($bSelected) - { - $sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue, MetaModel::GetName($this->GetHostClass())); - } - } - return $sRet; - } - - /** - * Makes the string representation out of the values given by the form defined in GetDisplayForm - */ - public function ReadValueFromPostedForm($sFormPrefix) - { - $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); - - $iMinUpCount = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data'); - $iMinUpPercent = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data'); - $sSelectedOption = utils::ReadPostedParam($sHtmlNamesPrefix.'_user_option', null, 'raw_data'); - switch ($sSelectedOption) - { - case self::USER_OPTION_ENABLED_COUNT: - $sRet = $iMinUpCount; - break; - - case self::USER_OPTION_ENABLED_PERCENT: - $sRet = $iMinUpPercent.'%'; - break; - - case self::USER_OPTION_DISABLED: - default: - $sRet = 'disabled'; - break; - } - return $sRet; - } -} - -/** - * Custom fields managed by an external implementation - * - * @package iTopORM - */ -class AttributeCustomFields extends AttributeDefinition -{ - const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; - - static public function ListExpectedParams() - { - return array_merge(parent::ListExpectedParams(), array("handler_class")); - } - - public function GetEditClass() {return "CustomFields";} - public function IsWritable() {return true;} - static public function LoadFromDB() {return false;} // See ReadValue... - - public function GetDefaultValue(DBObject $oHostObject = null) - { - return new ormCustomFieldsValue($oHostObject, $this->GetCode()); - } - - public function GetBasicFilterOperators() {return array();} - public function GetBasicFilterLooseOperator() {return '';} - public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} - - /** - * @param DBObject $oHostObject - * @param array|null $aValues - * @return CustomFieldsHandler - */ - public function GetHandler($aValues = null) - { - $sHandlerClass = $this->Get('handler_class'); - $oHandler = new $sHandlerClass($this->GetCode()); - if (!is_null($aValues)) - { - $oHandler->SetCurrentValues($aValues); - } - return $oHandler; - } - - public function GetPrerequisiteAttributes($sClass = null) - { - $sHandlerClass = $this->Get('handler_class'); - return $sHandlerClass::GetPrerequisiteAttributes($sClass); - } - - public function GetEditValue($sValue, $oHostObj = null) - { - return $this->GetForTemplate($sValue, '', $oHostObj, true); - } - - /** - * Makes the string representation out of the values given by the form defined in GetDisplayForm - */ - public function ReadValueFromPostedForm($oHostObject, $sFormPrefix) - { - $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true); - return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData); - } - - public function MakeRealValue($proposedValue, $oHostObject) - { - if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue)) - { - return $proposedValue; - } - elseif (is_string($proposedValue)) - { - $aValues = json_decode($proposedValue, true); - return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); - } - elseif (is_array($proposedValue)) - { - return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $proposedValue); - } - elseif (is_null($proposedValue)) - { - return new ormCustomFieldsValue($oHostObject, $this->GetCode()); - } - throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue)); - } - - static public function GetFormFieldClass() - { - return '\\Combodo\\iTop\\Form\\Field\\SubFormField'; - } - - /** - * Override to build the relevant form field - * - * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behaves more like a Prepare. - */ - public function MakeFormField(DBObject $oObject, $oFormField = null) - { - if ($oFormField === null) - { - $sFormFieldClass = static::GetFormFieldClass(); - $oFormField = new $sFormFieldClass($this->GetCode()); - $oFormField->SetForm($this->GetForm($oObject)); - } - parent::MakeFormField($oObject, $oFormField); - - return $oFormField; - } - - /** - * @param DBObject $oHostObject - * @param null $sFormPrefix - * @return Combodo\iTop\Form\Form - * @throws \Exception - */ - public function GetForm(DBObject $oHostObject, $sFormPrefix = null) - { - try - { - $oValue = $oHostObject->Get($this->GetCode()); - $oHandler = $this->GetHandler($oValue->GetValues()); - $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode(); - $oHandler->BuildForm($oHostObject, $sFormId); - $oForm = $oHandler->GetForm(); - } - catch (Exception $e) - { - $oForm = new \Combodo\iTop\Form\Form(''); - $oField = new \Combodo\iTop\Form\Field\LabelField(''); - $oField->SetLabel('Custom field error: '.$e->getMessage()); - $oForm->AddField($oField); - $oForm->Finalize(); - } - return $oForm; - } - - /** - * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false and LoadInObject returns true - * @param $oHostObject - * @return ormCustomFieldsValue - */ - public function ReadValue($oHostObject) - { - try - { - $oHandler = $this->GetHandler(); - $aValues = $oHandler->ReadValues($oHostObject); - $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); - } - catch (Exception $e) - { - $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode()); - } - return $oRet; - } - - /** - * Record the data (currently in the processing of recording the host object) - * It is assumed that the data has been checked prior to calling Write() - * @param DBObject $oHostObject - * @param ormCustomFieldsValue|null $oValue (null is the default value) - */ - public function WriteValue(DBObject $oHostObject, ormCustomFieldsValue $oValue = null) - { - if (is_null($oValue)) - { - $oHandler = $this->GetHandler(); - $aValues = array(); - } - else - { - // Pass the values through the form to make sure that they are correct - $oHandler = $this->GetHandler($oValue->GetValues()); - $oHandler->BuildForm($oHostObject, ''); - $oForm = $oHandler->GetForm(); - $aValues = $oForm->GetCurrentValues(); - } - return $oHandler->WriteValues($oHostObject, $aValues); - } - - /** - * The part of the current attribute in the object's signature, for the supplied value - * @param ormCustomFieldsValue $value The value of this attribute for the object - * @return string The "signature" for this field/attribute - */ - public function Fingerprint($value) - { - $oHandler = $this->GetHandler($value->GetValues()); - return $oHandler->GetValueFingerprint(); - } - - /** - * Check the validity of the data - * @param DBObject $oHostObject - * @param $value - * @return bool|string true or error message - */ - public function CheckValue(DBObject $oHostObject, $value) - { - try - { - $oHandler = $this->GetHandler($value->GetValues()); - $oHandler->BuildForm($oHostObject, ''); - $oForm = $oHandler->GetForm(); - $oForm->Validate(); - if ($oForm->GetValid()) - { - $ret = true; - } - else - { - $aMessages = array(); - foreach ($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages) - { - $aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages); - } - $ret = 'Invalid value: '.implode(', ', $aMessages); - } - } - catch (Exception $e) - { - $ret = $e->getMessage(); - } - return $ret; - } - - /** - * Cleanup data upon object deletion (object id still available here) - * @param DBObject $oHostObject - * @return - * @throws \CoreException - */ - public function DeleteValue(DBObject $oHostObject) - { - $oValue = $oHostObject->Get($this->GetCode()); - $oHandler = $this->GetHandler($oValue->GetValues()); - return $oHandler->DeleteValues($oHostObject); - } - - public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) - { - try - { - $sRet = $value->GetAsHTML($bLocalize); - } - catch (Exception $e) - { - $sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); - } - return $sRet; - } - - public function GetAsXML($value, $oHostObject = null, $bLocalize = true) - { - try - { - $sRet = $value->GetAsXML($bLocalize); - } - catch (Exception $e) - { - $sRet = Str::pure2xml('Custom field error: '.$e->getMessage()); - } - return $sRet; - } - - public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) - { - try - { - $sRet = $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText); - } - catch (Exception $e) - { - $sFrom = array("\r\n", $sTextQualifier); - $sTo = array("\n", $sTextQualifier.$sTextQualifier); - $sEscaped = str_replace($sFrom, $sTo, 'Custom field error: '.$e->getMessage()); - $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; - } - return $sRet; - } - - /** - * List the available verbs for 'GetForTemplate' - */ - public function EnumTemplateVerbs() - { - $sHandlerClass = $this->Get('handler_class'); - return $sHandlerClass::EnumTemplateVerbs(); - } - - /** - * Get various representations of the value, for insertion into a template (e.g. in Notifications) - * - * @param $value mixed The current value of the field - * @param $sVerb string The verb specifying the representation of the value - * @param $oHostObject DBObject The object - * @param $bLocalize bool Whether or not to localize the value - * - * @return string - */ - public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) - { - try - { - $sRet = $value->GetForTemplate($sVerb, $bLocalize); - } - catch (Exception $e) - { - $sRet = 'Custom field error: '.$e->getMessage(); - } - return $sRet; - } - - public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) - { - return null; - } - - /** - * Helper to get a value that will be JSON encoded - * The operation is the opposite to FromJSONToValue - * - * @param $value - * - * @return string - */ - public function GetForJSON($value) - { - return null; - } - - /** - * Helper to form a value, given JSON decoded data - * The operation is the opposite to GetForJSON - * - * @param string $json - * - * @return array - */ - public function FromJSONToValue($json) - { - return null; - } - - public function Equals($val1, $val2) - { - try - { - $bEquals = $val1->Equals($val2); - } - catch (Exception $e) - { - $bEquals = false; - } - return $bEquals; - } -} - -class AttributeArchiveFlag extends AttributeBoolean -{ - public function __construct($sCode) - { - parent::__construct($sCode, array("allowed_values" => null, "sql" => $sCode, "default_value" => false, "is_null_allowed" => false, "depends_on" => array())); - } - public function RequiresIndex() - { - return true; - } - public function CopyOnAllTables() - { - return true; - } - public function IsWritable() - { - return false; - } - public function IsMagic() - { - return true; - } - public function GetLabel($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeArchiveFlag/Label', $sDefault); - return parent::GetLabel($sDefault); - } - public function GetDescription($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeArchiveFlag/Label+', $sDefault); - return parent::GetDescription($sDefault); - } -} -class AttributeArchiveDate extends AttributeDate -{ - public function GetLabel($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeArchiveDate/Label', $sDefault); - return parent::GetLabel($sDefault); - } - public function GetDescription($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeArchiveDate/Label+', $sDefault); - return parent::GetDescription($sDefault); - } -} - -class AttributeObsolescenceFlag extends AttributeBoolean -{ - public function __construct($sCode) - { - parent::__construct($sCode, array("allowed_values"=>null, "sql"=>$sCode, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())); - } - public function IsWritable() - { - return false; - } - public function IsMagic() - { - return true; - } - - static public function IsBasedOnDBColumns() {return false;} - /** - * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) - * @return bool - */ - static public function IsBasedOnOQLExpression() {return true;} - public function GetOQLExpression() - { - return MetaModel::GetObsolescenceExpression($this->GetHostClass()); - } - - public function GetSQLExpressions($sPrefix = '') - { - return array(); - } - public function GetSQLColumns($bFullSpec = false) {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) - public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) - - public function GetEditClass() {return "";} - - public function GetValuesDef() {return null;} - public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} - - public function IsDirectField() {return true;} - static public function IsScalar() {return true;} - public function GetSQLExpr() - { - return null; - } - - public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} - public function IsNullAllowed() {return false;} - - public function GetLabel($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label', $sDefault); - return parent::GetLabel($sDefault); - } - public function GetDescription($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label+', $sDefault); - return parent::GetDescription($sDefault); - } -} - -class AttributeObsolescenceDate extends AttributeDate -{ - public function GetLabel($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label', $sDefault); - return parent::GetLabel($sDefault); - } - public function GetDescription($sDefault = null) - { - $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label+', $sDefault); - return parent::GetDescription($sDefault); - } -} + + + +/** + * Typology for the attributes + * + * @copyright Copyright (C) 2010-2018 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + + +require_once('MyHelpers.class.inc.php'); +require_once('ormdocument.class.inc.php'); +require_once('ormstopwatch.class.inc.php'); +require_once('ormpassword.class.inc.php'); +require_once('ormcaselog.class.inc.php'); +require_once('ormlinkset.class.inc.php'); +require_once('ormtagset.class.inc.php'); +require_once('htmlsanitizer.class.inc.php'); +require_once(APPROOT.'sources/autoload.php'); +require_once('customfieldshandler.class.inc.php'); +require_once('ormcustomfieldsvalue.class.inc.php'); +require_once('datetimeformat.class.inc.php'); +// This should be changed to a use when we go full-namespace +require_once(APPROOT . 'sources/form/validator/validator.class.inc.php'); +require_once(APPROOT . 'sources/form/validator/notemptyextkeyvalidator.class.inc.php'); + +/** + * MissingColumnException - sent if an attribute is being created but the column is missing in the row + * + * @package iTopORM + */ +class MissingColumnException extends Exception +{} + +/** + * add some description here... + * + * @package iTopORM + */ +define('EXTKEY_RELATIVE', 1); + +/** + * add some description here... + * + * @package iTopORM + */ +define('EXTKEY_ABSOLUTE', 2); + +/** + * Propagation of the deletion through an external key - ask the user to delete the referencing object + * + * @package iTopORM + */ +define('DEL_MANUAL', 1); + +/** + * Propagation of the deletion through an external key - ask the user to delete the referencing object + * + * @package iTopORM + */ +define('DEL_AUTO', 2); +/** + * Fully silent delete... not yet implemented + */ +define('DEL_SILENT', 2); +/** + * For HierarchicalKeys only: move all the children up one level automatically + */ +define('DEL_MOVEUP', 3); + + +/** + * For Link sets: tracking_level + * + * @package iTopORM + */ +define('ATTRIBUTE_TRACKING_NONE', 0); // Do not track changes of the attribute +define('ATTRIBUTE_TRACKING_ALL', 3); // Do track all changes of the attribute +define('LINKSET_TRACKING_NONE', 0); // Do not track changes in the link set +define('LINKSET_TRACKING_LIST', 1); // Do track added/removed items +define('LINKSET_TRACKING_DETAILS', 2); // Do track modified items +define('LINKSET_TRACKING_ALL', 3); // Do track added/removed/modified items + +define('LINKSET_EDITMODE_NONE', 0); // The linkset cannot be edited at all from inside this object +define('LINKSET_EDITMODE_ADDONLY', 1); // The only possible action is to open a new window to create a new object +define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu +define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place +define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place + + +/** + * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) + * + * @package iTopORM + */ +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; + + const INDEX_LENGTH = 95; + + public function GetType() + { + return Dict::S('Core:'.get_class($this)); + } + public function GetTypeDesc() + { + return Dict::S('Core:'.get_class($this).'+'); + } + + 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!'; + public function Get($sParamName) {return $this->m_aParams[$sParamName];} + + public function GetIndexLength() { + $iMaxLength = $this->GetMaxSize(); + if (is_null($iMaxLength)) + { + return null; + } + if ($iMaxLength > static::INDEX_LENGTH) + { + return static::INDEX_LENGTH; + } + return $iMaxLength; + } + + public function IsParam($sParamName) {return (array_key_exists($sParamName, $this->m_aParams));} + + protected function GetOptional($sParamName, $default) + { + if (array_key_exists($sParamName, $this->m_aParams)) + { + return $this->m_aParams[$sParamName]; + } + else + { + return $default; + } + } + + /** + * AttributeDefinition constructor. + * + * @param string $sCode + * @param array $aParams + * + * @throws \Exception + */ + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $this->m_aParams = $aParams; + $this->ConsistencyCheck(); + } + + public function GetParams() + { + return $this->m_aParams; + } + + public function HasParam($sParam) + { + return array_key_exists($sParam, $this->m_aParams); + } + + public function SetHostClass($sHostClass) + { + $this->m_sHostClass = $sHostClass; + } + public function GetHostClass() + { + return $this->m_sHostClass; + } + + /** + * @return array + * + * @throws \CoreException + */ + public function ListSubItems() + { + $aSubItems = array(); + foreach(MetaModel::ListAttributeDefs($this->m_sHostClass) as $sAttCode => $oAttDef) + { + if ($oAttDef instanceof AttributeSubItem) + { + if ($oAttDef->Get('target_attcode') == $this->m_sCode) + { + $aSubItems[$sAttCode] = $oAttDef; + } + } + } + return $aSubItems; + } + + // Note: I could factorize this code with the parameter management made for the AttributeDef class + // to be overloaded + static public function ListExpectedParams() + { + return array(); + } + + /** + * @throws \Exception + */ + private function ConsistencyCheck() + { + // Check that any mandatory param has been specified + // + $aExpectedParams = $this->ListExpectedParams(); + foreach($aExpectedParams as $sParamName) + { + if (!array_key_exists($sParamName, $this->m_aParams)) + { + $aBacktrace = debug_backtrace(); + $sTargetClass = $aBacktrace[2]["class"]; + $sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"]; + throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)"); + } + } + } + + /** + * Check the validity of the given value + * + * @param \DBObject $oHostObject + * @param $value Object error if any, null otherwise + * + * @return bool + */ + public function CheckValue(DBObject $oHostObject, $value) + { + // later: factorize here the cases implemented into DBObject + return true; + } + + // table, key field, name field + + /** + * @return string + * @deprecated never used + */ + public function ListDBJoins() + { + return ""; + // e.g: return array("Site", "infrid", "name"); + } + + public function GetFinalAttDef() + { + return $this; + } + + /** + * Deprecated - use IsBasedOnDBColumns instead + * @return bool + */ + public function IsDirectField() {return static::IsBasedOnDBColumns();} + + /** + * Returns true if the attribute value is built after DB columns + * @return bool + */ + static public function IsBasedOnDBColumns() {return false;} + /** + * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) + * @return bool + */ + static public function IsBasedOnOQLExpression() {return false;} + /** + * Returns true if the attribute value can be shown as a string + * @return bool + */ + static public function IsScalar() {return false;} + /** + * Returns true if the attribute value is a set of related objects (1-N or N-N) + * @return bool + */ + static public function IsLinkSet() {return false;} + + /** + * @param int $iType + * + * @return bool true if the attribute is an external key, either directly (RELATIVE to the host class), or indirectly (ABSOLUTELY) + */ + public function IsExternalKey($iType = EXTKEY_RELATIVE) + { + return false; + } + /** + * @return bool true if the attribute value is an external key, pointing to the host class + */ + static public function IsHierarchicalKey() {return false;} + /** + * @return bool true if the attribute value is stored on an object pointed to be an external key + */ + static public function IsExternalField() {return false;} + /** + * @return bool true if the attribute can be written (by essence : metamodel field option) + * @see \DBObject::IsAttributeReadOnlyForCurrentState() for a specific object instance (depending on its workflow) + */ + public function IsWritable() {return false;} + /** + * @return bool true if the attribute has been added automatically by the framework + */ + public function IsMagic() {return $this->GetOptional('magic', false);} + /** + * @return bool true if the attribute value is kept in the loaded object (in memory) + */ + static public function LoadInObject() {return true;} + /** + * @return bool true if the attribute value comes from the database in one way or another + */ + static public function LoadFromDB() {return true;} + /** + * @return bool true if the attribute should be loaded anytime (in addition to the column selected by the user) + */ + public function AlwaysLoadInTables() {return $this->GetOptional('always_load_in_tables', false);} + + /** + * @param \DBObject $oHostObject + * + * @return mixed Must return the value if LoadInObject returns false + */ + public function GetValue($oHostObject) + { + return null; + } + + /** + * Returns true if the attribute must not be stored if its current value is "null" (Cf. IsNull()) + * @return bool + */ + public function IsNullAllowed() {return true;} + /** + * Returns the attribute code (identifies the attribute in the host class) + * @return string + */ + public function GetCode() {return $this->m_sCode;} + + /** + * Find the corresponding "link" attribute on the target class, if any + * @return null | AttributeDefinition + */ + public function GetMirrorLinkAttribute() {return null;} + + /** + * Helper to browse the hierarchy of classes, searching for a label + * + * @param string $sDictEntrySuffix + * @param string $sDefault + * @param bool $bUserLanguageOnly + * + * @return string + * @throws \Exception + */ + protected function SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly) + { + $sLabel = Dict::S('Class:'.$this->m_sHostClass.$sDictEntrySuffix, '', $bUserLanguageOnly); + if (strlen($sLabel) == 0) + { + // Nothing found: go higher in the hierarchy (if possible) + // + $sLabel = $sDefault; + $sParentClass = MetaModel::GetParentClass($this->m_sHostClass); + if ($sParentClass) + { + if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) + { + $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode); + $sLabel = $oAttDef->SearchLabel($sDictEntrySuffix, $sDefault, $bUserLanguageOnly); + } + } + } + return $sLabel; + } + + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetLabel($sDefault = null) + { + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, null, true /*user lang*/); + if (is_null($sLabel)) + { + // If no default value is specified, let's define the most relevant one for developping purposes + if (is_null($sDefault)) + { + $sDefault = str_replace('_', ' ', $this->m_sCode); + } + // Browse the hierarchy again, accepting default (english) translations + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode, $sDefault, false); + } + return $sLabel; + } + + /** + * To be overloaded for localized enums + * + * @param string $sValue + * + * @return string label corresponding to the given value (in plain text) + */ + public function GetValueLabel($sValue) + { + return $sValue; + } + + /** + * Get the value from a given string (plain text, CSV import) + * + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return mixed null if no match could be found + */ + public function MakeValueFromString( + $sProposedValue, + $bLocalizedValue = false, + $sSepItem = null, + $sSepAttribute = null, + $sSepValue = null, + $sAttributeQualifier = null + ) + { + return $this->MakeRealValue($sProposedValue, null); + } + + /** + * Parses a search string coming from user input + * @param string $sSearchString + * @return string + */ + public function ParseSearchString($sSearchString) + { + return $sSearchString; + } + + /** + * @return string + * + * @throws \Exception + */ + public function GetLabel_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('label', $this->m_aParams)) + { + return $this->m_aParams['label']; + } + else + { + return $this->GetLabel(); + } + } + + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetDescription($sDefault = null) + { + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', null, true /*user lang*/); + if (is_null($sLabel)) + { + // If no default value is specified, let's define the most relevant one for developping purposes + if (is_null($sDefault)) + { + $sDefault = ''; + } + // Browse the hierarchy again, accepting default (english) translations + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'+', $sDefault, false); + } + return $sLabel; + } + + /** + * @param string|null $sDefault + * + * @return string + * + * @throws \Exception + */ + public function GetHelpOnEdition($sDefault = null) + { + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', null, true /*user lang*/); + if (is_null($sLabel)) + { + // If no default value is specified, let's define the most relevant one for developping purposes + if (is_null($sDefault)) + { + $sDefault = ''; + } + // Browse the hierarchy again, accepting default (english) translations + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'?', $sDefault, false); + } + return $sLabel; + } + + public function GetHelpOnSmartSearch() + { + $aParents = array_merge(array(get_class($this) => get_class($this)), class_parents($this)); + foreach ($aParents as $sClass) + { + $sHelp = Dict::S("Core:$sClass?SmartSearch", '-missing-'); + if ($sHelp != '-missing-') + { + return $sHelp; + } + } + return ''; + } + + /** + * @return string + * + * @throws \Exception + */ + public function GetDescription_Obsolete() + { + // Written for compatibility with a data model written prior to version 0.9.1 + if (array_key_exists('description', $this->m_aParams)) + { + return $this->m_aParams['description']; + } + else + { + return $this->GetDescription(); + } + } + + public function GetTrackingLevel() + { + return $this->GetOptional('tracking_level', ATTRIBUTE_TRACKING_ALL); + } + + /** + * @return \ValueSetObjects + */ + public function GetValuesDef() {return null;} + + public function GetPrerequisiteAttributes($sClass = null) + { + return array(); + } + + public function GetNullValue() {return null;} + + public function IsNull($proposedValue) + { + return is_null($proposedValue); + } + + /** + * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! + * + * @param $proposedValue + * @param $oHostObj + * + * @return mixed + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + return $proposedValue; + } + + public function Equals($val1, $val2) {return ($val1 == $val2);} + + /** + * @param string $sPrefix + * + * @return array suffix/expression pairs (1 in most of the cases), for READING (Select) + */ + public function GetSQLExpressions($sPrefix = '') + { + return array(); + } + + /** + * @param array $aCols + * @param string $sPrefix + * + * @return mixed a value out of suffix/value pairs, for SELECT result interpretation + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + return null; + } + + /** + * @param bool $bFullSpec + * + * @return array column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) + * @see \CMDBSource::GetFieldSpec() + */ + public function GetSQLColumns($bFullSpec = false) + { + return array(); + } + + /** + * @param $value + * + * @return array column/value pairs (1 in most of the cases), for WRITING (Insert, Update) + */ + public function GetSQLValues($value) + { + return array(); + } + public function RequiresIndex() {return false;} + + public function RequiresFullTextIndex() + { + return false; + } + public function CopyOnAllTables() {return false;} + + public function GetOrderBySQLExpressions($sClassAlias) + { + // Note: This is the responsibility of this function to place backticks around column aliases + return array('`'.$sClassAlias.$this->GetCode().'`'); + } + + public function GetOrderByHint() + { + return ''; + } + + // Import - differs slightly from SQL input, but identical in most cases + // + public function GetImportColumns() {return $this->GetSQLColumns();} + public function FromImportToValue($aCols, $sPrefix = '') + { + $aValues = array(); + foreach ($this->GetSQLExpressions($sPrefix) as $sAlias => $sExpr) + { + // This is working, based on the assumption that importable fields + // are not computed fields => the expression is the name of a column + $aValues[$sPrefix.$sAlias] = $aCols[$sExpr]; + } + return $this->FromSQLToValue($aValues, $sPrefix); + } + + public function GetValidationPattern() + { + return ''; + } + + public function CheckFormat($value) + { + return true; + } + + public function GetMaxSize() + { + return null; + } + + /** + * @return mixed|null + * @deprecated never used + */ + public function MakeValue() + { + $sComputeFunc = $this->Get("compute_func"); + if (empty($sComputeFunc)) return null; + + return call_user_func($sComputeFunc); + } + + abstract public function GetDefaultValue(DBObject $oHostObject = null); + + // + // To be overloaded in subclasses + // + + abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description" + abstract public function GetBasicFilterLooseOperator(); // returns an "opCode" + //abstract protected GetBasicFilterHTMLInput(); + abstract public function GetBasicFilterSQLExpr($sOpCode, $value); + + public function GetFilterDefinitions() + { + return array(); + } + + public function GetEditValue($sValue, $oHostObj = null) + { + return (string)$sValue; + } + + /** + * For fields containing a potential markup, return the value without this markup + * + * @param string $sValue + * @param \DBObject $oHostObj + * + * @return string + */ + public function GetAsPlainText($sValue, $oHostObj = null) + { + return (string) $this->GetEditValue($sValue, $oHostObj); + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param $value + * + * @return string + */ + public function GetForJSON($value) + { + // In most of the cases, that will be the expected behavior... + return $this->GetEditValue($value); + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param $json + * + * @return mixed + */ + public function FromJSONToValue($json) + { + // Passthrough in most of the cases + return $json; + } + + /** + * Override to display the value in the GUI + * + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + */ + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + return Str::pure2html((string)$sValue); + } + + /** + * Override to export the value in XML + * + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return mixed + */ + public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) + { + return Str::pure2xml((string)$sValue); + } + + /** + * Override to escape the value when read by DBObject::GetAsCSV() + * + * @param string $sValue + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return string + */ + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + return (string)$sValue; + } + + /** + * Override to differentiate a value displayed in the UI or in the history + * + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + */ + public function GetAsHTMLForHistory($sValue, $oHostObject = null, $bLocalize = true) + { + return $this->GetAsHTML($sValue, $oHostObject, $bLocalize); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\StringField'; + } + + /** + * Override to specify Field class + * + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is + * passed, MakeFormField behave more like a Prepare. + * + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\Field $oFormField + * + * @return null + * @throws \CoreException + * @throws \Exception + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + // This is a fallback in case the AttributeDefinition subclass has no overloading of this function. + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + //$oFormField->SetReadOnly(true); + } + + $oFormField->SetLabel($this->GetLabel()); + + // Attributes flags + // - Retrieving flags for the current object + if ($oObject->IsNew()) + { + $iFlags = $oObject->GetInitialStateAttributeFlags($this->GetCode()); + } + else + { + $iFlags = $oObject->GetAttributeFlags($this->GetCode()); + } + + // - Comparing flags + if ($this->IsWritable() && (!$this->IsNullAllowed() || (($iFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY))) + { + $oFormField->SetMandatory(true); + } + if ((!$oObject->IsNew() || !$oFormField->GetMandatory()) && (($iFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY)) + { + $oFormField->SetReadOnly(true); + } + + // CurrentValue + $oFormField->SetCurrentValue($oObject->Get($this->GetCode())); + + // Validation pattern + if ($this->GetValidationPattern() !== '') + { + $oFormField->AddValidator(new \Combodo\iTop\Form\Validator\Validator($this->GetValidationPattern())); + } + + return $oFormField; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text (unlocalized) representation', + 'html' => 'HTML representation', + 'label' => 'Localized representation', + 'text' => 'Plain text representation (without any markup)', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param mixed $value The current value of the field + * @param string $sVerb The verb specifying the representation of the value + * @param \DBObject $oHostObject + * @param bool $bLocalize Whether or not to localize the value + * + * @return mixed|null|string + * + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + if ($this->IsScalar()) + { + switch ($sVerb) + { + case '': + return $value; + + case 'html': + return $this->GetAsHtml($value, $oHostObject, $bLocalize); + + case 'label': + return $this->GetEditValue($value); + + case 'text': + return $this->GetAsPlainText($value); + break; + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + return null; + } + + /** + * @param array $aArgs + * @param string $sContains + * + * @return array|null + * @throws \CoreException + * @throws \OQLException + */ + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $oValSetDef = $this->GetValuesDef(); + if (!$oValSetDef) return null; + return $oValSetDef->GetValues($aArgs, $sContains); + } + + /** + * Explain the change of the attribute (history) + * + * @param string $sOldValue + * @param string $sNewValue + * @param string $sLabel + * + * @return string + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \DictExceptionMissingString + * @throws \OQLException + * @throws \Exception + */ + public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null) + { + if (is_null($sLabel)) + { + $sLabel = $this->GetLabel(); + } + + $sNewValueHtml = $this->GetAsHTMLForHistory($sNewValue); + $sOldValueHtml = $this->GetAsHTMLForHistory($sOldValue); + + if($this->IsExternalKey()) + { + /** @var \AttributeExternalKey $this */ + $sTargetClass = $this->GetTargetClass(); + $sOldValueHtml = (int)$sOldValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sOldValue) : null; + $sNewValueHtml = (int)$sNewValue ? MetaModel::GetHyperLink($sTargetClass, (int)$sNewValue) : null; + } + if ( (($this->GetType() == 'String') || ($this->GetType() == 'Text')) && + (strlen($sNewValue) > strlen($sOldValue)) ) + { + // Check if some text was not appended to the field + if (substr($sNewValue,0, strlen($sOldValue)) == $sOldValue) // Text added at the end + { + $sDelta = $this->GetAsHTML(substr($sNewValue, strlen($sOldValue))); + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); + } + else if (substr($sNewValue, -strlen($sOldValue)) == $sOldValue) // Text added at the beginning + { + $sDelta = $this->GetAsHTML(substr($sNewValue, 0, strlen($sNewValue) - strlen($sOldValue))); + $sResult = Dict::Format('Change:Text_AppendedTo_AttName', $sDelta, $sLabel); + } + else + { + if (strlen($sOldValue) == 0) + { + $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); + } + else + { + if (is_null($sNewValue)) + { + $sNewValueHtml = Dict::S('UI:UndefinedObject'); + } + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); + } + } + } + else + { + if (strlen($sOldValue) == 0) + { + $sResult = Dict::Format('Change:AttName_SetTo', $sLabel, $sNewValueHtml); + } + else + { + if (is_null($sNewValue)) + { + $sNewValueHtml = Dict::S('UI:UndefinedObject'); + } + $sResult = Dict::Format('Change:AttName_SetTo_NewValue_PreviousValue_OldValue', $sLabel, $sNewValueHtml, $sOldValueHtml); + } + } + return $sResult; + } + + + /** + * Parses a string to find some smart search patterns and build the corresponding search/OQL condition + * Each derived class is reponsible for defining and processing their own smart patterns, the base class + * does nothing special, and just calls the default (loose) operator + * + * @param string $sSearchText The search string to analyze for smart patterns + * @param \FieldExpression $oField + * @param array $aParams Values of the query parameters + * + * @return \Expression The search condition to be added (AND) to the current search + * + * @throws \CoreException + */ + public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams) + { + $sParamName = $oField->GetParent().'_'.$oField->GetName(); + $oRightExpr = new VariableExpression($sParamName); + $sOperator = $this->GetBasicFilterLooseOperator(); + switch ($sOperator) + { + case 'Contains': + $aParams[$sParamName] = "%$sSearchText%"; + $sSQLOperator = 'LIKE'; + break; + + default: + $sSQLOperator = $sOperator; + $aParams[$sParamName] = $sSearchText; + } + $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); + return $oNewCondition; + } + + /** + * Tells if an attribute is part of the unique fingerprint of the object (used for comparing two objects) + * All attributes which value is not based on a value from the object itself (like ExternalFields or LinkedSet) + * must be excluded from the object's signature + * @return boolean + */ + public function IsPartOfFingerprint() + { + return true; + } + + /** + * The part of the current attribute in the object's signature, for the supplied value + * @param mixed $value The value of this attribute for the object + * @return string The "signature" for this field/attribute + */ + public function Fingerprint($value) + { + return (string)$value; + } +} + +/** + * Set of objects directly linked to an object, and being part of its definition + * + * @package iTopORM + */ +class AttributeLinkedSet extends AttributeDefinition +{ + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max")); + } + + public function GetEditClass() {return "LinkedSet";} + + public function IsWritable() {return true;} + static public function IsLinkSet() {return true;} + public function IsIndirect() {return false;} + + public function GetValuesDef() {return $this->Get("allowed_values");} + public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} + + /** + * @param \DBObject|null $oHostObject + * + * @return \ormLinkSet + * + * @throws \Exception + * @throws \CoreException + * @throws \CoreWarning + */ + public function GetDefaultValue(DBObject $oHostObject = null) + { + $sLinkClass = $this->GetLinkedClass(); + $sExtKeyToMe = $this->GetExtKeyToMe(); + + // The class to target is not the current class, because if this is a derived class, + // it may differ from the target class, then things start to become confusing + /** @var \AttributeExternalKey $oRemoteExtKeyAtt */ + $oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe); + $sMyClass = $oRemoteExtKeyAtt->GetTargetClass(); + + $oMyselfSearch = new DBObjectSearch($sMyClass); + if ($oHostObject !== null) + { + $oMyselfSearch->AddCondition('id', $oHostObject->GetKey(), '='); + } + + $oLinkSearch = new DBObjectSearch($sLinkClass); + $oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe); + if ($this->IsIndirect()) + { + // Join the remote class so that the archive flag will be taken into account + /** @var \AttributeLinkedSetIndirect $this */ + $sExtKeyToRemote = $this->GetExtKeyToRemote(); + /** @var \AttributeExternalKey $oExtKeyToRemote */ + $oExtKeyToRemote = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToRemote); + $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); + if (MetaModel::IsArchivable($sRemoteClass)) + { + $oRemoteSearch = new DBObjectSearch($sRemoteClass); + /** @var \AttributeLinkedSetIndirect $this */ + $oLinkSearch->AddCondition_PointingTo($oRemoteSearch, $this->GetExtKeyToRemote()); + } + } + $oLinks = new DBObjectSet($oLinkSearch); + $oLinkSet = new ormLinkSet($this->GetHostClass(), $this->GetCode(), $oLinks); + return $oLinkSet; + } + + public function GetTrackingLevel() + { + return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default')); + } + + public function GetEditMode() + { + return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS); + } + + public function GetLinkedClass() {return $this->Get('linked_class');} + public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');} + + public function GetBasicFilterOperators() {return array();} + public function GetBasicFilterLooseOperator() {return '';} + public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} + + /** + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + */ + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (is_object($sValue) && ($sValue instanceof ormLinkSet)) + { + $sValue->Rewind(); + $aItems = array(); + while ($oObj = $sValue->Fetch()) + { + // Show only relevant information (hide the external key to the current object) + $aAttributes = array(); + foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef) + { + if ($sAttCode == $this->GetExtKeyToMe()) continue; + if ($oAttDef->IsExternalField()) continue; + $sAttValue = $oObj->GetAsHTML($sAttCode); + if (strlen($sAttValue) > 0) + { + $aAttributes[] = $sAttValue; + } + } + $sAttributes = implode(', ', $aAttributes); + $aItems[] = $sAttributes; + } + return implode('
', $aItems); + } + return null; + } + + /** + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * + * @throws \CoreException + */ + public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true) + { + if (is_object($sValue) && ($sValue instanceof ormLinkSet)) + { + $sValue->Rewind(); + $sRes = "\n"; + while ($oObj = $sValue->Fetch()) + { + $sObjClass = get_class($oObj); + $sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n"; + // Show only relevant information (hide the external key to the current object) + foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) + { + if ($sAttCode == 'finalclass') + { + if ($sObjClass == $this->GetLinkedClass()) + { + // Simplify the output if the exact class could be determined implicitely + continue; + } + } + if ($sAttCode == $this->GetExtKeyToMe()) continue; + if ($oAttDef->IsExternalField()) + { + /** @var \AttributeExternalField $oAttDef */ + if ($oAttDef->GetKeyAttCode() == $this->GetExtKeyToMe()) continue; + /** @var AttributeExternalField $oAttDef */ + if ($oAttDef->IsFriendlyName()) continue; + } + if ($oAttDef instanceof AttributeFriendlyName) continue; + if (!$oAttDef->IsScalar()) continue; + $sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize); + $sRes .= "<$sAttCode>$sAttValue\n"; + } + $sRes .= "\n"; + } + $sRes .= "\n"; + } + else + { + $sRes = ''; + } + return $sRes; + } + + /** + * @param $sValue + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return mixed|string + * @throws \CoreException + */ + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); + $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); + $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); + $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier'); + + if (is_object($sValue) && ($sValue instanceof ormLinkSet)) + { + $sValue->Rewind(); + $aItems = array(); + while ($oObj = $sValue->Fetch()) + { + $sObjClass = get_class($oObj); + // Show only relevant information (hide the external key to the current object) + $aAttributes = array(); + foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) + { + if ($sAttCode == 'finalclass') + { + if ($sObjClass == $this->GetLinkedClass()) + { + // Simplify the output if the exact class could be determined implicitely + continue; + } + } + if ($sAttCode == $this->GetExtKeyToMe()) continue; + if ($oAttDef->IsExternalField()) continue; + if (!$oAttDef->IsBasedOnDBColumns()) continue; + if (!$oAttDef->IsScalar()) continue; + $sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize); + if (strlen($sAttValue) > 0) + { + $sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, $sAttCode.$sSepValue.$sAttValue); + $aAttributes[] = $sAttributeQualifier.$sAttributeData.$sAttributeQualifier; + } + } + $sAttributes = implode($sSepAttribute, $aAttributes); + $aItems[] = $sAttributes; + } + $sRes = implode($sSepItem, $aItems); + } + else + { + $sRes = ''; + } + $sRes = str_replace($sTextQualifier, $sTextQualifier.$sTextQualifier, $sRes); + $sRes = $sTextQualifier.$sRes.$sTextQualifier; + return $sRes; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text (unlocalized) representation', + 'html' => 'HTML representation (unordered list)', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param mixed $value The current value of the field + * @param string $sVerb The verb specifying the representation of the value + * @param DBObject $oHostObject The object + * @param bool $bLocalize Whether or not to localize the value + * + * @return string + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + $sRemoteName = $this->IsIndirect() ? + /** @var \AttributeLinkedSetIndirect $this */ + $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname'; + + $oLinkSet = clone $value; // Workaround/Safety net for Trac #887 + $iLimit = MetaModel::GetConfig()->Get('max_linkset_output'); + $iCount = 0; + $aNames = array(); + foreach($oLinkSet as $oItem) + { + if (($iLimit > 0) && ($iCount == $iLimit)) + { + $iTotal = $oLinkSet->Count(); + $aNames[] = '... '.Dict::Format('UI:TruncatedResults', $iCount, $iTotal); + break; + } + $aNames[] = $oItem->Get($sRemoteName); + $iCount++; + } + + switch($sVerb) + { + case '': + return implode("\n", $aNames); + + case 'html': + return '
  • '.implode("
  • ", $aNames).'
'; + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + + public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never + + public function GetImportColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode()] = 'TEXT'; + return $aColumns; + } + + /** + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return \DBObjectSet|mixed + * @throws \CSVParserException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \MissingQueryArgument + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \Exception + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + if (is_null($sSepItem) || empty($sSepItem)) + { + $sSepItem = MetaModel::GetConfig()->Get('link_set_item_separator'); + } + if (is_null($sSepAttribute) || empty($sSepAttribute)) + { + $sSepAttribute = MetaModel::GetConfig()->Get('link_set_attribute_separator'); + } + if (is_null($sSepValue) || empty($sSepValue)) + { + $sSepValue = MetaModel::GetConfig()->Get('link_set_value_separator'); + } + if (is_null($sAttributeQualifier) || empty($sAttributeQualifier)) + { + $sAttributeQualifier = MetaModel::GetConfig()->Get('link_set_attribute_qualifier'); + } + + $sTargetClass = $this->Get('linked_class'); + + $sInput = str_replace($sSepItem, "\n", $sProposedValue); + $oCSVParser = new CSVParser($sInput, $sSepAttribute, $sAttributeQualifier); + + $aInput = $oCSVParser->ToArray(0 /* do not skip lines */); + + $aLinks = array(); + foreach($aInput as $aRow) + { + // 1st - get the values, split the extkey->searchkey specs, and eventually get the finalclass value + $aExtKeys = array(); + $aValues = array(); + foreach($aRow as $sCell) + { + $iSepPos = strpos($sCell, $sSepValue); + if ($iSepPos === false) + { + // Houston... + throw new CoreException('Wrong format for link attribute specification', array('value' => $sCell)); + } + + $sAttCode = trim(substr($sCell, 0, $iSepPos)); + $sValue = substr($sCell, $iSepPos + strlen($sSepValue)); + + if (preg_match('/^(.+)->(.+)$/', $sAttCode, $aMatches)) + { + $sKeyAttCode = $aMatches[1]; + $sRemoteAttCode = $aMatches[2]; + $aExtKeys[$sKeyAttCode][$sRemoteAttCode] = $sValue; + if (!MetaModel::IsValidAttCode($sTargetClass, $sKeyAttCode)) + { + throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sKeyAttCode)); + } + /** @var \AttributeExternalKey $oKeyAttDef */ + $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); + $sRemoteClass = $oKeyAttDef->GetTargetClass(); + if (!MetaModel::IsValidAttCode($sRemoteClass, $sRemoteAttCode)) + { + throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sRemoteClass, 'attcode' => $sRemoteAttCode)); + } + } + else + { + if(!MetaModel::IsValidAttCode($sTargetClass, $sAttCode)) + { + throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode)); + } + $oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode); + $aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + } + } + + // 2nd - Instanciate the object and set the value + if (isset($aValues['finalclass'])) + { + $sLinkClass = $aValues['finalclass']; + if (!is_subclass_of($sLinkClass, $sTargetClass)) + { + throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); + } + } + elseif (MetaModel::IsAbstract($sTargetClass)) + { + throw new CoreException('Missing finalclass for link attribute specification'); + } + else + { + $sLinkClass = $sTargetClass; + } + + $oLink = MetaModel::NewObject($sLinkClass); + foreach ($aValues as $sAttCode => $sValue) + { + $oLink->Set($sAttCode, $sValue); + } + + // 3rd - Set external keys from search conditions + foreach ($aExtKeys as $sKeyAttCode => $aReconciliation) + { + $oKeyAttDef = MetaModel::GetAttributeDef($sTargetClass, $sKeyAttCode); + $sKeyClass = $oKeyAttDef->GetTargetClass(); + $oExtKeyFilter = new DBObjectSearch($sKeyClass); + $aReconciliationDesc = array(); + foreach($aReconciliation as $sRemoteAttCode => $sValue) + { + $oExtKeyFilter->AddCondition($sRemoteAttCode, $sValue, '='); + $aReconciliationDesc[] = "$sRemoteAttCode=$sValue"; + } + $oExtKeySet = new DBObjectSet($oExtKeyFilter); + switch($oExtKeySet->Count()) + { + case 0: + $sReconciliationDesc = implode(', ', $aReconciliationDesc); + throw new CoreException("Found no match", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); + break; + case 1: + $oRemoteObj = $oExtKeySet->Fetch(); + $oLink->Set($sKeyAttCode, $oRemoteObj->GetKey()); + break; + default: + $sReconciliationDesc = implode(', ', $aReconciliationDesc); + throw new CoreException("Found several matches", array('ext_key' => $sKeyAttCode, 'reconciliation' => $sReconciliationDesc)); + // Found several matches, ambiguous + } + } + + // Check (roughly) if such a link is valid + $aErrors = array(); + foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + /** @var \AttributeExternalKey $oAttDef */ + if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) + { + continue; // Don't check the key to self + } + } + + if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) + { + $aErrors[] = $sAttCode; + } + } + if (count($aErrors) > 0) + { + throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors)); + } + + $aLinks[] = $oLink; + } + $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); + return $oSet; + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param \ormLinkSet $value + * + * @return array + * @throws \CoreException + */ + public function GetForJSON($value) + { + $aRet = array(); + if (is_object($value) && ($value instanceof ormLinkSet)) + { + $value->Rewind(); + while ($oObj = $value->Fetch()) + { + $sObjClass = get_class($oObj); + // Show only relevant information (hide the external key to the current object) + $aAttributes = array(); + foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef) + { + if ($sAttCode == 'finalclass') + { + if ($sObjClass == $this->GetLinkedClass()) + { + // Simplify the output if the exact class could be determined implicitely + continue; + } + } + if ($sAttCode == $this->GetExtKeyToMe()) continue; + if ($oAttDef->IsExternalField()) continue; + if (!$oAttDef->IsBasedOnDBColumns()) continue; + if (!$oAttDef->IsScalar()) continue; + $attValue = $oObj->Get($sAttCode); + $aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue); + } + $aRet[] = $aAttributes; + } + } + return $aRet; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param $json + * + * @return \DBObjectSet + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function FromJSONToValue($json) + { + $sTargetClass = $this->Get('linked_class'); + + $aLinks = array(); + foreach($json as $aValues) + { + if (isset($aValues['finalclass'])) + { + $sLinkClass = $aValues['finalclass']; + if (!is_subclass_of($sLinkClass, $sTargetClass)) + { + throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass)); + } + } + elseif (MetaModel::IsAbstract($sTargetClass)) + { + throw new CoreException('Missing finalclass for link attribute specification'); + } + else + { + $sLinkClass = $sTargetClass; + } + + $oLink = MetaModel::NewObject($sLinkClass); + foreach ($aValues as $sAttCode => $sValue) + { + $oLink->Set($sAttCode, $sValue); + } + + // Check (roughly) if such a link is valid + $aErrors = array(); + foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + /** @var AttributeExternalKey $oAttDef */ + if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass()))) + { + continue; // Don't check the key to self + } + } + + if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed()) + { + $aErrors[] = $sAttCode; + } + } + if (count($aErrors) > 0) + { + throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors)); + } + + $aLinks[] = $oLink; + } + $oSet = DBObjectSet::FromArray($sTargetClass, $aLinks); + return $oSet; + } + + /** + * @param $proposedValue + * @param $oHostObj + * + * @return mixed + * @throws \Exception + */ + public function MakeRealValue($proposedValue, $oHostObj){ + if($proposedValue === null) + { + $sLinkedClass = $this->GetLinkedClass(); + $aLinkedObjectsArray = array(); + $oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray); + + return new ormLinkSet( + get_class($oHostObj), + $this->GetCode(), + $oSet + ); + } + + return $proposedValue; + } + + /** + * @param ormLinkSet $val1 + * @param ormLinkSet $val2 + * @return bool + */ + public function Equals($val1, $val2) + { + if ($val1 === $val2) + { + $bAreEquivalent = true; + } + else + { + $bAreEquivalent = ($val2->HasDelta() === false); + } + return $bAreEquivalent; + } + + /** + * Find the corresponding "link" attribute on the target class, if any + * + * @return null | AttributeDefinition + * @throws \Exception + */ + public function GetMirrorLinkAttribute() + { + $oRemoteAtt = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToMe()); + return $oRemoteAtt; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\LinkedSetField'; + } + + /** + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\LinkedSetField $oFormField + * + * @return \Combodo\iTop\Form\Field\LinkedSetField + * @throws \CoreException + * @throws \DictExceptionMissingString + * @throws \Exception + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + // Setting target class + if (!$this->IsIndirect()) + { + $sTargetClass = $this->GetLinkedClass(); + } + else + { + /** @var \AttributeExternalKey $oRemoteAttDef */ + /** @var \AttributeLinkedSetIndirect $this */ + $oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); + $sTargetClass = $oRemoteAttDef->GetTargetClass(); + + /** @var \AttributeLinkedSetIndirect $this */ + $oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote()); + } + $oFormField->SetTargetClass($sTargetClass); + $oFormField->SetIndirect($this->IsIndirect()); + // Setting attcodes to display + $aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list')); + // - Adding friendlyname attribute to the list is not already in it + $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass); + if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) + { + $aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay); + } + // - Adding attribute labels + $aAttributesToDisplay = array(); + foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) + { + $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay); + $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel(); + } + $oFormField->SetAttributesToDisplay($aAttributesToDisplay); + + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + public function IsPartOfFingerprint() { return false; } +} + +/** + * Set of objects linked to an object (n-n), and being part of its definition + * + * @package iTopORM + */ +class AttributeLinkedSetIndirect extends AttributeLinkedSet +{ + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote")); + } + + public function IsIndirect() + { + return true; + } + + public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); } + public function GetEditClass() {return "LinkedSet";} + public function DuplicatesAllowed() {return $this->GetOptional("duplicates", false);} // The same object may be linked several times... or not... + + public function GetTrackingLevel() + { + return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_indirect_default')); + } + + /** + * Find the corresponding "link" attribute on the target class, if any + * @return null | AttributeDefinition + * @throws \CoreException + */ + public function GetMirrorLinkAttribute() + { + $oRet = null; + /** @var \AttributeExternalKey $oExtKeyToRemote */ + $oExtKeyToRemote = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); + $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); + foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) + { + if (!$oRemoteAttDef instanceof AttributeLinkedSetIndirect) continue; + if ($oRemoteAttDef->GetLinkedClass() != $this->GetLinkedClass()) continue; + if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetExtKeyToRemote()) continue; + if ($oRemoteAttDef->GetExtKeyToRemote() != $this->GetExtKeyToMe()) continue; + $oRet = $oRemoteAttDef; + break; + } + return $oRet; + } +} + +/** + * Abstract class implementing default filters for a DB column + * + * @package iTopORM + */ +class AttributeDBFieldVoid extends AttributeDefinition +{ + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("allowed_values", "depends_on", "sql")); + } + + // To be overriden, used in GetSQLColumns + protected function GetSQLCol($bFullSpec = false) + { + return 'VARCHAR(255)' + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + protected function GetSQLColSpec() + { + $default = $this->ScalarToSQL($this->GetDefaultValue()); + if (is_null($default)) + { + $sRet = ''; + } + else + { + if (is_numeric($default)) + { + // Though it is a string in PHP, it will be considered as a numeric value in MySQL + // Then it must not be quoted here, to preserve the compatibility with the value returned by CMDBSource::GetFieldSpec + $sRet = " DEFAULT $default"; + } + else + { + $sRet = " DEFAULT ".CMDBSource::Quote($default); + } + } + return $sRet; + } + + public function GetEditClass() {return "String";} + + public function GetValuesDef() {return $this->Get("allowed_values");} + public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");} + + static public function IsBasedOnDBColumns() {return true;} + static public function IsScalar() {return true;} + public function IsWritable() {return !$this->IsMagic();} + public function GetSQLExpr() + { + return $this->Get("sql"); + } + + public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} + public function IsNullAllowed() {return false;} + + // + protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) + + public function GetSQLExpressions($sPrefix = '') + { + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $this->Get("sql"); + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + $value = $this->MakeRealValue($aCols[$sPrefix.''], null); + return $value; + } + public function GetSQLValues($value) + { + $aValues = array(); + $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); + return $aValues; + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->Get("sql")] = $this->GetSQLCol($bFullSpec); + return $aColumns; + } + + public function GetFilterDefinitions() + { + return array($this->GetCode() => new FilterFromAttribute($this)); + } + + public function GetBasicFilterOperators() + { + return array("="=>"equals", "!="=>"differs from"); + } + public function GetBasicFilterLooseOperator() + { + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '=': + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } +} + +/** + * Base class for all kind of DB attributes, with the exception of external keys + * + * @package iTopORM + */ +class AttributeDBField extends AttributeDBFieldVoid +{ + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed")); + } + public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue($this->Get("default_value"), $oHostObject);} + public function IsNullAllowed() {return $this->Get("is_null_allowed");} +} + +/** + * Map an integer column to an attribute + * + * @package iTopORM + */ +class AttributeInteger extends AttributeDBField +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_NUMERIC; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "String";} + protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? $this->GetSQLColSpec() : '');} + + public function GetValidationPattern() + { + return "^[0-9]+$"; + } + + public function GetBasicFilterOperators() + { + return array( + "!="=>"differs from", + "="=>"equals", + ">"=>"greater (strict) than", + ">="=>"greater than", + "<"=>"less (strict) than", + "<="=>"less than", + "in"=>"in" + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement an "equals approximately..." or "same order of magnitude" + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '>': + return $this->GetSQLExpr()." > $sQValue"; + break; + case '>=': + return $this->GetSQLExpr()." >= $sQValue"; + break; + case '<': + return $this->GetSQLExpr()." < $sQValue"; + break; + case '<=': + return $this->GetSQLExpr()." <= $sQValue"; + break; + case 'in': + if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; + break; + + case '=': + default: + return $this->GetSQLExpr()." = \"$value\""; + } + } + + public function GetNullValue() + { + return null; + } + public function IsNull($proposedValue) + { + return is_null($proposedValue); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return null; + if ($proposedValue === '') return null; // 0 is transformed into '' ! + return (int)$proposedValue; + } + + public function ScalarToSQL($value) + { + assert(is_numeric($value) || is_null($value)); + return $value; // supposed to be an int + } +} + +/** + * An external key for which the class is defined as the value of another attribute + * + * @package iTopORM + */ +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')); + } + + public function GetEditClass() {return "String";} + protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} + + public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} + public function IsNullAllowed() + { + return $this->Get("is_null_allowed"); + } + + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return parent::GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return parent::GetBasicFilterSQLExpr($sOpCode, $value); + } + + public function GetNullValue() + { + return 0; + } + + public function IsNull($proposedValue) + { + return ($proposedValue == 0); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return 0; + if ($proposedValue === '') return 0; + if (MetaModel::IsValidObject($proposedValue)) { + /** @var \DBObject $proposedValue */ + return $proposedValue->GetKey(); + } + return (int)$proposedValue; + } +} + +/** + * Display an integer between 0 and 100 as a percentage / horizontal bar graph + * + * @package iTopORM + */ +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... + $iValue = (int)$sValue; + if ($iValue > 100) + { + $iValue = 100; + } + else if ($iValue < 0) + { + $iValue = 0; + } + if ($iValue > 90) + { + $sColor = "#cc3300"; + } + else if ($iValue > 50) + { + $sColor = "#cccc00"; + } + else + { + $sColor = "#33cc00"; + } + $iPercentWidth = ($iWidth * $iValue) / 100; + return "
 
 $sValue %"; + } +} + +/** + * Map a decimal value column (suitable for financial computations) to an attribute + * internally in PHP such numbers are represented as string. Should you want to perform + * a calculation on them, it is recommended to use the BC Math functions in order to + * retain the precision + * + * @package iTopORM + */ +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 */)); + } + + public function GetEditClass() {return "String";} + protected function GetSQLCol($bFullSpec = false) + { + return "DECIMAL(".$this->Get('digits').",".$this->Get('decimals').")".($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function GetValidationPattern() + { + $iNbDigits = $this->Get('digits'); + $iPrecision = $this->Get('decimals'); + $iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below + return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$"; + } + + public function GetBasicFilterOperators() + { + return array( + "!="=>"differs from", + "="=>"equals", + ">"=>"greater (strict) than", + ">="=>"greater than", + "<"=>"less (strict) than", + "<="=>"less than", + "in"=>"in" + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement an "equals approximately..." or "same order of magnitude" + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '>': + return $this->GetSQLExpr()." > $sQValue"; + break; + case '>=': + return $this->GetSQLExpr()." >= $sQValue"; + break; + case '<': + return $this->GetSQLExpr()." < $sQValue"; + break; + case '<=': + return $this->GetSQLExpr()." <= $sQValue"; + break; + case 'in': + if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')"); + return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; + break; + + case '=': + default: + return $this->GetSQLExpr()." = \"$value\""; + } + } + + public function GetNullValue() + { + return null; + } + public function IsNull($proposedValue) + { + return is_null($proposedValue); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return null; + if ($proposedValue === '') return null; + return (string)$proposedValue; + } + + public function ScalarToSQL($value) + { + assert(is_null($value) || preg_match('/'.$this->GetValidationPattern().'/', $value)); + return $value; // null or string + } +} + +/** + * Map a boolean column to an attribute + * + * @package iTopORM + */ +class AttributeBoolean extends AttributeInteger +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "Integer";} + protected function GetSQLCol($bFullSpec = false) {return "TINYINT(1)".($bFullSpec ? $this->GetSQLColSpec() : '');} + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return null; + if ($proposedValue === '') return null; + if ((int)$proposedValue) return true; + return false; + } + + public function ScalarToSQL($value) + { + if ($value) return 1; + return 0; + } + + public function GetValueLabel($bValue) + { + if (is_null($bValue)) + { + $sLabel = Dict::S('Core:'.get_class($this).'/Value:null'); + } + else + { + $sValue = $bValue ? 'yes' : 'no'; + $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue); + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, true /*user lang*/); + } + return $sLabel; + } + + public function GetValueDescription($bValue) + { + if (is_null($bValue)) + { + $sDescription = Dict::S('Core:'.get_class($this).'/Value:null+'); + } + else + { + $sValue = $bValue ? 'yes' : 'no'; + $sDefault = Dict::S('Core:'.get_class($this).'/Value:'.$sValue.'+'); + $sDescription = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue.'+', $sDefault, true /*user lang*/); + } + return $sDescription; + } + + public function GetAsHTML($bValue, $oHostObject = null, $bLocalize = true) + { + if (is_null($bValue)) + { + $sRes = ''; + } + elseif ($bLocalize) + { + $sLabel = $this->GetValueLabel($bValue); + $sDescription = $this->GetValueDescription($bValue); + // later, we could imagine a detailed description in the title + $sRes = "".parent::GetAsHtml($sLabel).""; + } + else + { + $sRes = $bValue ? 'yes' : 'no'; + } + return $sRes; + } + + public function GetAsXML($bValue, $oHostObject = null, $bLocalize = true) + { + if (is_null($bValue)) + { + $sFinalValue = ''; + } + elseif ($bLocalize) + { + $sFinalValue = $this->GetValueLabel($bValue); + } + else + { + $sFinalValue = $bValue ? 'yes' : 'no'; + } + $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); + return $sRes; + } + + public function GetAsCSV($bValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if (is_null($bValue)) + { + $sFinalValue = ''; + } + elseif ($bLocalize) + { + $sFinalValue = $this->GetValueLabel($bValue); + } + else + { + $sFinalValue = $bValue ? 'yes' : 'no'; + } + $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); + return $sRes; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\SelectField'; + } + + /** + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\SelectField $oFormField + * + * @return \Combodo\iTop\Form\Field\SelectField + * @throws \CoreException + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + $oFormField->SetChoices(array('yes' => $this->GetValueLabel(true), 'no' => $this->GetValueLabel(false))); + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + public function GetEditValue($value, $oHostObj = null) + { + if (is_null($value)) + { + return ''; + } + else + { + return $this->GetValueLabel($value); + } + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param $value + * + * @return bool + */ + public function GetForJSON($value) + { + return (bool)$value; + } + + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + $sInput = strtolower(trim($sProposedValue)); + if ($bLocalizedValue) + { + switch ($sInput) + { + case '1': // backward compatibility + case $this->GetValueLabel(true): + $value = true; + break; + case '0': // backward compatibility + case 'no': + case $this->GetValueLabel(false): + $value = false; + break; + default: + $value = null; + } + } + else + { + switch ($sInput) + { + case '1': // backward compatibility + case 'yes': + $value = true; + break; + case '0': // backward compatibility + case 'no': + $value = false; + break; + default: + $value = null; + } + } + return $value; + } +} + +/** + * Map a varchar column (size < ?) to an attribute + * + * @package iTopORM + */ +class AttributeString extends AttributeDBField +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "String";} + + protected function GetSQLCol($bFullSpec = false) + { + return 'VARCHAR(255)' + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function GetValidationPattern() + { + $sPattern = $this->GetOptional('validation_pattern', ''); + if (empty($sPattern)) + { + return parent::GetValidationPattern(); + } + else + { + return $sPattern; + } + } + + public function CheckFormat($value) + { + $sRegExp = $this->GetValidationPattern(); + if (empty($sRegExp)) + { + return true; + } + else + { + $sRegExp = str_replace('/', '\\/', $sRegExp); + return preg_match("/$sRegExp/", $value); + } + } + + public function GetMaxSize() + { + return 255; + } + + public function GetBasicFilterOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "Like"=>"equals (no case)", + "NotLike"=>"differs from (no case)", + "Contains"=>"contains", + "Begins with"=>"begins with", + "Finishes with"=>"finishes with" + ); + } + public function GetBasicFilterLooseOperator() + { + return "Contains"; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '=': + case '!=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'Begins with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%"); + case 'Finishes with': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value"); + case 'Contains': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); + case 'NotLike': + return $this->GetSQLExpr()." NOT LIKE $sQValue"; + case 'Like': + default: + return $this->GetSQLExpr()." LIKE $sQValue"; + } + } + + public function GetNullValue() + { + return ''; + } + + public function IsNull($proposedValue) + { + return ($proposedValue == ''); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return ''; + return (string)$proposedValue; + } + + public function ScalarToSQL($value) + { + if (!is_string($value) && !is_null($value)) + { + throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + } + return $value; + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; + } + + public function GetDisplayStyle() + { + return $this->GetOptional('display_style', 'select'); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\StringField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + +} + +/** + * An attibute that matches an object class + * + * @package iTopORM + */ +class AttributeClass extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM; + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("class_category", "more_values")); + } + + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $aParams["allowed_values"] = new ValueSetEnumClasses($aParams['class_category'], $aParams['more_values']); + parent::__construct($sCode, $aParams); + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + $sDefault = parent::GetDefaultValue($oHostObject); + if (!$this->IsNullAllowed() && $this->IsNull($sDefault)) + { + // For this kind of attribute specifying null as default value + // is authorized even if null is not allowed + + // Pick the first one... + $aClasses = $this->GetAllowedValues(); + $sDefault = key($aClasses); + } + return $sDefault; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) return ''; + return MetaModel::GetName($sValue); + } + + public function RequiresIndex() + { + return true; + } + + public function GetBasicFilterLooseOperator() + { + return '='; + } + +} + +/** + * An attibute that matches one of the language codes availables in the dictionnary + * + * @package iTopORM + */ +class AttributeApplicationLanguage extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + } + + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $aAvailableLanguages = Dict::GetLanguages(); + $aLanguageCodes = array(); + foreach($aAvailableLanguages as $sLangCode => $aInfo) + { + $aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')'; + } + $aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes); + parent::__construct($sCode, $aParams); + } + + public function RequiresIndex() + { + return true; + } + + public function GetBasicFilterLooseOperator() + { + return '='; + } +} + +/** + * The attribute dedicated to the finalclass automatic attribute + * + * @package iTopORM + */ +class AttributeFinalClass extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + public $m_sValue; + + public function __construct($sCode, $aParams) + { + $this->m_sCode = $sCode; + $aParams["allowed_values"] = null; + parent::__construct($sCode, $aParams); + + $this->m_sValue = $this->Get("default_value"); + } + + public function IsWritable() + { + return false; + } + public function IsMagic() + { + return true; + } + + public function RequiresIndex() + { + return true; + } + + public function SetFixedValue($sValue) + { + $this->m_sValue = $sValue; + } + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->m_sValue; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) return ''; + if ($bLocalize) + { + return MetaModel::GetName($sValue); + } + else + { + return $sValue; + } + } + + /** + * An enum can be localized + * + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return mixed|null|string + * @throws \CoreException + * @throws \OQLException + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + if ($bLocalizedValue) + { + // Lookup for the value matching the input + // + $sFoundValue = null; + $aRawValues = self::GetAllowedValues(); + if (!is_null($aRawValues)) + { + foreach ($aRawValues as $sKey => $sValue) + { + if ($sProposedValue == $sValue) + { + $sFoundValue = $sKey; + break; + } + } + } + if (is_null($sFoundValue)) + { + return null; + } + return $this->MakeRealValue($sFoundValue, null); + } + else + { + return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + } + } + + + // Because this is sometimes used to get a localized/string version of an attribute... + public function GetEditValue($sValue, $oHostObj = null) + { + if (empty($sValue)) return ''; + return MetaModel::GetName($sValue); + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param $value + * + * @return string + */ + public function GetForJSON($value) + { + // JSON values are NOT localized + return $value; + } + + /** + * @param $value + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return string + * @throws \CoreException + * @throws \DictExceptionMissingString + */ + public function GetAsCSV( + $value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false + ) + { + if ($bLocalize && $value != '') + { + $sRawValue = MetaModel::GetName($value); + } + else + { + $sRawValue = $value; + } + return parent::GetAsCSV($sRawValue, $sSeparator, $sTextQualifier, null, false, $bConvertToPlainText); + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (empty($value)) return ''; + if ($bLocalize) + { + $sRawValue = MetaModel::GetName($value); + } + else + { + $sRawValue = $value; + } + return Str::pure2xml($sRawValue); + } + + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetValueLabel($sValue) + { + if (empty($sValue)) return ''; + return MetaModel::GetName($sValue); + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $aRawValues = MetaModel::EnumChildClasses($this->GetHostClass(), ENUM_CHILD_CLASSES_ALL); + $aLocalizedValues = array(); + foreach ($aRawValues as $sClass) + { + $aLocalizedValues[$sClass] = MetaModel::GetName($sClass); + } + return $aLocalizedValues; + } +} + + +/** + * Map a varchar column (size < ?) to an attribute that must never be shown to the user + * + * @package iTopORM + */ +class AttributePassword extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "Password";} + + protected function GetSQLCol($bFullSpec = false) + { + return "VARCHAR(64)" + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function GetMaxSize() + { + return 64; + } + + public function GetFilterDefinitions() + { + // Note: due to this, you will get an error if a password is being declared as a search criteria (see ZLists) + // not allowed to search on passwords! + return array(); + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (strlen($sValue) == 0) + { + return ''; + } + else + { + return '******'; + } + } + + public function IsPartOfFingerprint() { return false; } // Cannot reliably compare two encrypted passwords since the same password will be encrypted in diffferent manners depending on the random 'salt' +} + +/** + * Map a text column (size < 255) to an attribute that is encrypted in the database + * The encryption is based on a key set per iTop instance. Thus if you export your + * database (in SQL) to someone else without providing the key at the same time + * the encrypted fields will remain encrypted + * + * @package iTopORM + */ +class AttributeEncryptedString extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + static $sKey = null; // Encryption key used for all encrypted fields + static $sLibrary = null; // Encryption library used for all encrypted fields + public function __construct($sCode, $aParams) + { + parent::__construct($sCode, $aParams); + if (self::$sKey == null) + { + self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); + } + if(self::$sLibrary == null) + { + self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); + } + } + /** + * When the attribute definitions are stored in APC cache: + * 1) The static class variable $sKey is NOT serialized + * 2) The object's constructor is NOT called upon wakeup + * 3) mcrypt may crash the server if passed an empty key !! + * + * So let's restore the key (if needed) when waking up + **/ + public function __wakeup() + { + if (self::$sKey == null) + { + self::$sKey = MetaModel::GetConfig()->GetEncryptionKey(); + } + if(self::$sLibrary == null) + { + self::$sLibrary = MetaModel::GetConfig()->GetEncryptionLibrary(); + } + } + + + protected function GetSQLCol($bFullSpec = false) {return "TINYBLOB";} + + public function GetMaxSize() + { + return 255; + } + + public function GetFilterDefinitions() + { + // Note: due to this, you will get an error if a an encrypted field is declared as a search criteria (see ZLists) + // not allowed to search on encrypted fields ! + return array(); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return null; + return (string)$proposedValue; + } + + /** + * Decrypt the value when reading from the database + * + * @param array $aCols + * @param string $sPrefix + * + * @return string + * @throws \Exception + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); + $sValue = $oSimpleCrypt->Decrypt(self::$sKey, $aCols[$sPrefix]); + return $sValue; + } + + /** + * Encrypt the value before storing it in the database + * + * @param $value + * + * @return array + * @throws \Exception + */ + public function GetSQLValues($value) + { + $oSimpleCrypt = new SimpleCrypt(self::$sLibrary); + $encryptedValue = $oSimpleCrypt->Encrypt(self::$sKey, $value); + + $aValues = array(); + $aValues[$this->Get("sql")] = $encryptedValue; + return $aValues; + } +} + + +// Wiki formatting - experimental +// +// [[:]] +// Example: [[Server:db1.tnut.com]] +define('WIKI_OBJECT_REGEXP', '/\[\[(.+):(.+)\]\]/U'); + + +/** + * Map a text column (size > ?) to an attribute + * + * @package iTopORM + */ +class AttributeText extends AttributeString +{ + public function GetEditClass() {return ($this->GetFormat() == 'text') ? 'Text' : "HTML";} + + protected function GetSQLCol($bFullSpec = false) + { + return "TEXT".CMDBSource::GetSqlStringColumnDefinition(); + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->Get('sql')] = $this->GetSQLCol($bFullSpec); + if ($this->GetOptional('format', null) != null ) + { + // Add the extra column only if the property 'format' is specified for the attribute + $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')".CMDBSource::GetSqlStringColumnDefinition(); + if ($bFullSpec) + { + $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'text'"; // default 'text' is for migrating old records + } + } + return $aColumns; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->Get('sql'); + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix; + if ($this->GetOptional('format', null) != null ) + { + // Add the extra column only if the property 'format' is specified for the attribute + $aColumns['_format'] = $sPrefix.'_format'; + } + return $aColumns; + } + + public function GetMaxSize() + { + // Is there a way to know the current limitation for mysql? + // See mysql_field_len() + return 65535; + } + + static public function RenderWikiHtml($sText, $bWikiOnly = false) + { + if (!$bWikiOnly) + { + $sPattern = '/'.str_replace('/', '\/', utils::GetConfig()->Get('url_validation_pattern')).'/i'; + if (preg_match_all($sPattern, $sText, $aAllMatches, PREG_SET_ORDER /* important !*/ |PREG_OFFSET_CAPTURE /* important ! */)) + { + $i = count($aAllMatches); + // Replace the URLs by an actual hyperlink ... + // Let's do it backwards so that the initial positions are not modified by the replacement + // This works if the matches are captured: in the order they occur in the string AND + // with their offset (i.e. position) inside the string + while($i > 0) + { + $i--; + $sUrl = $aAllMatches[$i][0][0]; // String corresponding to the main pattern + $iPos = $aAllMatches[$i][0][1]; // Position of the main pattern + $sText = substr_replace($sText, "$sUrl", $iPos, strlen($sUrl)); + + } + } + } + if (preg_match_all(WIKI_OBJECT_REGEXP, $sText, $aAllMatches, PREG_SET_ORDER)) + { + foreach($aAllMatches as $iPos => $aMatches) + { + $sClass = trim($aMatches[1]); + $sName = trim($aMatches[2]); + + if (MetaModel::IsValidClass($sClass)) + { + $oObj = MetaModel::GetObjectByName($sClass, $sName, false /* MustBeFound */); + if (is_object($oObj)) + { + // Propose a std link to the object + $sText = str_replace($aMatches[0], $oObj->GetHyperlink(), $sText); + } + else + { + // Propose a std link to the object + $sClassLabel = MetaModel::GetName($sClass); + $sText = str_replace($aMatches[0], "$sClassLabel:$sName", $sText); + // Later: propose a link to create a new object + // Anyhow... there is no easy way to suggest default values based on the given FRIENDLY name + //$sText = preg_replace('/\[\[(.+):(.+)\]\]/', ''.$sName.'', $sText); + } + } + } + } + return $sText; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + $aStyles = array(); + if ($this->GetWidth() != '') + { + $aStyles[] = 'width:'.$this->GetWidth(); + } + if ($this->GetHeight() != '') + { + $aStyles[] = 'height:'.$this->GetHeight(); + } + $sStyle = ''; + if (count($aStyles) > 0) + { + $aStyles[] = 'overflow:auto'; + $sStyle = 'style="'.implode(';', $aStyles).'"'; + } + + if ($this->GetFormat() == 'text') + { + $sValue = parent::GetAsHTML($sValue, $oHostObject, $bLocalize); + $sValue = self::RenderWikiHtml($sValue); + return "
".str_replace("\n", "
\n", $sValue).'
'; + } + else + { + $sValue = self::RenderWikiHtml($sValue, true /* wiki only */); + return "
".InlineImage::FixUrls($sValue).'
'; + } + + } + + public function GetEditValue($sValue, $oHostObj = null) + { + if ($this->GetFormat() == 'text') + { + if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) + { + foreach($aAllMatches as $iPos => $aMatches) + { + $sClass = $aMatches[1]; + $sName = $aMatches[2]; + + if (MetaModel::IsValidClass($sClass)) + { + $sClassLabel = MetaModel::GetName($sClass); + $sValue = str_replace($aMatches[0], "[[$sClassLabel:$sName]]", $sValue); + } + } + } + } + else + { + $sValue = str_replace('&', '&', $sValue); + } + return $sValue; + } + + /** + * For fields containing a potential markup, return the value without this markup + * + * @param string $sValue + * @param \DBObject $oHostObj + * + * @return string + */ + public function GetAsPlainText($sValue, $oHostObj = null) + { + if ($this->GetFormat() == 'html') + { + return (string) utils::HtmlToText($this->GetEditValue($sValue, $oHostObj)); + } + else + { + return parent::GetAsPlainText($sValue, $oHostObj); + } + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + $sValue = $proposedValue; + switch ($this->GetFormat()) + { + case 'html': + if (($sValue !== null) && ($sValue !== '')) + { + $sValue = HTMLSanitizer::Sanitize($sValue); + } + break; + + case 'text': + default: + if (preg_match_all(WIKI_OBJECT_REGEXP, $sValue, $aAllMatches, PREG_SET_ORDER)) + { + foreach($aAllMatches as $iPos => $aMatches) + { + $sClassLabel = $aMatches[1]; + $sName = $aMatches[2]; + + if (!MetaModel::IsValidClass($sClassLabel)) + { + $sClass = MetaModel::GetClassFromLabel($sClassLabel); + if ($sClass) + { + $sValue = str_replace($aMatches[0], "[[$sClass:$sName]]", $sValue); + } + } + } + } + } + return $sValue; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + return Str::pure2xml($value); + } + + public function GetWidth() + { + return $this->GetOptional('width', ''); + } + + public function GetHeight() + { + return $this->GetOptional('height', ''); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\TextAreaField'; + } + + /** + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\TextAreaField $oFormField + * + * @return \Combodo\iTop\Form\Field\TextAreaField + * @throws \CoreException + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + /** @var \Combodo\iTop\Form\Field\TextAreaField $oFormField */ + $oFormField = new $sFormFieldClass($this->GetCode(), null, $oObject); + $oFormField->SetFormat($this->GetFormat()); + } + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + /** + * The actual formatting of the field: either text (=plain text) or html (= text with HTML markup) + * @return string + */ + public function GetFormat() + { + return $this->GetOptional('format', 'text'); + } + + /** + * Read the value from the row returned by the SQL query and transorms it to the appropriate + * internal format (either text or html) + * + * @see AttributeDBFieldVoid::FromSQLToValue() + * + * @param array $aCols + * @param string $sPrefix + * + * @return string + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + $value = $aCols[$sPrefix.'']; + if ($this->GetOptional('format', null) != null ) + { + // Read from the extra column only if the property 'format' is specified for the attribute + $sFormat = $aCols[$sPrefix.'_format']; + } + else + { + $sFormat = $this->GetFormat(); + } + + switch($sFormat) + { + case 'text': + if ($this->GetFormat() == 'html') + { + $value = utils::TextToHtml($value); + } + break; + + case 'html': + if ($this->GetFormat() == 'text') + { + $value = utils::HtmlToText($value); + } + else + { + $value = InlineImage::FixUrls((string)$value); + } + break; + + default: + // unknown format ?? + } + return $value; + } + + public function GetSQLValues($value) + { + $aValues = array(); + $aValues[$this->Get("sql")] = $this->ScalarToSQL($value); + if ($this->GetOptional('format', null) != null ) + { + // Add the extra column only if the property 'format' is specified for the attribute + $aValues[$this->Get("sql").'_format'] = $this->GetFormat(); + } + return $aValues; + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + switch($this->GetFormat()) + { + case 'html': + if ($bConvertToPlainText) + { + $sValue = utils::HtmlToText((string)$sValue); + } + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; + break; + + case 'text': + default: + return parent::GetAsCSV($sValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); + } + } +} + +/** + * Map a log to an attribute + * + * @package iTopORM + */ +class AttributeLongText extends AttributeText +{ + protected function GetSQLCol($bFullSpec = false) + { + return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition(); + } + + public function GetMaxSize() + { + // Is there a way to know the current limitation for mysql? + // See mysql_field_len() + return 65535*1024; // Limited... still 64 Mb! + } +} + +/** + * An attibute that stores a case log (i.e journal) + * + * @package iTopORM + */ +class AttributeCaseLog extends AttributeLongText +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + + public function GetNullValue() + { + return ''; + } + + public function IsNull($proposedValue) + { + if (!($proposedValue instanceof ormCaseLog)) + { + return ($proposedValue == ''); + } + return ($proposedValue->GetText() == ''); + } + + public function ScalarToSQL($value) + { + if (!is_string($value) && !is_null($value)) + { + throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetCode(), 'attribute' => $this->GetHostClass())); + } + return $value; + } + public function GetEditClass() {return "CaseLog";} + + public function GetEditValue($sValue, $oHostObj = null) + { + if (!($sValue instanceOf ormCaseLog)) + { + return ''; + } + return $sValue->GetModifiedEntry(); + } + + /** + * For fields containing a potential markup, return the value without this markup + * + * @param mixed $value + * @param \DBObject $oHostObj + * + * @return string + */ + public function GetAsPlainText($value, $oHostObj = null) + { + if ($value instanceOf ormCaseLog) + { + /** ormCaseLog $value */ + return $value->GetAsPlainText(); + } + else + { + return (string) $value; + } + } + + public function GetDefaultValue(DBObject $oHostObject = null) {return new ormCaseLog();} + public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());} + + + /** + * Facilitate things: allow the user to Set the value from a string + * + * @param $proposedValue + * @param \DBObject $oHostObj + * + * @return mixed|null|\ormCaseLog|string + * @throws \Exception + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + if ($proposedValue instanceof ormCaseLog) + { + // Passthrough + $ret = clone $proposedValue; + } + else + { + // Append the new value if an instance of the object is supplied + // + $oPreviousLog = null; + if ($oHostObj != null) + { + $oPreviousLog = $oHostObj->Get($this->GetCode()); + if (!is_object($oPreviousLog)) + { + $oPreviousLog = $oHostObj->GetOriginal($this->GetCode());; + } + + } + if (is_object($oPreviousLog)) + { + $oCaseLog = clone($oPreviousLog); + } + else + { + $oCaseLog = new ormCaseLog(); + } + + if ($proposedValue instanceof stdClass) + { + $oCaseLog->AddLogEntryFromJSON($proposedValue); + } + else + { + if (strlen($proposedValue) > 0) + { + $oCaseLog->AddLogEntry($proposedValue); + } + } + $ret = $oCaseLog; + } + return $ret; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->Get('sql'); + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix; + $aColumns['_index'] = $sPrefix.'_index'; + return $aColumns; + } + + /** + * @param array $aCols + * @param string $sPrefix + * + * @return \ormCaseLog + * @throws \MissingColumnException + */ + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!array_key_exists($sPrefix, $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $sLog = $aCols[$sPrefix]; + + if (isset($aCols[$sPrefix.'_index'])) + { + $sIndex = $aCols[$sPrefix.'_index']; + } + else + { + // For backward compatibility, allow the current state to be: 1 log, no index + $sIndex = ''; + } + + if (strlen($sIndex) > 0) + { + $aIndex = unserialize($sIndex); + $value = new ormCaseLog($sLog, $aIndex); + } + else + { + $value = new ormCaseLog($sLog); + } + return $value; + } + + public function GetSQLValues($value) + { + if (!($value instanceOf ormCaseLog)) + { + $value = new ormCaseLog(''); + } + $aValues = array(); + $aValues[$this->GetCode()] = $value->GetText(); + $aValues[$this->GetCode().'_index'] = serialize($value->GetIndex()); + + return $aValues; + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->GetCode()] = 'LONGTEXT' // 2^32 (4 Gb) + .CMDBSource::GetSqlStringColumnDefinition(); + $aColumns[$this->GetCode().'_index'] = 'BLOB'; + return $aColumns; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if ($value instanceOf ormCaseLog) + { + $sContent = $value->GetAsHTML(null, false, array(__class__, 'RenderWikiHtml')); + } + else + { + $sContent = ''; + } + $aStyles = array(); + if ($this->GetWidth() != '') + { + $aStyles[] = 'width:'.$this->GetWidth(); + } + if ($this->GetHeight() != '') + { + $aStyles[] = 'height:'.$this->GetHeight(); + } + $sStyle = ''; + if (count($aStyles) > 0) + { + $sStyle = 'style="'.implode(';', $aStyles).'"'; + } + return "
".$sContent.'
'; } + + + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if ($value instanceOf ormCaseLog) + { + return parent::GetAsCSV($value->GetText($bConvertToPlainText), $sSeparator, $sTextQualifier, $oHostObject, $bLocalize, $bConvertToPlainText); + } + else + { + return ''; + } + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if ($value instanceOf ormCaseLog) + { + return parent::GetAsXML($value->GetText(), $oHostObject, $bLocalize); + } + else + { + return ''; + } + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text representation of all the log entries', + 'head' => 'Plain text representation of the latest entry', + 'head_html' => 'HTML representation of the latest entry', + 'html' => 'HTML representation of all the log entries', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param $value mixed The current value of the field + * @param $sVerb string The verb specifying the representation of the value + * @param $oHostObject DBObject The object + * @param $bLocalize bool Whether or not to localize the value + * + * @return mixed + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + switch($sVerb) + { + case '': + return $value->GetText(true); + + case 'head': + return $value->GetLatestEntry('text'); + + case 'head_html': + return $value->GetLatestEntry('html'); + + case 'html': + return $value->GetAsEmailHtml(); + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + */ + public function GetForJSON($value) + { + return $value->GetForJSON(); + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + */ + public function FromJSONToValue($json) + { + if (is_string($json)) + { + // Will be correctly handled in MakeRealValue + $ret = $json; + } + else + { + if (isset($json->add_item)) + { + // Will be correctly handled in MakeRealValue + $ret = $json->add_item; + if (!isset($ret->message)) + { + throw new Exception("Missing mandatory entry: 'message'"); + } + } + else + { + $ret = ormCaseLog::FromJSON($json); + } + } + return $ret; + } + + public function Fingerprint($value) + { + $sFingerprint = ''; + if ($value instanceOf ormCaseLog) + { + $sFingerprint = $value->GetText(); + } + return $sFingerprint; + } + + /** + * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * @return string + */ + public function GetFormat() + { + return $this->GetOptional('format', 'html'); // default format for case logs is now HTML + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\CaseLogField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + // First we call the parent so the field is build + $oFormField = parent::MakeFormField($oObject, $oFormField); + // Then only we set the value + $oFormField->SetCurrentValue($this->GetEditValue($oObject->Get($this->GetCode()))); + // And we set the entries + $oFormField->SetEntries($oObject->Get($this->GetCode())->GetAsArray()); + + return $oFormField; + } +} + +/** + * Map a text column (size > ?), containing HTML code, to an attribute + * + * @package iTopORM + */ +class AttributeHTML extends AttributeLongText +{ + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->Get('sql')] = $this->GetSQLCol(); + if ($this->GetOptional('format', null) != null ) + { + // Add the extra column only if the property 'format' is specified for the attribute + $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; + if ($bFullSpec) + { + $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records + } + } + return $aColumns; + } + + /** + * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * @return string + */ + public function GetFormat() + { + return $this->GetOptional('format', 'html'); // Defaults to HTML + } +} + +/** + * Specialization of a string: email + * + * @package iTopORM + */ +class AttributeEmailAddress extends AttributeString +{ + public function GetValidationPattern() + { + return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('email_validation_pattern').'$'); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\EmailField'; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) return ''; + + $sUrlDecorationClass = utils::GetConfig()->Get('email_decoration_class'); + + return ''.parent::GetAsHTML($sValue).''; + } +} + +/** + * Specialization of a string: IP address + * + * @package iTopORM + */ +class AttributeIPAddress extends AttributeString +{ + public function GetValidationPattern() + { + $sNum = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])'; + return "^($sNum\\.$sNum\\.$sNum\\.$sNum)$"; + } + + public function GetOrderBySQLExpressions($sClassAlias) + { + // Note: This is the responsibility of this function to place backticks around column aliases + return array('INET_ATON(`'.$sClassAlias.$this->GetCode().'`)'); + } +} + +/** + * Specialization of a string: phone number + * + * @package iTopORM + */ +class AttributePhoneNumber extends AttributeString +{ + public function GetValidationPattern() + { + return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('phone_number_validation_pattern').'$'); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\PhoneField'; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if (empty($sValue)) return ''; + + $sUrlDecorationClass = utils::GetConfig()->Get('phone_number_decoration_class'); + $sUrlPattern = utils::GetConfig()->Get('phone_number_url_pattern'); + $sUrl = sprintf($sUrlPattern, $sValue); + + return ''.parent::GetAsHTML($sValue).''; + } +} + +/** + * Specialization of a string: OQL expression + * + * @package iTopORM + */ +class AttributeOQL extends AttributeText +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + + public function GetEditClass() {return "OQLExpression";} +} + +/** + * Specialization of a string: template (contains iTop placeholders like $current_contact_id$ or $this->name$) + * + * @package iTopORM + */ +class AttributeTemplateString extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; +} + +/** + * Specialization of a text: template (contains iTop placeholders like $current_contact_id$ or $this->name$) + * + * @package iTopORM + */ +class AttributeTemplateText extends AttributeText +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; +} + +/** + * Specialization of a HTML: template (contains iTop placeholders like $current_contact_id$ or $this->name$) + * + * @package iTopORM + */ +class AttributeTemplateHTML extends AttributeText +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->Get('sql')] = $this->GetSQLCol(); + if ($this->GetOptional('format', null) != null ) + { + // Add the extra column only if the property 'format' is specified for the attribute + $aColumns[$this->Get('sql').'_format'] = "ENUM('text','html')"; + if ($bFullSpec) + { + $aColumns[$this->Get('sql').'_format'].= " DEFAULT 'html'"; // default 'html' is for migrating old records + } + } + return $aColumns; + } + + /** + * The actual formatting of the text: either text (=plain text) or html (= text with HTML markup) + * @return string + */ + public function GetFormat() + { + return $this->GetOptional('format', 'html'); // Defaults to HTML + } +} + + +/** + * Map a enum column to an attribute + * + * @package iTopORM + */ +class AttributeEnum extends AttributeString +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_ENUM; + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "String";} + protected function GetSQLCol($bFullSpec = false) + { + $oValDef = $this->GetValuesDef(); + if ($oValDef) + { + $aValues = CMDBSource::Quote(array_keys($oValDef->GetValues(array(), "")), true); + } + else + { + $aValues = array(); + } + if (count($aValues) > 0) + { + // The syntax used here do matters + // In particular, I had to remove unnecessary spaces to + // make sure that this string will match the field type returned by the DB + // (used to perform a comparison between the current DB format and the data model) + return "ENUM(".implode(",", $aValues).")" + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + else + { + return "VARCHAR(255)" + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? " DEFAULT ''" : ""); // ENUM() is not an allowed syntax! + } + } + + protected function GetSQLColSpec() + { + $default = $this->ScalarToSQL($this->GetDefaultValue()); + if (is_null($default)) + { + $sRet = ''; + } + else + { + // ENUMs values are strings so the default value must be a string as well, + // otherwise MySQL interprets the number as the zero-based index of the value in the list (i.e. the nth value in the list) + $sRet = " DEFAULT ".CMDBSource::Quote($default); + } + return $sRet; + } + + public function ScalarToSQL($value) + { + // Note: for strings, the null value is an empty string and it is recorded as such in the DB + // but that wasn't working for enums, because '' is NOT one of the allowed values + // that's why a null value must be forced to a real null + $value = parent::ScalarToSQL($value); + if ($this->IsNull($value)) + { + return null; + } + else + { + return $value; + } + } + + public function RequiresIndex() + { + return false; + } + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return parent::GetBasicFilterSQLExpr($sOpCode, $value); + } + + public function GetValueLabel($sValue) + { + if (is_null($sValue)) + { + // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label + $sLabel = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue, Dict::S('Enum:Undefined')); + } + else + { + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, null, true /*user lang*/); + if (is_null($sLabel)) + { + $sDefault = str_replace('_', ' ', $sValue); + // Browse the hierarchy again, accepting default (english) translations + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/Value:'.$sValue, $sDefault, false); + } + } + return $sLabel; + } + + public function GetValueDescription($sValue) + { + if (is_null($sValue)) + { + // Unless a specific label is defined for the null value of this enum, use a generic "undefined" label + $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', Dict::S('Enum:Undefined')); + } + else + { + $sDescription = Dict::S('Class:'.$this->GetHostClass().'/Attribute:'.$this->GetCode().'/Value:'.$sValue.'+', '', true /* user language only */); + if (strlen($sDescription) == 0) + { + $sParentClass = MetaModel::GetParentClass($this->m_sHostClass); + if ($sParentClass) + { + if (MetaModel::IsValidAttCode($sParentClass, $this->m_sCode)) + { + $oAttDef = MetaModel::GetAttributeDef($sParentClass, $this->m_sCode); + $sDescription = $oAttDef->GetValueDescription($sValue); + } + } + } + } + return $sDescription; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + if ($bLocalize) + { + $sLabel = $this->GetValueLabel($sValue); + $sDescription = $this->GetValueDescription($sValue); + // later, we could imagine a detailed description in the title + $sRes = "".parent::GetAsHtml($sLabel).""; + } + else + { + $sRes = parent::GetAsHtml($sValue, $oHostObject, $bLocalize); + } + return $sRes; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (is_null($value)) + { + $sFinalValue = ''; + } + elseif ($bLocalize) + { + $sFinalValue = $this->GetValueLabel($value); + } + else + { + $sFinalValue = $value; + } + $sRes = parent::GetAsXML($sFinalValue, $oHostObject, $bLocalize); + return $sRes; + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if (is_null($sValue)) + { + $sFinalValue = ''; + } + elseif ($bLocalize) + { + $sFinalValue = $this->GetValueLabel($sValue); + } + else + { + $sFinalValue = $sValue; + } + $sRes = parent::GetAsCSV($sFinalValue, $sSeparator, $sTextQualifier, $oHostObject, $bLocalize); + return $sRes; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\SelectField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + $oFormField->SetChoices($this->GetAllowedValues($oObject->ToArgsForQuery())); + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + public function GetEditValue($sValue, $oHostObj = null) + { + if (is_null($sValue)) + { + return ''; + } + else + { + return $this->GetValueLabel($sValue); + } + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + */ + public function GetForJSON($value) + { + return $value; + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $aRawValues = parent::GetAllowedValues($aArgs, $sContains); + if (is_null($aRawValues)) return null; + $aLocalizedValues = array(); + foreach ($aRawValues as $sKey => $sValue) + { + $aLocalizedValues[$sKey] = $this->GetValueLabel($sKey); + } + return $aLocalizedValues; + } + + public function GetMaxSize() + { + return null; + } + + /** + * An enum can be localized + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + if ($bLocalizedValue) + { + // Lookup for the value matching the input + // + $sFoundValue = null; + $aRawValues = parent::GetAllowedValues(); + if (!is_null($aRawValues)) + { + foreach ($aRawValues as $sKey => $sValue) + { + $sRefValue = $this->GetValueLabel($sKey); + if ($sProposedValue == $sRefValue) + { + $sFoundValue = $sKey; + break; + } + } + } + if (is_null($sFoundValue)) + { + return null; + } + return $this->MakeRealValue($sFoundValue, null); + } + else + { + return parent::MakeValueFromString($sProposedValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier); + } + } + + /** + * Processes the input value to align it with the values supported + * by this type of attribute. In this case: turns empty strings into nulls + * @param mixed $proposedValue The value to be set for the attribute + * @return mixed The actual value that will be set + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + if ($proposedValue == '') return null; + return parent::MakeRealValue($proposedValue, $oHostObj); + } + + public function GetOrderByHint() + { + $aValues = $this->GetAllowedValues(); + + return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues)); + } +} + +/** + * A meta enum is an aggregation of enum from subclasses into an enum of a base class + * It has been designed is to cope with the fact that statuses must be defined in leaf classes, while it makes sense to + * have a superstatus available on the root classe(s) + * + * @package iTopORM + */ +class AttributeMetaEnum extends AttributeEnum +{ + static public function ListExpectedParams() + { + return array('allowed_values', 'sql', 'default_value', 'mapping'); + } + + public function IsNullAllowed() + { + return false; // Well... this actually depends on the mapping + } + + public function IsWritable() + { + return false; + } + + public function RequiresIndex() + { + return true; + } + + public function GetPrerequisiteAttributes($sClass = null) + { + if (is_null($sClass)) + { + $sClass = $this->GetHostClass(); + } + $aMappingData = $this->GetMapRule($sClass); + if ($aMappingData == null) + { + $aRet = array(); + } + else + { + $aRet = array($aMappingData['attcode']); + } + return $aRet; + } + + /** + * Overload the standard so as to leave the data unsorted + * + * @param array $aArgs + * @param string $sContains + * @return array|null + */ + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $oValSetDef = $this->GetValuesDef(); + if (!$oValSetDef) return null; + $aRawValues = $oValSetDef->GetValueList(); + + if (is_null($aRawValues)) return null; + $aLocalizedValues = array(); + foreach ($aRawValues as $sKey => $sValue) + { + $aLocalizedValues[$sKey] = Str::pure2html($this->GetValueLabel($sKey)); + } + return $aLocalizedValues; + } + + /** + * Returns the meta value for the given object. + * See also MetaModel::RebuildMetaEnums() that must be maintained when MapValue changes + * + * @param $oObject + * @return mixed + * @throws Exception + */ + public function MapValue($oObject) + { + $aMappingData = $this->GetMapRule(get_class($oObject)); + if ($aMappingData == null) + { + $sRet = $this->GetDefaultValue(); + } + else + { + $sAttCode = $aMappingData['attcode']; + $value = $oObject->Get($sAttCode); + if (array_key_exists($value, $aMappingData['values'])) + { + $sRet = $aMappingData['values'][$value]; + } + elseif ($this->GetDefaultValue() != '') + { + $sRet = $this->GetDefaultValue(); + } + else + { + throw new Exception('AttributeMetaEnum::MapValue(): mapping not found for value "'.$value.'" in '.get_class($oObject).', on attribute '.MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()).'::'.$this->GetCode()); + } + } + return $sRet; + } + + public function GetMapRule($sClass) + { + $aMappings = $this->Get('mapping'); + if (array_key_exists($sClass, $aMappings)) + { + $aMappingData = $aMappings[$sClass]; + } + else + { + $sParent = MetaModel::GetParentClass($sClass); + if (is_null($sParent)) + { + $aMappingData = null; + } + else + { + $aMappingData = $this->GetMapRule($sParent); + } + } + + return $aMappingData; + } +} +/** + * Map a date+time column to an attribute + * + * @package iTopORM + */ +class AttributeDateTime extends AttributeDBField +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE_TIME; + + static $oFormat = null; + + /** + * + * @return DateTimeFormat + */ + static public function GetFormat() + { + if (self::$oFormat == null) + { + static::LoadFormatFromConfig(); + } + return self::$oFormat; + } + + /** + * Load the 3 settings: date format, time format and data_time format from the configuration + */ + protected static function LoadFormatFromConfig() + { + $aFormats = MetaModel::GetConfig()->Get('date_and_time_format'); + $sLang = Dict::GetUserLanguage(); + $sDateFormat = isset($aFormats[$sLang]['date']) ? $aFormats[$sLang]['date'] : (isset($aFormats['default']['date']) ? $aFormats['default']['date'] : 'Y-m-d'); + $sTimeFormat = isset($aFormats[$sLang]['time']) ? $aFormats[$sLang]['time'] : (isset($aFormats['default']['time']) ? $aFormats['default']['time'] : 'H:i:s'); + $sDateAndTimeFormat = isset($aFormats[$sLang]['date_time']) ? $aFormats[$sLang]['date_time'] : (isset($aFormats['default']['date_time']) ? $aFormats['default']['date_time'] : '$date $time'); + + $sFullFormat = str_replace(array('$date', '$time'), array($sDateFormat, $sTimeFormat), $sDateAndTimeFormat); + + self::SetFormat(new DateTimeFormat($sFullFormat)); + AttributeDate::SetFormat(new DateTimeFormat($sDateFormat)); + } + + /** + * Returns the format string used for the date & time stored in memory + * @return string + */ + static public function GetInternalFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Returns the format string used for the date & time written to MySQL + * @return string + */ + static public function GetSQLFormat() + { + return 'Y-m-d H:i:s'; + } + + static public function SetFormat(DateTimeFormat $oDateTimeFormat) + { + self::$oFormat = $oDateTimeFormat; + } + + static public function GetSQLTimeFormat() + { + return 'H:i:s'; + } + + /** + * Parses a search string coming from user input + * @param string $sSearchString + * @return string + */ + public function ParseSearchString($sSearchString) + { + try + { + $oDateTime = $this->GetFormat()->Parse($sSearchString); + $sSearchString = $oDateTime->format($this->GetInternalFormat()); + } + catch(Exception $e) + { + $sFormatString = '!'.(string)AttributeDate::GetFormat(); // BEWARE: ! is needed to set non-parsed fields to zero !!! + $oDateTime = DateTime::createFromFormat($sFormatString, $sSearchString); + if ($oDateTime !== false) + { + $sSearchString = $oDateTime->format($this->GetInternalFormat()); + } + } + return $sSearchString; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\DateTimeField'; + } + + /** + * Override to specify Field class + * + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + $oFormField->SetPHPDateTimeFormat((string) $this->GetFormat()); + $oFormField->SetJSDateTimeFormat($this->GetFormat()->ToMomentJS()); + + $oFormField = parent::MakeFormField($oObject, $oFormField); + + // After call to the parent as it sets the current value + $oFormField->SetCurrentValue($this->GetFormat()->Format($oObject->Get($this->GetCode()))); + + return $oFormField; + } + + /** + * @inheritdoc + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Formatted representation', + 'raw' => 'Not formatted representation', + ); + } + + /** + * @inheritdoc + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + switch ($sVerb) + { + case '': + case 'text': + return static::GetFormat()->format($value); + break; + case 'html': + // Note: Not passing formatted value as the method will format it. + return $this->GetAsHTML($value); + break; + case 'raw': + return $value; + break; + default: + return parent::GetForTemplate($value, $sVerb, $oHostObject, $bLocalize); + break; + } + } + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "DateTime";} + + + public function GetEditValue($sValue, $oHostObj = null) + { + return (string)static::GetFormat()->format($sValue); + } + public function GetValueLabel($sValue, $oHostObj = null) + { + return (string)static::GetFormat()->format($sValue); + } + + protected function GetSQLCol($bFullSpec = false) {return "DATETIME";} + + public function GetImportColumns() + { + // Allow an empty string to be a valid value (synonym for "reset") + $aColumns = array(); + $aColumns[$this->GetCode()] = 'VARCHAR(19)'; + return $aColumns; + } + + public static function GetAsUnixSeconds($value) + { + $oDeadlineDateTime = new DateTime($value); + $iUnixSeconds = $oDeadlineDateTime->format('U'); + return $iUnixSeconds; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + // null value will be replaced by the current date, if not already set, in DoComputeValues + return $this->GetNullValue(); + } + + public function GetValidationPattern() + { + return static::GetFormat()->ToRegExpr(); + } + + public function GetBasicFilterOperators() + { + return array( + "="=>"equals", + "!="=>"differs from", + "<"=>"before", + "<="=>"before", + ">"=>"after (strictly)", + ">="=>"after", + "SameDay"=>"same day (strip time)", + "SameMonth"=>"same year/month", + "SameYear"=>"same year", + "Today"=>"today", + ">|"=>"after today + N days", + "<|"=>"before today + N days", + "=|"=>"equals today + N days", + ); + } + public function GetBasicFilterLooseOperator() + { + // Unless we implement a "same xxx, depending on given precision" ! + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + + switch ($sOpCode) + { + case '=': + case '!=': + case '<': + case '<=': + case '>': + case '>=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'SameDay': + return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)"; + case 'SameMonth': + return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')"; + case 'SameYear': + return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)"; + case 'Today': + return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()"; + case '>|': + return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '<|': + return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + case '=|': + return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)"; + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) + { + return null; + } + if (is_string($proposedValue) && ($proposedValue == "") && $this->IsNullAllowed()) + { + return null; + } + if (!is_numeric($proposedValue)) + { + // Check the format + try + { + $oFormat = new DateTimeFormat($this->GetInternalFormat()); + $oFormat->Parse($proposedValue); + } + catch (Exception $e) + { + throw new Exception('Wrong format for date attribute '.$this->GetCode().', expecting "'.$this->GetInternalFormat().'" and got "'.$proposedValue.'"'); + } + + return $proposedValue; + } + + return date(static::GetInternalFormat(), $proposedValue); + } + + public function ScalarToSQL($value) + { + if (empty($value)) + { + return null; + } + return $value; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + return Str::pure2html(static::GetFormat()->format($value)); + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + return Str::pure2xml($value); + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if (empty($sValue) || ($sValue === '0000-00-00 00:00:00') || ($sValue === '0000-00-00')) + { + return ''; + } + else if ((string)static::GetFormat() !== static::GetInternalFormat()) + { + // Format conversion + $oDate = new DateTime($sValue); + if ($oDate !== false) + { + $sValue = static::GetFormat()->format($oDate); + } + } + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; + } + + /** + * Parses a string to find some smart search patterns and build the corresponding search/OQL condition + * Each derived class is reponsible for defining and processing their own smart patterns, the base class + * does nothing special, and just calls the default (loose) operator + * + * @param string $sSearchText The search string to analyze for smart patterns + * @param FieldExpression $oField The FieldExpression representing the atttribute code in this OQL query + * @param array $aParams Values of the query parameters + * @param bool $bParseSearchString + * + * @return Expression The search condition to be added (AND) to the current search + * @throws \CoreException + */ + public function GetSmartConditionExpression($sSearchText, FieldExpression $oField, &$aParams, $bParseSearchString = false) + { + // Possible smart patterns + $aPatterns = array( + 'between' => array('pattern' => '/^\[(.*),(.*)\]$/', 'operator' => 'n/a'), + 'greater than or equal' => array('pattern' => '/^>=(.*)$/', 'operator' => '>='), + 'greater than' => array('pattern' => '/^>(.*)$/', 'operator' => '>'), + 'less than or equal' => array('pattern' => '/^<=(.*)$/', 'operator' => '<='), + 'less than' => array('pattern' => '/^<(.*)$/', 'operator' => '<'), + ); + + $sPatternFound = ''; + $aMatches = array(); + foreach($aPatterns as $sPatName => $sPattern) + { + if (preg_match($sPattern['pattern'], $sSearchText, $aMatches)) + { + $sPatternFound = $sPatName; + break; + } + } + + switch($sPatternFound) + { + case 'between': + + $sParamName1 = $oField->GetParent().'_'.$oField->GetName().'_1'; + $oRightExpr = new VariableExpression($sParamName1); + if ($bParseSearchString) + { + $aParams[$sParamName1] = $this->ParseSearchString($aMatches[1]); + } + else + { + $aParams[$sParamName1] = $aMatches[1]; + } + $oCondition1 = new BinaryExpression($oField, '>=', $oRightExpr); + + $sParamName2 = $oField->GetParent().'_'.$oField->GetName().'_2'; + $oRightExpr = new VariableExpression($sParamName2); + if ($bParseSearchString) + { + $aParams[$sParamName2] = $this->ParseSearchString($aMatches[2]); + } + else + { + $aParams[$sParamName2] = $aMatches[2]; + } + $oCondition2 = new BinaryExpression($oField, '<=', $oRightExpr); + + $oNewCondition = new BinaryExpression($oCondition1, 'AND', $oCondition2); + break; + + case 'greater than': + case 'greater than or equal': + case 'less than': + case 'less than or equal': + $sSQLOperator = $aPatterns[$sPatternFound]['operator']; + $sParamName = $oField->GetParent().'_'.$oField->GetName(); + $oRightExpr = new VariableExpression($sParamName); + if ($bParseSearchString) + { + $aParams[$sParamName] = $this->ParseSearchString($aMatches[1]); + } + else + { + $aParams[$sParamName] = $aMatches[1]; + } + $oNewCondition = new BinaryExpression($oField, $sSQLOperator, $oRightExpr); + + break; + + default: + $oNewCondition = parent::GetSmartConditionExpression($sSearchText, $oField, $aParams); + + } + + return $oNewCondition; + } + + + public function GetHelpOnSmartSearch() + { + $sDict = parent::GetHelpOnSmartSearch(); + + $oFormat = static::GetFormat(); + $sExample = $oFormat->Format(new DateTime('2015-07-19 18:40:00')); + return vsprintf($sDict, array($oFormat->ToPlaceholder(), $sExample)); + } +} + +/** + * Store a duration as a number of seconds + * + * @package iTopORM + */ +class AttributeDuration extends AttributeInteger +{ + public function GetEditClass() {return "Duration";} + protected function GetSQLCol($bFullSpec = false) {return "INT(11) UNSIGNED";} + + public function GetNullValue() {return '0';} + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return null; + if (!is_numeric($proposedValue)) return null; + if ( ((int)$proposedValue) < 0) return null; + + return (int)$proposedValue; + } + + public function ScalarToSQL($value) + { + if (is_null($value)) + { + return null; + } + return $value; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + return Str::pure2html(self::FormatDuration($value)); + } + + public static function FormatDuration($duration) + { + $aDuration = self::SplitDuration($duration); + + if ($duration < 60) + { + // Less than 1 min + $sResult = Dict::Format('Core:Duration_Seconds', $aDuration['seconds']); + } + else if ($duration < 3600) + { + // less than 1 hour, display it in minutes/seconds + $sResult = Dict::Format('Core:Duration_Minutes_Seconds', $aDuration['minutes'], $aDuration['seconds']); + } + else if ($duration < 86400) + { + // Less than 1 day, display it in hours/minutes/seconds + $sResult = Dict::Format('Core:Duration_Hours_Minutes_Seconds', $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); + } + else + { + // more than 1 day, display it in days/hours/minutes/seconds + $sResult = Dict::Format('Core:Duration_Days_Hours_Minutes_Seconds', $aDuration['days'], $aDuration['hours'], $aDuration['minutes'], $aDuration['seconds']); + } + return $sResult; + } + + static function SplitDuration($duration) + { + $duration = (int) $duration; + $days = floor($duration / 86400); + $hours = floor(($duration - (86400*$days)) / 3600); + $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); + $seconds = ($duration % 60); // modulo + return array( 'days' => $days, 'hours' => $hours, 'minutes' => $minutes, 'seconds' => $seconds ); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\DurationField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); + + // Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition + $sAttCode = $this->GetCode(); + $oFormField->SetCurrentValue($oObject->Get($sAttCode)); + $oFormField->SetReadOnly(true); + + return $oFormField; + } + +} +/** + * Map a date+time column to an attribute + * + * @package iTopORM + */ +class AttributeDate extends AttributeDateTime +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_DATE; + + static $oDateFormat = null; + + static public function GetFormat() + { + if (self::$oDateFormat == null) + { + AttributeDateTime::LoadFormatFromConfig(); + } + return self::$oDateFormat; + } + + static public function SetFormat(DateTimeFormat $oDateFormat) + { + self::$oDateFormat = $oDateFormat; + } + + /** + * Returns the format string used for the date & time stored in memory + * @return string + */ + static public function GetInternalFormat() + { + return 'Y-m-d'; + } + + /** + * Returns the format string used for the date & time written to MySQL + * @return string + */ + static public function GetSQLFormat() + { + return 'Y-m-d'; + } + + static public function ListExpectedParams() + { + return parent::ListExpectedParams(); + //return array_merge(parent::ListExpectedParams(), array()); + } + + public function GetEditClass() {return "Date";} + protected function GetSQLCol($bFullSpec = false) {return "DATE";} + public function GetImportColumns() + { + // Allow an empty string to be a valid value (synonym for "reset") + $aColumns = array(); + $aColumns[$this->GetCode()] = 'VARCHAR(10)'; + return $aColumns; + } + + + /** + * Override to specify Field class + * + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behave more like a Prepare. + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + $oFormField = parent::MakeFormField($oObject, $oFormField); + $oFormField->SetDateOnly(true); + + return $oFormField; + } + +} + +/** + * A dead line stored as a date & time + * The only difference with the DateTime attribute is the display: + * relative to the current time + */ +class AttributeDeadline extends AttributeDateTime +{ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + $sResult = self::FormatDeadline($value); + return $sResult; + } + + public static function FormatDeadline($value) + { + $sResult = ''; + if ($value !== null) + { + $iValue = AttributeDateTime::GetAsUnixSeconds($value); + $sDate = AttributeDateTime::GetFormat()->Format($value); + $difference = $iValue - time(); + + if ($difference >= 0) + { + $sDifference = self::FormatDuration($difference); + } + else + { + $sDifference = Dict::Format('UI:DeadlineMissedBy_duration', self::FormatDuration(-$difference)); + } + $sFormat = MetaModel::GetConfig()->Get('deadline_format'); + $sResult = str_replace(array('$date$', '$difference$'), array($sDate, $sDifference), $sFormat); + } + + return $sResult; + } + + static function FormatDuration($duration) + { + $days = floor($duration / 86400); + $hours = floor(($duration - (86400*$days)) / 3600); + $minutes = floor(($duration - (86400*$days + 3600*$hours)) / 60); + + if ($duration < 60) + { + // Less than 1 min + $sResult =Dict::S('UI:Deadline_LessThan1Min'); + } + else if ($duration < 3600) + { + // less than 1 hour, display it in minutes + $sResult =Dict::Format('UI:Deadline_Minutes', $minutes); + } + else if ($duration < 86400) + { + // Less that 1 day, display it in hours/minutes + $sResult =Dict::Format('UI:Deadline_Hours_Minutes', $hours, $minutes); + } + else + { + // Less that 1 day, display it in hours/minutes + $sResult =Dict::Format('UI:Deadline_Days_Hours_Minutes', $days, $hours, $minutes); + } + return $sResult; + } +} + +/** + * Map a foreign key to an attribute + * AttributeExternalKey and AttributeExternalField may be an external key + * the difference is that AttributeExternalKey corresponds to a column into the defined table + * where an AttributeExternalField corresponds to a column into another table (class) + * + * @package iTopORM + */ +class AttributeExternalKey extends AttributeDBFieldVoid +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; + + + /** + * Return the search widget type corresponding to this attribute + * + * @return string + */ + public function GetSearchType() + { + try + { + $oRemoteAtt = $this->GetFinalAttDef(); + $sTargetClass = $oRemoteAtt->GetTargetClass(); + if (MetaModel::IsHierarchicalClass($sTargetClass)) + { + return self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY; + } + return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; + } + catch (CoreException $e) + { + } + + return self::SEARCH_WIDGET_TYPE_RAW; + } + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed", "on_target_delete")); + } + + public function GetEditClass() {return "ExtKey";} + protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");} + public function RequiresIndex() + { + return true; + } + + public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;} + public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");} + public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} + public function GetKeyAttCode() {return $this->GetCode();} + public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); } + + + public function GetDefaultValue(DBObject $oHostObject = null) {return 0;} + public function IsNullAllowed() + { + if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys')) + { + return true; + } + return $this->Get("is_null_allowed"); + } + + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return parent::GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return parent::GetBasicFilterSQLExpr($sOpCode, $value); + } + + // overloaded here so that an ext key always have the answer to + // "what are your possible values?" + public function GetValuesDef() + { + $oValSetDef = $this->Get("allowed_values"); + if (!$oValSetDef) + { + // Let's propose every existing value + $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); + } + return $oValSetDef; + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + //throw new Exception("GetAllowedValues on ext key has been deprecated"); + try + { + return parent::GetAllowedValues($aArgs, $sContains); + } + catch (Exception $e) + { + // Some required arguments could not be found, enlarge to any existing value + $oValSetDef = new ValueSetObjects('SELECT '.$this->GetTargetClass()); + return $oValSetDef->GetValues($aArgs, $sContains); + } + } + + public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + $oValSetDef = $this->GetValuesDef(); + $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); + return $oSet; + } + + public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + return DBObjectSearch::FromOQL($this->GetValuesDef()->GetFilterExpression()); + } + + public function GetDeletionPropagationOption() + { + return $this->Get("on_target_delete"); + } + + public function GetNullValue() + { + return 0; + } + + public function IsNull($proposedValue) + { + return ($proposedValue == 0); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return 0; + if ($proposedValue === '') return 0; + if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey(); + return (int)$proposedValue; + } + + public function GetMaximumComboLength() + { + return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length')); + } + + public function GetMinAutoCompleteChars() + { + return $this->GetOptional('min_autocomplete_chars', MetaModel::GetConfig()->Get('min_autocomplete_chars')); + } + + public function AllowTargetCreation() + { + return $this->GetOptional('allow_target_creation', MetaModel::GetConfig()->Get('allow_target_creation')); + } + + /** + * Find the corresponding "link" attribute on the target class, if any + * @return null | AttributeDefinition + * @throws \CoreException + */ + public function GetMirrorLinkAttribute() + { + $oRet = null; + $sRemoteClass = $this->GetTargetClass(); + foreach (MetaModel::ListAttributeDefs($sRemoteClass) as $sRemoteAttCode => $oRemoteAttDef) + { + if (!$oRemoteAttDef->IsLinkSet()) continue; + if (!is_subclass_of($this->GetHostClass(), $oRemoteAttDef->GetLinkedClass()) && $oRemoteAttDef->GetLinkedClass() != $this->GetHostClass()) continue; + if ($oRemoteAttDef->GetExtKeyToMe() != $this->GetCode()) continue; + $oRet = $oRemoteAttDef; + break; + } + return $oRet; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\SelectObjectField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + // Setting params + $oFormField->SetMaximumComboLength($this->GetMaximumComboLength()); + $oFormField->SetMinAutoCompleteChars($this->GetMinAutoCompleteChars()); + $oFormField->SetHierarchical(MetaModel::IsHierarchicalClass($this->GetTargetClass())); + // Setting choices regarding the field dependencies + $aFieldDependencies = $this->GetPrerequisiteAttributes(); + if (!empty($aFieldDependencies)) + { + $oTmpAttDef = $this; + $oTmpField = $oFormField; + $oFormField->SetOnFinalizeCallback(function() use ($oTmpField, $oTmpAttDef, $oObject) + { + /** @var $oTmpField \Combodo\iTop\Form\Field\Field */ + /** @var $oTmpAttDef \AttributeDefinition */ + /** @var $oObject \DBObject */ + + // We set search object only if it has not already been set (overrided) + if ($oTmpField->GetSearch() === null) + { + $oSearch = DBSearch::FromOQL($oTmpAttDef->GetValuesDef()->GetFilterExpression()); + $oSearch->SetInternalParams(array('this' => $oObject)); + $oTmpField->SetSearch($oSearch); + } + }); + } + else + { + $oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression()); + $oSearch->SetInternalParams(array('this' => $oObject)); + $oFormField->SetSearch($oSearch); + } + + // If ExtKey is mandatory, we add a validator to ensure that the value 0 is not selected + if ($oObject->GetAttributeFlags($this->GetCode()) & OPT_ATT_MANDATORY) + { + $oFormField->AddValidator(new \Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator()); + } + + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + +} + +/** + * Special kind of External Key to manage a hierarchy of objects + */ +class AttributeHierarchicalKey extends AttributeExternalKey +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_HIERARCHICAL_KEY; + + protected $m_sTargetClass; + + static public function ListExpectedParams() + { + $aParams = parent::ListExpectedParams(); + $idx = array_search('targetclass', $aParams); + unset($aParams[$idx]); + $idx = array_search('jointype', $aParams); + unset($aParams[$idx]); + return $aParams; // Later: mettre les bons parametres ici !! + } + + public function GetEditClass() {return "ExtKey";} + public function RequiresIndex() + { + return true; + } + + /* + * The target class is the class for which the attribute has been defined first + */ + public function SetHostClass($sHostClass) + { + if (!isset($this->m_sTargetClass)) + { + $this->m_sTargetClass = $sHostClass; + } + parent::SetHostClass($sHostClass); + } + + static public function IsHierarchicalKey() {return true;} + public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->m_sTargetClass;} + public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;} + public function GetKeyAttCode() {return $this->GetCode();} + + public function GetBasicFilterOperators() + { + return parent::GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + return parent::GetBasicFilterLooseOperator(); + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->GetCode()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); + $aColumns[$this->GetSQLLeft()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); + $aColumns[$this->GetSQLRight()] = 'INT(11)'.($bFullSpec ? ' DEFAULT 0' : ''); + return $aColumns; + } + public function GetSQLRight() + { + return $this->GetCode().'_right'; + } + public function GetSQLLeft() + { + return $this->GetCode().'_left'; + } + + public function GetSQLValues($value) + { + if (!is_array($value)) + { + $aValues[$this->GetCode()] = $value; + } + else + { + $aValues = array(); + $aValues[$this->GetCode()] = $value[$this->GetCode()]; + $aValues[$this->GetSQLRight()] = $value[$this->GetSQLRight()]; + $aValues[$this->GetSQLLeft()] = $value[$this->GetSQLLeft()]; + } + return $aValues; + } + + public function GetAllowedValues($aArgs = array(), $sContains = '') + { + $oFilter = $this->GetHierachicalFilter($aArgs, $sContains); + if ($oFilter) + { + $oValSetDef = $this->GetValuesDef(); + $oValSetDef->AddCondition($oFilter); + return $oValSetDef->GetValues($aArgs, $sContains); + } + else + { + return parent::GetAllowedValues($aArgs, $sContains); + } + } + + public function GetAllowedValuesAsObjectSet($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + $oValSetDef = $this->GetValuesDef(); + $oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue); + if ($oFilter) + { + $oValSetDef->AddCondition($oFilter); + } + $oSet = $oValSetDef->ToObjectSet($aArgs, $sContains, $iAdditionalValue); + return $oSet; + } + + public function GetAllowedValuesAsFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + $oFilter = $this->GetHierachicalFilter($aArgs, $sContains, $iAdditionalValue); + if ($oFilter) + { + return $oFilter; + } + return parent::GetAllowedValuesAsFilter($aArgs, $sContains, $iAdditionalValue); + } + + private function GetHierachicalFilter($aArgs = array(), $sContains = '', $iAdditionalValue = null) + { + if (array_key_exists('this', $aArgs)) + { + // Hierarchical keys have one more constraint: the "parent value" cannot be + // "under" themselves + $iRootId = $aArgs['this']->GetKey(); + if ($iRootId > 0) // ignore objects that do no exist in the database... + { + $sClass = $this->m_sTargetClass; + return DBObjectSearch::FromOQL("SELECT $sClass AS node JOIN $sClass AS root ON node.".$this->GetCode()." NOT BELOW root.id WHERE root.id = $iRootId"); + } + } + return false; + } + + /** + * Find the corresponding "link" attribute on the target class, if any + * @return null | AttributeDefinition + */ + public function GetMirrorLinkAttribute() + { + return null; + } +} + +/** + * An attribute which corresponds to an external key (direct or indirect) + * + * @package iTopORM + */ +class AttributeExternalField extends AttributeDefinition +{ + /** + * Return the search widget type corresponding to this attribute + * + * @return string + * @throws \CoreException + */ + public function GetSearchType() + { + // Not necessary the external key is already present + if ($this->IsFriendlyName()) + { + return self::SEARCH_WIDGET_TYPE_RAW; + } + + try + { + $oRemoteAtt = $this->GetFinalAttDef(); + switch (true) + { + case ($oRemoteAtt instanceof AttributeString): + return self::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD; + case ($oRemoteAtt instanceof AttributeExternalKey): + return self::SEARCH_WIDGET_TYPE_EXTERNAL_KEY; + } + } + catch (CoreException $e) + { + } + + return self::SEARCH_WIDGET_TYPE_RAW; + } + + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode")); + } + + public function GetEditClass() {return "ExtField";} + + /** + * @return \AttributeDefinition + * @throws \CoreException + */ + public function GetFinalAttDef() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetFinalAttDef(); + } + + protected function GetSQLCol($bFullSpec = false) + { + // throw new CoreException("external attribute: does it make any sense to request its type ?"); + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetSQLCol($bFullSpec); + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + return array('' => $this->GetCode()); // Warning: Use GetCode() since AttributeExternalField does not have any 'sql' property + } + else + { + return $sPrefix; + } + } + + public function GetLabel($sDefault = null) + { + if ($this->IsFriendlyName()) + { + $sKeyAttCode = $this->Get("extkey_attcode"); + $oExtKeyAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $sKeyAttCode); + $sLabel = $oExtKeyAttDef->GetLabel($this->m_sCode); + } + else + { + $sLabel = parent::GetLabel(''); + if (strlen($sLabel) == 0) + { + $oRemoteAtt = $this->GetExtAttDef(); + $sLabel = $oRemoteAtt->GetLabel($this->m_sCode); + } + } + 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(''); + if (strlen($sLabel) == 0) + { + $oRemoteAtt = $this->GetExtAttDef(); + $sLabel = $oRemoteAtt->GetDescription(''); + } + return $sLabel; + } + public function GetHelpOnEdition($sDefault = null) + { + $sLabel = parent::GetHelpOnEdition(''); + if (strlen($sLabel) == 0) + { + $oRemoteAtt = $this->GetExtAttDef(); + $sLabel = $oRemoteAtt->GetHelpOnEdition(''); + } + return $sLabel; + } + + public function IsExternalKey($iType = EXTKEY_RELATIVE) + { + switch($iType) + { + case EXTKEY_ABSOLUTE: + // see further + $oRemoteAtt = $this->GetExtAttDef(); + return $oRemoteAtt->IsExternalKey($iType); + + case EXTKEY_RELATIVE: + return false; + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); + } + } + + /** + * @return bool + * @throws \CoreException + */ + public function IsFriendlyName() + { + $oRemoteAtt = $this->GetExtAttDef(); + if ($oRemoteAtt instanceof AttributeExternalField) + { + $bRet = $oRemoteAtt->IsFriendlyName(); + } + elseif ($oRemoteAtt instanceof AttributeFriendlyName) + { + $bRet = true; + } + else + { + $bRet = false; + } + return $bRet; + } + + public function GetTargetClass($iType = EXTKEY_RELATIVE) + { + return $this->GetKeyAttDef($iType)->GetTargetClass(); + } + + static public function IsExternalField() {return true;} + + public function GetKeyAttCode() + { + return $this->Get("extkey_attcode"); + } + + public function GetExtAttCode() + { + return $this->Get("target_attcode"); + } + + /** + * @param int $iType + * + * @return \AttributeExternalKey + * @throws \CoreException + * @throws \Exception + */ + public function GetKeyAttDef($iType = EXTKEY_RELATIVE) + { + switch($iType) + { + case EXTKEY_ABSOLUTE: + // see further + /** @var \AttributeExternalKey $oRemoteAtt */ + $oRemoteAtt = $this->GetExtAttDef(); + if ($oRemoteAtt->IsExternalField()) + { + return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE); + } + else if ($oRemoteAtt->IsExternalKey()) + { + return $oRemoteAtt; + } + return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter ! + + case EXTKEY_RELATIVE: + /** @var \AttributeExternalKey $oAttDef */ + $oAttDef = MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode")); + return $oAttDef; + + default: + throw new CoreException("Unexpected value for argument iType: '$iType'"); + } + } + + public function GetPrerequisiteAttributes($sClass = null) + { + return array($this->Get("extkey_attcode")); + } + + + /** + * @return \AttributeExternalField + * @throws \CoreException + * @throws \Exception + */ + public function GetExtAttDef() + { + $oKeyAttDef = $this->GetKeyAttDef(); + /** @var \AttributeExternalField $oExtAttDef */ + $oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->GetTargetClass(), $this->Get("target_attcode")); + if (!is_object($oExtAttDef)) throw new CoreException("Invalid external field ".$this->GetCode()." in class ".$this->GetHostClass().". The class ".$oKeyAttDef->GetTargetClass()." has no attribute ".$this->Get("target_attcode")); + return $oExtAttDef; + } + + /** + * @return mixed + * @throws \CoreException + */ + public function GetSQLExpr() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetSQLExpr(); + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetDefaultValue(); + } + public function IsNullAllowed() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->IsNullAllowed(); + } + + static public function IsScalar() + { + return true; + } + + public function GetFilterDefinitions() + { + return array($this->GetCode() => new FilterFromAttribute($this)); + } + + public function GetBasicFilterOperators() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterOperators(); + } + public function GetBasicFilterLooseOperator() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterLooseOperator(); + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); + } + + public function GetNullValue() + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetNullValue(); + } + + public function IsNull($proposedValue) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->IsNull($proposedValue); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj); + } + + public function ScalarToSQL($value) + { + // This one could be used in case of filtering only + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->ScalarToSQL($value); + } + + + // Do not overload GetSQLExpression here because this is handled in the joins + //public function GetSQLExpressions($sPrefix = '') {return array();} + + // Here, we get the data... + public function FromSQLToValue($aCols, $sPrefix = '') + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->FromSQLToValue($aCols, $sPrefix); + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsHTML($value, null, $bLocalize); + } + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsXML($value, null, $bLocalize); + } + public function GetAsCSV($value, $sSeparator = ',', $sTestQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $oExtAttDef = $this->GetExtAttDef(); + return $oExtAttDef->GetAsCSV($value, $sSeparator, $sTestQualifier, null, $bLocalize, $bConvertToPlainText); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\LabelField'; + } + + /** + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\Field $oFormField + * + * @return null + * @throws \CoreException + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + // Retrieving AttDef from the remote attribute + $oRemoteAttDef = $this->GetExtAttDef(); + + if ($oFormField === null) + { + // ExternalField's FormField are actually based on the FormField from the target attribute. + // Except for the AttributeExternalKey because we have no OQL and stuff + if($oRemoteAttDef instanceof AttributeExternalKey) + { + $sFormFieldClass = static::GetFormFieldClass(); + } + else + { + $sFormFieldClass = $oRemoteAttDef::GetFormFieldClass(); + } + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); + + // Manually setting for remote ExternalKey, otherwise, the id would be displayed. + if($oRemoteAttDef instanceof AttributeExternalKey) + { + $oFormField->SetCurrentValue($oObject->Get($this->GetCode().'_friendlyname')); + } + + // Readonly field because we can't update external fields + $oFormField->SetReadOnly(true); + + return $oFormField; + } + + public function IsPartOfFingerprint() + { + return false; + } + +} + + +/** + * Multi value list of tags + * + * @see TagSetFieldData + * @since 2.6 N°931 tag fields + */ +class AttributeTagSet extends AttributeDBFieldVoid +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("is_null_allowed")); + } + public function GetDefaultValue(DBObject $oHostObject = null) {return null;} + public function IsNullAllowed() {return $this->Get("is_null_allowed");} + + public function GetEditClass() { + return "String"; + } + + public function GetEditValue($value, $oHostObj = null) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + return ''; + } + + protected function GetSQLCol($bFullSpec = false) + { + return 'VARCHAR(255)' + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function GetMaxSize() + { + return 255; + } + + public function RequiresIndex() {return true;} + + public function RequiresFullTextIndex() {return true;} + + public function Equals($val1, $val2) { + if (($val1 instanceof ormTagSet) && ($val2 instanceof ormTagSet)) + { + return $val1->Equals($val2); + } + return ($val1 == $val2); + } + + /** + * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! + * + * @param $proposedValue + * @param $oHostObj + * + * @return mixed + * @throws \Exception + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + if (is_string($proposedValue) && !empty($proposedValue)) + { + $aTagCodes = explode(' ', "$proposedValue"); + $oTagSet->SetValue($aTagCodes); + } + elseif ($proposedValue instanceof ormTagSet) + { + $oTagSet = $proposedValue; + } + return $oTagSet; + } + + /** + * Get the value from a given string (plain text, CSV import) + * + * @param string $sProposedValue + * @param bool $bLocalizedValue + * @param string $sSepItem + * @param string $sSepAttribute + * @param string $sSepValue + * @param string $sAttributeQualifier + * + * @return mixed null if no match could be found + * @throws \Exception + */ + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + // TODO $bLocalizedValue + return $this->MakeRealValue($sProposedValue, null); + } + + public function GetNullValue() { + return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + } + + public function IsNull($proposedValue) + { + /** @var \ormTagSet $proposedValue */ + return count($proposedValue->GetValue()) == 0; + } + + /** + * To be overloaded for localized enums + * + * @param $sValue + * + * @return string label corresponding to the given value (in plain text) + */ + public function GetValueLabel($sValue) + { + // TODO + return $sValue; + } + + /** + * @param $value + * + * @return string + * @throws \CoreWarning + */ + public function ScalarToSQL($value) + { + if (empty($value)) + { + return ''; + } + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + throw new CoreWarning('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string|null + * + * @throws \CoreException + * @throws \Exception + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if ($value instanceof ormTagSet) + { + if ($bLocalize) + { + $aValues = $value->GetTags(); + } + else + { + $aValues = $value->GetValue(); + } + if (empty($aValues)) + { + return ''; + } + return ''.implode('', $aValues).''; + } + if (is_string($value)) + { + try + { + $oValue = $this->MakeRealValue($value, $oHostObject); + return $this->GetAsHTML($oValue, $oHostObject, $bLocalize); + } + catch (Exception $e) + { + // unknown tags are present display the code instead + } + $aTagCodes = explode(' ', $value); + $aValues = array(); + $oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode()); + foreach ($aTagCodes as $sTagCode) + { + try + { + $oTagSet->AddTag($sTagCode); + } + catch (Exception $e) + { + $aValues[] = $sTagCode; + } + } + $sHTML = ''; + if (!empty($aValues)) + { + $sHTML .= ''.implode('', $aValues).''; + } + $aValues = $oTagSet->GetTags(); + if (!empty($aValues)) + { + $sHTML .= ''.implode('', $aValues).''; + } + + return $sHTML; + } + return parent::GetAsHTML($value, $oHostObject, $bLocalize); + + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * + */ + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + $sRes = "\n"; + if ($bLocalize) + { + $aValues = $value->GetTags(); + } + else + { + $aValues = $value->GetValue(); + } if (!empty($aValuess)) + { + $sRes .= ''.implode('', $aValues).''; + } + $sRes .= "\n"; + } + else + { + $sRes = ''; + } + return $sRes; + } + + /** + * @param $value + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return mixed|string + * @throws \CoreException + */ + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + if ($bLocalize) + { + $aValues = $value->GetTags(); + } + else + { + $aValues = $value->GetValue(); + } + $sRes = implode('|', $aValues); + } + else + { + $sRes = ''; + } + return $sRes; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + return array( + '' => 'Plain text (unlocalized) representation', + 'html' => 'HTML representation (unordered list)', + ); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param mixed $value The current value of the field + * @param string $sVerb The verb specifying the representation of the value + * @param DBObject $oHostObject The object + * @param bool $bLocalize Whether or not to localize the value + * + * @return string + * @throws \Exception + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + if (is_object($value) && ($value instanceof ormTagSet)) + { + if ($bLocalize) + { + $aValues = $value->GetTags(); + } + else + { + $aValues = $value->GetValue(); + } + + switch ($sVerb) + { + case '': + return implode(' ', $aValues); + + case 'html': + return '
  • '.implode("
  • ", $aValues).'
'; + + default: + throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + } + throw new CoreUnexpectedValue("Bad value '$value' for attribute ".$this->GetCode().' in class '.get_class($oHostObject)); + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param \ormTagSet $value + * + * @return array + * @throws \CoreException + */ + public function GetForJSON($value) + { + $aRet = array(); + if (is_object($value) && ($value instanceof ormTagSet)) + { + $aRet = $value->GetValue(); + } + return $aRet; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param $json + * + * @return \ormTagSet + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \Exception + */ + public function FromJSONToValue($json) + { + $oSet = new ormTagSet($this->GetHostClass(), $this->GetCode()); + $oSet->SetValue($json); + return $oSet; + } + + /** + * The part of the current attribute in the object's signature, for the supplied value + * @param mixed $value The value of this attribute for the object + * @return string The "signature" for this field/attribute + */ + public function Fingerprint($value) + { + if ($value instanceof ormTagSet) + { + $aValues = $value->GetValue(); + return implode(' ', $aValues); + } + return parent::Fingerprint($value); + } + +} + +/** + * Map a varchar column to an URL (formats the ouput in HMTL) + * + * @package iTopORM + */ +class AttributeURL extends AttributeString +{ + static public function ListExpectedParams() + { + //return parent::ListExpectedParams(); + return array_merge(parent::ListExpectedParams(), array("target")); + } + + protected function GetSQLCol($bFullSpec = false) + { + return "VARCHAR(2048)" + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + public function GetMaxSize() + { + return 2048; + } + + public function GetEditClass() {return "String";} + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + $sTarget = $this->Get("target"); + if (empty($sTarget)) $sTarget = "_blank"; + $sLabel = Str::pure2html($sValue); + if (strlen($sLabel) > 128) + { + // Truncate the length to 128 characters, by removing the middle + $sLabel = substr($sLabel, 0, 100).'.....'.substr($sLabel, -20); + } + return "$sLabel"; + } + + public function GetValidationPattern() + { + return $this->GetOptional('validation_pattern', '^'.utils::GetConfig()->Get('url_validation_pattern').'$'); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\UrlField'; + } + + /** + * @param \DBObject $oObject + * @param \Combodo\iTop\Form\Field\UrlField $oFormField + * + * @return null + * @throws \CoreException + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); + + $oFormField->SetTarget($this->Get('target')); + + return $oFormField; + } +} + +/** + * A blob is an ormDocument, it is stored as several columns in the database + * + * @package iTopORM + */ +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")); + } + + public function GetEditClass() {return "Document";} + + static public function IsBasedOnDBColumns() {return true;} + static public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue(DBObject $oHostObject = null) {return "";} + public function IsNullAllowed(DBObject $oHostObject = null) {return $this->GetOptional("is_null_allowed", false);} + + public function GetEditValue($sValue, $oHostObj = null) + { + return ''; + } + + /** + * Users can provide the document from an URL (including an URL on iTop itself) + * for CSV import. Administrators can even provide the path to a local file + * {@inheritDoc} + * @see AttributeDefinition::MakeRealValue() + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + if ($proposedValue === null) return null; + + if (is_object($proposedValue)) + { + $proposedValue = clone $proposedValue; + } + else + { + try + { + // Read the file from iTop, an URL (or the local file system - for admins only) + $proposedValue = Utils::FileGetContentsAndMIMEType($proposedValue); + } + catch(Exception $e) + { + IssueLog::Warning(get_class($this)."::MakeRealValue - ".$e->getMessage()); + // Not a real document !! store is as text !!! (This was the default behavior before) + $proposedValue = new ormDocument($e->getMessage()." \n".$proposedValue, 'text/plain'); + } + } + return $proposedValue; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix.'_mimetype'; + $aColumns['_data'] = $sPrefix.'_data'; + $aColumns['_filename'] = $sPrefix.'_filename'; + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!array_key_exists($sPrefix, $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; + + if (!array_key_exists($sPrefix.'_data', $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}"); + } + $data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null; + + if (!array_key_exists($sPrefix.'_filename', $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}"); + } + $sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : ''; + + $value = new ormDocument($data, $sMimeType, $sFileName); + return $value; + } + + public function GetSQLValues($value) + { + // #@# Optimization: do not load blobs anytime + // As per mySQL doc, selecting blob columns will prevent mySQL from + // using memory in case a temporary table has to be created + // (temporary tables created on disk) + // We will have to remove the blobs from the list of attributes when doing the select + // then the use of Get() should finalize the load + if ($value instanceOf ormDocument && !$value->IsEmpty()) + { + $aValues = array(); + $aValues[$this->GetCode().'_data'] = $value->GetData(); + $aValues[$this->GetCode().'_mimetype'] = $value->GetMimeType(); + $aValues[$this->GetCode().'_filename'] = $value->GetFileName(); + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_data'] = ''; + $aValues[$this->GetCode().'_mimetype'] = ''; + $aValues[$this->GetCode().'_filename'] = ''; + } + return $aValues; + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->GetCode().'_data'] = 'LONGBLOB'; // 2^32 (4 Gb) + $aColumns[$this->GetCode().'_mimetype'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); + $aColumns[$this->GetCode().'_filename'] = 'VARCHAR(255)'.CMDBSource::GetSqlStringColumnDefinition(); + return $aColumns; + } + + public function GetFilterDefinitions() + { + return array(); + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value)) + { + return $value->GetAsHTML(); + } + return ''; + } + + /** + * @param string $sValue + * @param string $sSeparator + * @param string $sTextQualifier + * @param \DBObject $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return string + */ + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sAttCode = $this->GetCode(); + if ($sValue instanceof ormDocument && !$sValue->IsEmpty()) + { + return $sValue->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $sAttCode); + } + return ''; // Not exportable in CSV ! + } + + /** + * @param $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return mixed|string + */ + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + $sRet = ''; + if (is_object($value)) + { + if (!$value->IsEmpty()) + { + $sRet = ''.$value->GetMimeType().''; + $sRet .= ''.$value->GetFileName().''; + $sRet .= ''.base64_encode($value->GetData()).''; + } + } + return $sRet; + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + */ + public function GetForJSON($value) + { + if ($value instanceOf ormDocument) + { + $aValues = array(); + $aValues['data'] = base64_encode($value->GetData()); + $aValues['mimetype'] = $value->GetMimeType(); + $aValues['filename'] = $value->GetFileName(); + } + else + { + $aValues = null; + } + return $aValues; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + */ + public function FromJSONToValue($json) + { + if (isset($json->data)) + { + $data = base64_decode($json->data); + $value = new ormDocument($data, $json->mimetype, $json->filename); + } + else + { + $value = null; + } + return $value; + } + + public function Fingerprint($value) + { + $sFingerprint = ''; + if ($value instanceOf ormDocument) + { + $sFingerprint = md5($value->GetData()); + } + return $sFingerprint; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\BlobField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + // Note: As of today we want this field to always be read-only + $oFormField->SetReadOnly(true); + + // Generating urls + $value = $oObject->Get($this->GetCode()); + $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); + $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); + + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + +} + +/** + * An image is a specific type of document, it is stored as several columns in the database + * + * @package iTopORM + */ +class AttributeImage extends AttributeBlob +{ + public function GetEditClass() {return "Image";} + + /** + * {@inheritDoc} + * @see AttributeBlob::MakeRealValue() + */ + public function MakeRealValue($proposedValue, $oHostObj) + { + $oDoc = parent::MakeRealValue($proposedValue, $oHostObj); + // The validation of the MIME Type is done by CheckFormat below + return $oDoc; + } + + /** + * Check that the supplied ormDocument actually contains an image + * {@inheritDoc} + * @see AttributeDefinition::CheckFormat() + */ + public function CheckFormat($value) + { + if ($value instanceof ormDocument && !$value->IsEmpty()) + { + return ($value->GetMainMimeType() == 'image'); + } + return true; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + $iMaxWidthPx = $this->Get('display_max_width').'px'; + $iMaxHeightPx = $this->Get('display_max_height').'px'; + $sUrl = $this->Get('default_image'); + $sRet = ($sUrl !== null) ? '' : ''; + if (is_object($value) && !$value->IsEmpty()) + { + if ($oHostObject->IsNew() || ($oHostObject->IsModified() && (array_key_exists($this->GetCode(), $oHostObject->ListChanges())))) + { + // If the object is modified (or not yet stored in the database) we must serve the content of the image directly inline + // otherwise (if we just give an URL) the browser will be given the wrong content... and may cache it + $sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData()); + } + else + { + $sUrl = $value->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode()); + } + $sRet = ''; + } + return '
'.$sRet.'
'; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\ImageField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + + parent::MakeFormField($oObject, $oFormField); + + // Generating urls + $value = $oObject->Get($this->GetCode()); + if (is_object($value) && !$value->IsEmpty()) + { + $oFormField->SetDownloadUrl($value->GetDownloadURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); + $oFormField->SetDisplayUrl($value->GetDisplayURL(get_class($oObject), $oObject->GetKey(), $this->GetCode())); + } + else + { + $oFormField->SetDownloadUrl($this->Get('default_image')); + $oFormField->SetDisplayUrl($this->Get('default_image')); + } + + return $oFormField; + } +} +/** + * A stop watch is an ormStopWatch object, it is stored as several columns in the database + * + * @package iTopORM + */ +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 + return array_merge(parent::ListExpectedParams(), array("states", "goal_computing", "working_time_computing", "thresholds")); + } + + public function GetEditClass() {return "StopWatch";} + + static public function IsBasedOnDBColumns() {return true;} + static public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue(DBObject $oHostObject = null) {return $this->NewStopWatch();} + + /** + * @param \ormStopWatch $value + * @param \DBObject $oHostObj + * + * @return string + */ + public function GetEditValue($value, $oHostObj = null) + { + return $value->GetTimeSpent(); + } + + public function GetStates() + { + return $this->Get('states'); + } + + public function AlwaysLoadInTables() + { + // Each and every stop watch is accessed for computing the highlight code (DBObject::GetHighlightCode()) + return true; + } + + /** + * Construct a brand new (but configured) stop watch + */ + public function NewStopWatch() + { + $oSW = new ormStopWatch(); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $oSW->DefineThreshold($iThreshold); + } + return $oSW; + } + + // Facilitate things: allow the user to Set the value from a string + public function MakeRealValue($proposedValue, $oHostObj) + { + if (!$proposedValue instanceof ormStopWatch) + { + return $this->NewStopWatch(); + } + return $proposedValue; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); // Warning: a stopwatch does not have any 'sql' property, so its SQL column is equal to its attribute code !! + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix.'_timespent'; + $aColumns['_started'] = $sPrefix.'_started'; + $aColumns['_laststart'] = $sPrefix.'_laststart'; + $aColumns['_stopped'] = $sPrefix.'_stopped'; + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = '_'.$iThreshold; + $aColumns[$sThPrefix.'_deadline'] = $sPrefix.$sThPrefix.'_deadline'; + $aColumns[$sThPrefix.'_passed'] = $sPrefix.$sThPrefix.'_passed'; + $aColumns[$sThPrefix.'_triggered'] = $sPrefix.$sThPrefix.'_triggered'; + $aColumns[$sThPrefix.'_overrun'] = $sPrefix.$sThPrefix.'_overrun'; + } + return $aColumns; + } + + public static function DateToSeconds($sDate) + { + if (is_null($sDate)) + { + return null; + } + $oDateTime = new DateTime($sDate); + $iSeconds = $oDateTime->format('U'); + return $iSeconds; + } + + public static function SecondsToDate($iSeconds) + { + if (is_null($iSeconds)) + { + return null; + } + return date("Y-m-d H:i:s", $iSeconds); + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + $aExpectedCols = array($sPrefix, $sPrefix.'_started', $sPrefix.'_laststart', $sPrefix.'_stopped'); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = '_'.$iThreshold; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_deadline'; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_passed'; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_triggered'; + $aExpectedCols[] = $sPrefix.$sThPrefix.'_overrun'; + } + foreach ($aExpectedCols as $sExpectedCol) + { + if (!array_key_exists($sExpectedCol, $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sExpectedCol' from {$sAvailable}"); + } + } + + $value = new ormStopWatch( + $aCols[$sPrefix], + self::DateToSeconds($aCols[$sPrefix.'_started']), + self::DateToSeconds($aCols[$sPrefix.'_laststart']), + self::DateToSeconds($aCols[$sPrefix.'_stopped']) + ); + + foreach ($this->ListThresholds() as $iThreshold => $aDefinition) + { + $sThPrefix = '_'.$iThreshold; + $value->DefineThreshold( + $iThreshold, + self::DateToSeconds($aCols[$sPrefix.$sThPrefix.'_deadline']), + (bool)($aCols[$sPrefix.$sThPrefix.'_passed'] == 1), + (bool)($aCols[$sPrefix.$sThPrefix.'_triggered'] == 1), + $aCols[$sPrefix.$sThPrefix.'_overrun'], + array_key_exists('highlight', $aDefinition) ? $aDefinition['highlight'] : null + ); + } + + return $value; + } + + public function GetSQLValues($value) + { + if ($value instanceOf ormStopWatch) + { + $aValues = array(); + $aValues[$this->GetCode().'_timespent'] = $value->GetTimeSpent(); + $aValues[$this->GetCode().'_started'] = self::SecondsToDate($value->GetStartDate()); + $aValues[$this->GetCode().'_laststart'] = self::SecondsToDate($value->GetLastStartDate()); + $aValues[$this->GetCode().'_stopped'] = self::SecondsToDate($value->GetStopDate()); + + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aValues[$sPrefix.'_deadline'] = self::SecondsToDate($value->GetThresholdDate($iThreshold)); + $aValues[$sPrefix.'_passed'] = $value->IsThresholdPassed($iThreshold) ? '1' : '0'; + $aValues[$sPrefix.'_triggered'] = $value->IsThresholdTriggered($iThreshold) ? '1' : '0'; + $aValues[$sPrefix.'_overrun'] = $value->GetOverrun($iThreshold); + } + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_timespent'] = ''; + $aValues[$this->GetCode().'_started'] = ''; + $aValues[$this->GetCode().'_laststart'] = ''; + $aValues[$this->GetCode().'_stopped'] = ''; + } + return $aValues; + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->GetCode().'_timespent'] = 'INT(11) UNSIGNED'; + $aColumns[$this->GetCode().'_started'] = 'DATETIME'; + $aColumns[$this->GetCode().'_laststart'] = 'DATETIME'; + $aColumns[$this->GetCode().'_stopped'] = 'DATETIME'; + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aColumns[$sPrefix.'_deadline'] = 'DATETIME'; + $aColumns[$sPrefix.'_passed'] = 'TINYINT(1) UNSIGNED'; + $aColumns[$sPrefix.'_triggered'] = 'TINYINT(1)'; + $aColumns[$sPrefix.'_overrun'] = 'INT(11) UNSIGNED'; + } + return $aColumns; + } + + public function GetFilterDefinitions() + { + $aRes = array( + $this->GetCode() => new FilterFromAttribute($this), + $this->GetCode().'_started' => new FilterFromAttribute($this, '_started'), + $this->GetCode().'_laststart' => new FilterFromAttribute($this, '_laststart'), + $this->GetCode().'_stopped' => new FilterFromAttribute($this, '_stopped') + ); + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sPrefix = $this->GetCode().'_'.$iThreshold; + $aRes[$sPrefix.'_deadline'] = new FilterFromAttribute($this, '_deadline'); + $aRes[$sPrefix.'_passed'] = new FilterFromAttribute($this, '_passed'); + $aRes[$sPrefix.'_triggered'] = new FilterFromAttribute($this, '_triggered'); + $aRes[$sPrefix.'_overrun'] = new FilterFromAttribute($this, '_overrun'); + } + return $aRes; + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + /** + * @param \ormStopWatch $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + */ + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value)) + { + return $value->GetAsHTML($this, $oHostObject); + } + return ''; + } + + /** + * @param ormStopWatch $value + * @param string $sSeparator + * @param string $sTextQualifier + * @param null $oHostObject + * @param bool $bLocalize + * @param bool $bConvertToPlainText + * + * @return string + */ + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + return $value->GetTimeSpent(); + } + + /** + * @param \ormStopWatch $value + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return mixed + */ + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + return $value->GetTimeSpent(); + } + + public function ListThresholds() + { + return $this->Get('thresholds'); + } + + public function Fingerprint($value) + { + $sFingerprint = ''; + if (is_object($value)) + { + $sFingerprint = $value->GetAsHTML($this); + } + return $sFingerprint; + } + + /** + * To expose internal values: Declare an attribute AttributeSubItem + * and implement the GetSubItemXXXX verbs + * + * @param string $sItemCode + * + * @return array + * @throws \CoreException + */ + public function GetSubItemSQLExpression($sItemCode) + { + $sPrefix = $this->GetCode(); + switch($sItemCode) + { + case 'timespent': + return array('' => $sPrefix.'_timespent'); + case 'started': + return array('' => $sPrefix.'_started'); + case 'laststart': + return array('' => $sPrefix.'_laststart'); + case 'stopped': + return array('' => $sPrefix.'_stopped'); + } + + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + return array('' => $sPrefix.'_'.$iThreshold.'_deadline'); + case 'passed': + return array('' => $sPrefix.'_'.$iThreshold.'_passed'); + case 'triggered': + return array('' => $sPrefix.'_'.$iThreshold.'_triggered'); + case 'overrun': + return array('' => $sPrefix.'_'.$iThreshold.'_overrun'); + } + } + } + throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); + } + + /** + * @param string $sItemCode + * @param \ormStopWatch $value + * @param \DBObject $oHostObject + * + * @return mixed + * @throws \CoreException + */ + public function GetSubItemValue($sItemCode, $value, $oHostObject = null) + { + $oStopWatch = $value; + switch($sItemCode) + { + case 'timespent': + return $oStopWatch->GetTimeSpent(); + case 'started': + return $oStopWatch->GetStartDate(); + case 'laststart': + return $oStopWatch->GetLastStartDate(); + case 'stopped': + return $oStopWatch->GetStopDate(); + } + + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + return $oStopWatch->GetThresholdDate($iThreshold); + case 'passed': + return $oStopWatch->IsThresholdPassed($iThreshold); + case 'triggered': + return $oStopWatch->IsThresholdTriggered($iThreshold); + case 'overrun': + return $oStopWatch->GetOverrun($iThreshold); + } + } + } + + throw new CoreException("Unknown item code '$sItemCode' for attribute ".$this->GetHostClass().'::'.$this->GetCode()); + } + + protected function GetBooleanLabel($bValue) + { + $sDictKey = $bValue ? 'yes' : 'no'; + return Dict::S('BooleanLabel:'.$sDictKey, 'def:'.$sDictKey); + } + + public function GetSubItemAsHTMLForHistory($sItemCode, $sValue) + { + $sHtml = null; + switch($sItemCode) + { + case 'timespent': + $sHtml = (int)$sValue ? Str::pure2html(AttributeDuration::FormatDuration($sValue)) : null; + break; + case 'started': + case 'laststart': + case 'stopped': + $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + $sHtml = (int)$sValue ? date((string)AttributeDateTime::GetFormat(), (int)$sValue) : null; + break; + case 'passed': + $sHtml = $this->GetBooleanLabel((int)$sValue); + break; + case 'triggered': + $sHtml = $this->GetBooleanLabel((int)$sValue); + break; + case 'overrun': + $sHtml = (int)$sValue > 0 ? Str::pure2html(AttributeDuration::FormatDuration((int)$sValue)) : ''; + } + } + } + } + return $sHtml; + } + + public function GetSubItemAsPlainText($sItemCode, $value) + { + $sRet = $value; + + switch ($sItemCode) + { + case 'timespent': + $sRet = AttributeDuration::FormatDuration($value); + break; + case 'started': + case 'laststart': + case 'stopped': + if (is_null($value)) + { + $sRet = ''; // Undefined + } + else + { + $oDateTime = new DateTime(); + $oDateTime->setTimestamp($value); + $oDateTimeFormat = AttributeDateTime::GetFormat(); + $sRet = $oDateTimeFormat->Format($oDateTime); + } + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold . '_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) + { + case 'deadline': + if ($value) + { + $sDate = date(AttributeDateTime::GetInternalFormat(), $value); + $sRet = AttributeDeadline::FormatDeadline($sDate); + } + else + { + $sRet = ''; + } + break; + case 'passed': + case 'triggered': + $sRet = $this->GetBooleanLabel($value); + break; + case 'overrun': + $sRet = AttributeDuration::FormatDuration($value); + break; + } + } + } + } + return $sRet; + } + + public function GetSubItemAsHTML($sItemCode, $value) + { + $sHtml = $value; + + switch ($sItemCode) + { + case 'timespent': + $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); + break; + case 'started': + case 'laststart': + case 'stopped': + if (is_null($value)) + { + $sHtml = ''; // Undefined + } + else + { + $oDateTime = new DateTime(); + $oDateTime->setTimestamp($value); + $oDateTimeFormat = AttributeDateTime::GetFormat(); + $sHtml = Str::pure2html($oDateTimeFormat->Format($oDateTime)); + } + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold . '_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch ($sThresholdCode) + { + case 'deadline': + if ($value) + { + $sDate = date(AttributeDateTime::GetInternalFormat(), $value); + $sHtml = Str::pure2html(AttributeDeadline::FormatDeadline($sDate)); + } + else + { + $sHtml = ''; + } + break; + case 'passed': + case 'triggered': + $sHtml = $this->GetBooleanLabel($value); + break; + case 'overrun': + $sHtml = Str::pure2html(AttributeDuration::FormatDuration($value)); + break; + } + } + } + } + return $sHtml; + } + + public function GetSubItemAsCSV($sItemCode, $value, $sSeparator = ',', $sTextQualifier = '"', $bConvertToPlainText = false) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$value); + $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; + + switch($sItemCode) + { + case 'timespent': + $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; + break; + case 'started': + case 'laststart': + case 'stopped': + if ($value !== null) + { + $oDateTime = new DateTime(); + $oDateTime->setTimestamp($value); + $oDateTimeFormat = AttributeDateTime::GetFormat(); + $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; + } + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + if ($value != '') + { + $oDateTime = new DateTime(); + $oDateTime->setTimestamp($value); + $oDateTimeFormat = AttributeDateTime::GetFormat(); + $sRet = $sTextQualifier . $oDateTimeFormat->Format($oDateTime) . $sTextQualifier; + } + break; + + case 'passed': + case 'triggered': + $sRet = $sTextQualifier . $this->GetBooleanLabel($value) . $sTextQualifier; + break; + + case 'overrun': + $sRet = $sTextQualifier . AttributeDuration::FormatDuration($value) . $sTextQualifier; + break; + } + } + } + } + return $sRet; + } + + public function GetSubItemAsXML($sItemCode, $value) + { + $sRet = Str::pure2xml((string)$value); + + switch($sItemCode) + { + case 'timespent': + case 'started': + case 'laststart': + case 'stopped': + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + break; + + case 'passed': + case 'triggered': + $sRet = $this->GetBooleanLabel($value); + break; + + case 'overrun': + break; + } + } + } + } + return $sRet; + } + + /** + * Implemented for the HTML spreadsheet format! + * + * @param string $sItemCode + * @param \ormStopWatch $value + * + * @return false|string + */ + public function GetSubItemAsEditValue($sItemCode, $value) + { + $sRet = $value; + + switch($sItemCode) + { + case 'timespent': + break; + + case 'started': + case 'laststart': + case 'stopped': + if (is_null($value)) + { + $sRet = ''; // Undefined + } + else + { + $sRet = date((string)AttributeDateTime::GetFormat(), $value); + } + break; + + default: + foreach ($this->ListThresholds() as $iThreshold => $aFoo) + { + $sThPrefix = $iThreshold.'_'; + if (substr($sItemCode, 0, strlen($sThPrefix)) == $sThPrefix) + { + // The current threshold is concerned + $sThresholdCode = substr($sItemCode, strlen($sThPrefix)); + switch($sThresholdCode) + { + case 'deadline': + if ($value) + { + $sRet = date((string)AttributeDateTime::GetFormat(), $value); + } + else + { + $sRet = ''; + } + break; + case 'passed': + case 'triggered': + $sRet = $this->GetBooleanLabel($value); + break; + case 'overrun': + break; + } + } + } + } + return $sRet; + } +} + +/** + * View of a subvalue of another attribute + * If an attribute implements the verbs GetSubItem.... then it can expose + * internal values, each of them being an attribute and therefore they + * can be displayed at different times in the object lifecycle, and used for + * reporting (as a condition in OQL, or as an additional column in an export) + * Known usages: Stop Watches can expose threshold statuses + */ +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')); + } + + public function GetParentAttCode() {return $this->Get("target_attcode");} + + /** + * Helper : get the attribute definition to which the execution will be forwarded + */ + public function GetTargetAttDef() + { + $sClass = $this->GetHostClass(); + $oParentAttDef = MetaModel::GetAttributeDef($sClass, $this->Get('target_attcode')); + return $oParentAttDef; + } + + public function GetEditClass() {return "";} + + public function GetValuesDef() {return null;} + + static public function IsBasedOnDBColumns() {return true;} + static public function IsScalar() {return true;} + public function IsWritable() {return false;} + public function GetDefaultValue(DBObject $oHostObject = null) {return null;} +// public function IsNullAllowed() {return false;} + + static public function LoadInObject() {return false;} // if this verb returns false, then GetValue must be implemented + + /** + * Used by DBOBject::Get() + * + * @param \DBObject $oHostObject + * + * @return \AttributeSubItem + * @throws \CoreException + */ + public function GetValue($oHostObject) + { + /** @var \AttributeStopWatch $oParent */ + $oParent = $this->GetTargetAttDef(); + $parentValue = $oHostObject->GetStrict($oParent->GetCode()); + $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject); + return $res; + } + + // +// protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside) + + public function FromSQLToValue($aCols, $sPrefix = '') + { + } + + public function GetSQLColumns($bFullSpec = false) + { + return array(); + } + + public function GetFilterDefinitions() + { + return array($this->GetCode() => new FilterFromAttribute($this)); + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return "="; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '!=': + return $this->GetSQLExpr()." != $sQValue"; + break; + case '=': + default: + return $this->GetSQLExpr()." = $sQValue"; + } + } + + public function GetSQLExpressions($sPrefix = '') + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemSQLExpression($this->Get('item_code')); + return $res; + } + + public function GetAsPlainText($value, $oHostObject = null, $bLocalize = true) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsPlainText($this->Get('item_code'), $value); + return $res; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsHTML($this->Get('item_code'), $value); + return $res; + } + + public function GetAsHTMLForHistory($value, $oHostObject = null, $bLocalize = true) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsHTMLForHistory($this->Get('item_code'), $value); + return $res; + } + + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsCSV($this->Get('item_code'), $value, $sSeparator, $sTextQualifier, $bConvertToPlainText); + return $res; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsXML($this->Get('item_code'), $value); + return $res; + } + + /** + * As of now, this function must be implemented to have the value in spreadsheet format + */ + public function GetEditValue($value, $oHostObj = null) + { + $oParent = $this->GetTargetAttDef(); + $res = $oParent->GetSubItemAsEditValue($this->Get('item_code'), $value); + return $res; + } + + public function IsPartOfFingerprint() + { + return false; + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\LabelField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + parent::MakeFormField($oObject, $oFormField); + + // Note : As of today, this attribute is -by nature- only supported in readonly mode, not edition + $sAttCode = $this->GetCode(); + $oFormField->SetCurrentValue(html_entity_decode($oObject->GetAsHTML($sAttCode), ENT_QUOTES, 'UTF-8')); + $oFormField->SetReadOnly(true); + + return $oFormField; + } + +} + +/** + * One way encrypted (hashed) password + */ +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")); + } + + public function GetEditClass() {return "One Way Password";} + + static public function IsBasedOnDBColumns() {return true;} + static public function IsScalar() {return true;} + public function IsWritable() {return true;} + public function GetDefaultValue(DBObject $oHostObject = null) {return "";} + public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);} + + // Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted) + public function MakeRealValue($proposedValue, $oHostObj) + { + $oPassword = $proposedValue; + if (is_object($oPassword)) + { + $oPassword = clone $proposedValue; + } + else + { + $oPassword = new ormPassword('', ''); + $oPassword->SetPassword($proposedValue); + } + return $oPassword; + } + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); // Warning: AttributeOneWayPassword does not have any sql property so code = sql ! + } + $aColumns = array(); + // Note: to optimize things, the existence of the attribute is determined by the existence of one column with an empty suffix + $aColumns[''] = $sPrefix.'_hash'; + $aColumns['_salt'] = $sPrefix.'_salt'; + return $aColumns; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + if (!array_key_exists($sPrefix, $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : ''; + + if (!array_key_exists($sPrefix.'_salt', $aCols)) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}"); + } + $sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : ''; + + $value = new ormPassword($hashed, $sSalt); + return $value; + } + + public function GetSQLValues($value) + { + // #@# Optimization: do not load blobs anytime + // As per mySQL doc, selecting blob columns will prevent mySQL from + // using memory in case a temporary table has to be created + // (temporary tables created on disk) + // We will have to remove the blobs from the list of attributes when doing the select + // then the use of Get() should finalize the load + if ($value instanceOf ormPassword) + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = $value->GetHash(); + $aValues[$this->GetCode().'_salt'] = $value->GetSalt(); + } + else + { + $aValues = array(); + $aValues[$this->GetCode().'_hash'] = ''; + $aValues[$this->GetCode().'_salt'] = ''; + } + return $aValues; + } + + public function GetSQLColumns($bFullSpec = false) + { + $aColumns = array(); + $aColumns[$this->GetCode().'_hash'] = 'TINYBLOB'; + $aColumns[$this->GetCode().'_salt'] = 'TINYBLOB'; + return $aColumns; + } + + public function GetImportColumns() + { + $aColumns = array(); + $aColumns[$this->GetCode()] = 'TINYTEXT'.CMDBSource::GetSqlStringColumnDefinition(); + return $aColumns; + } + + public function FromImportToValue($aCols, $sPrefix = '') + { + if (!isset($aCols[$sPrefix])) + { + $sAvailable = implode(', ', array_keys($aCols)); + throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}"); + } + $sClearPwd = $aCols[$sPrefix]; + + $oPassword = new ormPassword('', ''); + $oPassword->SetPassword($sClearPwd); + return $oPassword; + } + + public function GetFilterDefinitions() + { + return array(); + // still not working... see later... + } + + public function GetBasicFilterOperators() + { + return array(); + } + public function GetBasicFilterLooseOperator() + { + return '='; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + return 'true'; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (is_object($value)) + { + return $value->GetAsHTML(); + } + return ''; + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + return ''; // Not exportable in CSV + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + return ''; // Not exportable in XML + } + + public function GetValueLabel($sValue, $oHostObj = null) + { + // Don't display anything in "group by" reports + return '*****'; + } + +} + +// Indexed array having two dimensions +class AttributeTable extends AttributeDBField +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + public function GetEditClass() {return "Table";} + + protected function GetSQLCol($bFullSpec = false) + { + return "LONGTEXT".CMDBSource::GetSqlStringColumnDefinition(); + } + + public function GetMaxSize() + { + return null; + } + + public function GetNullValue() + { + return array(); + } + + public function IsNull($proposedValue) + { + return (count($proposedValue) == 0); + } + + public function GetEditValue($sValue, $oHostObj = null) + { + return ''; + } + + // Facilitate things: allow the user to Set the value from a string + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) + { + return array(); + } + else if (!is_array($proposedValue)) + { + return array(0 => array(0 => $proposedValue)); + } + return $proposedValue; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + try + { + $value = @unserialize($aCols[$sPrefix.'']); + if ($value === false) + { + $value = $this->MakeRealValue($aCols[$sPrefix.''], null); + } + } + catch(Exception $e) + { + $value = $this->MakeRealValue($aCols[$sPrefix.''], null); + } + + return $value; + } + + public function GetSQLValues($value) + { + $aValues = array(); + $aValues[$this->Get("sql")] = serialize($value); + return $aValues; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (!is_array($value)) + { + throw new CoreException('Expecting an array', array('found' => get_class($value))); + } + if (count($value) == 0) + { + return ""; + } + + $sRes = ""; + $sRes .= ""; + foreach($value as $iRow => $aRawData) + { + $sRes .= ""; + foreach ($aRawData as $iCol => $cell) + { + // Note: avoid the warning in case the cell is made of an array + $sCell = @Str::pure2html((string)$cell); + $sCell = str_replace("\n", "
\n", $sCell); + $sRes .= ""; + } + $sRes .= ""; + } + $sRes .= ""; + $sRes .= "
$sCell
"; + return $sRes; + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + // Not implemented + return ''; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (!is_array($value) || count($value) == 0) + { + return ""; + } + + $sRes = ""; + foreach($value as $iRow => $aRawData) + { + $sRes .= ""; + foreach ($aRawData as $iCol => $cell) + { + $sCell = Str::pure2xml((string)$cell); + $sRes .= "$sCell"; + } + $sRes .= ""; + } + return $sRes; + } +} + +// The PHP value is a hash array, it is stored as a TEXT column +class AttributePropertySet extends AttributeTable +{ + public function GetEditClass() {return "PropertySet";} + + // Facilitate things: allow the user to Set the value from a string + public function MakeRealValue($proposedValue, $oHostObj) + { + if (!is_array($proposedValue)) + { + return array('?' => (string)$proposedValue); + } + return $proposedValue; + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + if (!is_array($value)) + { + throw new CoreException('Expecting an array', array('found' => get_class($value))); + } + if (count($value) == 0) + { + return ""; + } + + $sRes = ""; + $sRes .= ""; + foreach($value as $sProperty => $sValue) + { + if ($sProperty == 'auth_pwd') + { + $sValue = '*****'; + } + $sRes .= ""; + $sCell = str_replace("\n", "
\n", Str::pure2html((string)$sValue)); + $sRes .= ""; + $sRes .= ""; + } + $sRes .= ""; + $sRes .= "
$sProperty$sCell
"; + return $sRes; + } + + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + if (!is_array($value) || count($value) == 0) + { + return ""; + } + + $aRes = array(); + foreach($value as $sProperty => $sValue) + { + if ($sProperty == 'auth_pwd') + { + $sValue = '*****'; + } + $sFrom = array(',', '='); + $sTo = array('\,', '\='); + $aRes[] = $sProperty.'='.str_replace($sFrom, $sTo, (string)$sValue); + } + $sRaw = implode(',', $aRes); + + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, $sRaw); + return $sTextQualifier.$sEscaped.$sTextQualifier; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + if (!is_array($value) || count($value) == 0) + { + return ""; + } + + $sRes = ""; + foreach($value as $sProperty => $sValue) + { + if ($sProperty == 'auth_pwd') + { + $sValue = '*****'; + } + $sRes .= ""; + $sRes .= Str::pure2xml((string)$sValue); + $sRes .= ""; + } + return $sRes; + } +} + +/** + * The attribute dedicated to the friendly name automatic attribute (not written) + * + * @package iTopORM + */ + +/** + * The attribute dedicated to the friendly name automatic attribute (not written) + * + * @package iTopORM + */ +class AttributeFriendlyName extends AttributeDefinition +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_STRING; + public $m_sValue; + + public function __construct($sCode) + { + $this->m_sCode = $sCode; + $aParams = array(); + $aParams["default_value"] = ''; + parent::__construct($sCode, $aParams); + + $this->m_sValue = $this->Get("default_value"); + } + + + public function GetEditClass() {return "";} + + public function GetValuesDef() {return null;} + public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} + + static public function IsScalar() {return true;} + public function IsNullAllowed() {return false;} + + public function GetSQLExpressions($sPrefix = '') + { + if ($sPrefix == '') + { + $sPrefix = $this->GetCode(); // Warning AttributeComputedFieldVoid does not have any sql property + } + return array('' => $sPrefix); + } + + static public function IsBasedOnOQLExpression() {return true;} + public function GetOQLExpression() + { + return MetaModel::GetNameExpression($this->GetHostClass()); + } + + public function GetLabel($sDefault = null) + { + $sLabel = parent::GetLabel(''); + if (strlen($sLabel) == 0) + { + $sLabel = Dict::S('Core:FriendlyName-Label'); + } + return $sLabel; + } + public function GetDescription($sDefault = null) + { + $sLabel = parent::GetDescription(''); + if (strlen($sLabel) == 0) + { + $sLabel = Dict::S('Core:FriendlyName-Description'); + } + return $sLabel; + } + + public function FromSQLToValue($aCols, $sPrefix = '') + { + $sValue = $aCols[$sPrefix]; + return $sValue; + } + + public function IsWritable() + { + return false; + } + public function IsMagic() + { + return true; + } + + static public function IsBasedOnDBColumns() + { + return false; + } + + public function SetFixedValue($sValue) + { + $this->m_sValue = $sValue; + } + public function GetDefaultValue(DBObject $oHostObject = null) + { + return $this->m_sValue; + } + + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + return Str::pure2html((string)$sValue); + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; + } + + static function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\StringField'; + } + + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + } + $oFormField->SetReadOnly(true); + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + // Do not display friendly names in the history of change + public function DescribeChangeAsHTML($sOldValue, $sNewValue, $sLabel = null) + { + return ''; + } + + public function GetFilterDefinitions() + { + return array($this->GetCode() => new FilterFromAttribute($this)); + } + + public function GetBasicFilterOperators() + { + return array("="=>"equals", "!="=>"differs from"); + } + + public function GetBasicFilterLooseOperator() + { + return "Contains"; + } + + public function GetBasicFilterSQLExpr($sOpCode, $value) + { + $sQValue = CMDBSource::Quote($value); + switch ($sOpCode) + { + case '=': + case '!=': + return $this->GetSQLExpr()." $sOpCode $sQValue"; + case 'Contains': + return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%"); + case 'NotLike': + return $this->GetSQLExpr()." NOT LIKE $sQValue"; + case 'Like': + default: + return $this->GetSQLExpr()." LIKE $sQValue"; + } + } + + public function IsPartOfFingerprint() { return false; } +} + +/** + * Holds the setting for the redundancy on a specific relation + * Its value is a string, containing either: + * - 'disabled' + * - 'n', where n is a positive integer value giving the minimum count of items upstream + * - 'n%', where n is a positive integer value, giving the minimum as a percentage of the total count of items upstream + * + * @package iTopORM + */ +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'); + } + + public function GetValuesDef() {return null;} + public function GetPrerequisiteAttributes($sClass = null) {return array();} + + public function GetEditClass() {return "RedundancySetting";} + protected function GetSQLCol($bFullSpec = false) + { + return "VARCHAR(20)" + .CMDBSource::GetSqlStringColumnDefinition() + .($bFullSpec ? $this->GetSQLColSpec() : ''); + } + + + public function GetValidationPattern() + { + return "^[0-9]{1,3}|[0-9]{1,2}%|disabled$"; + } + + public function GetMaxSize() + { + return 20; + } + + public function GetDefaultValue(DBObject $oHostObject = null) + { + $sRet = 'disabled'; + if ($this->Get('enabled')) + { + if ($this->Get('min_up_type') == 'count') + { + $sRet = (string) $this->Get('min_up'); + } + else // percent + { + $sRet = $this->Get('min_up').'%'; + } + } + return $sRet; + } + + public function IsNullAllowed() + { + return false; + } + + public function GetNullValue() + { + return ''; + } + + public function IsNull($proposedValue) + { + return ($proposedValue == ''); + } + + public function MakeRealValue($proposedValue, $oHostObj) + { + if (is_null($proposedValue)) return ''; + return (string)$proposedValue; + } + + public function ScalarToSQL($value) + { + if (!is_string($value)) + { + throw new CoreException('Expected the attribute value to be a string', array('found_type' => gettype($value), 'value' => $value, 'class' => $this->GetHostClass(), 'attribute' => $this->GetCode())); + } + return $value; + } + + public function GetRelationQueryData() + { + foreach (MetaModel::EnumRelationQueries($this->GetHostClass(), $this->Get('relation_code'), false) as $sDummy => $aQueryInfo) + { + if ($aQueryInfo['sFromClass'] == $this->Get('from_class')) + { + if ($aQueryInfo['sNeighbour'] == $this->Get('neighbour_id')) + { + return $aQueryInfo; + } + } + } + return array(); + } + + /** + * Find the user option label + * + * @param string $sUserOption possible values : disabled|cout|percent + * @param string $sDefault + * + * @return string + * @throws \Exception + */ + public function GetUserOptionFormat($sUserOption, $sDefault = null) + { + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, null, true /*user lang*/); + if (is_null($sLabel)) + { + // If no default value is specified, let's define the most relevant one for developping purposes + if (is_null($sDefault)) + { + $sDefault = str_replace('_', ' ', $this->m_sCode.':'.$sUserOption.'(%1$s)'); + } + // Browse the hierarchy again, accepting default (english) translations + $sLabel = $this->SearchLabel('/Attribute:'.$this->m_sCode.'/'.$sUserOption, $sDefault, false); + } + return $sLabel; + } + + /** + * Override to display the value in the GUI + * + * @param string $sValue + * @param \DBObject $oHostObject + * @param bool $bLocalize + * + * @return string + * @throws \CoreException + * @throws \DictExceptionMissingString + */ + public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) + { + $sCurrentOption = $this->GetCurrentOption($sValue); + $sClass = $oHostObject ? get_class($oHostObject) : $this->m_sHostClass; + return sprintf($this->GetUserOptionFormat($sCurrentOption), $this->GetMinUpValue($sValue), MetaModel::GetName($sClass)); + } + + public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, (string)$sValue); + return $sTextQualifier.$sEscaped.$sTextQualifier; + } + + /** + * Helper to interpret the value, given the current settings and string representation of the attribute + */ + public function IsEnabled($sValue) + { + if ($this->get('enabled_mode') == 'fixed') + { + $bRet = $this->get('enabled'); + } + else + { + $bRet = ($sValue != 'disabled'); + } + return $bRet; + } + + /** + * Helper to interpret the value, given the current settings and string representation of the attribute + */ + public function GetMinUpType($sValue) + { + if ($this->get('min_up_mode') == 'fixed') + { + $sRet = $this->get('min_up_type'); + } + else + { + $sRet = 'count'; + if (substr(trim($sValue), -1, 1) == '%') + { + $sRet = 'percent'; + } + } + return $sRet; + } + + /** + * Helper to interpret the value, given the current settings and string representation of the attribute + */ + public function GetMinUpValue($sValue) + { + if ($this->get('min_up_mode') == 'fixed') + { + $iRet = (int) $this->Get('min_up'); + } + else + { + $sRefValue = $sValue; + if (substr(trim($sValue), -1, 1) == '%') + { + $sRefValue = substr(trim($sValue), 0, -1); + } + $iRet = (int) trim($sRefValue); + } + return $iRet; + } + + /** + * Helper to determine if the redundancy can be viewed/edited by the end-user + */ + public function IsVisible() + { + $bRet = false; + if ($this->Get('enabled_mode') == 'fixed') + { + $bRet = $this->Get('enabled'); + } + elseif ($this->Get('enabled_mode') == 'user') + { + $bRet = true; + } + return $bRet; + } + + public function IsWritable() + { + if (($this->Get('enabled_mode') == 'fixed') && ($this->Get('min_up_mode') == 'fixed')) + { + return false; + } + return true; + } + + /** + * Returns an HTML form that can be read by ReadValueFromPostedForm + */ + public function GetDisplayForm($sCurrentValue, $oPage, $bEditMode = false, $sFormPrefix = '') + { + $sRet = ''; + $aUserOptions = $this->GetUserOptions($sCurrentValue); + if (count($aUserOptions) < 2) + { + $bEditOption = false; + } + else + { + $bEditOption = $bEditMode; + } + $sCurrentOption = $this->GetCurrentOption($sCurrentValue); + foreach($aUserOptions as $sUserOption) + { + $bSelected = ($sUserOption == $sCurrentOption); + $sRet .= '
'; + $sRet .= $this->GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditOption, $sUserOption, $bSelected); + $sRet .= '
'; + } + return $sRet; + } + + const USER_OPTION_DISABLED = 'disabled'; + const USER_OPTION_ENABLED_COUNT = 'count'; + const USER_OPTION_ENABLED_PERCENT = 'percent'; + + /** + * Depending on the xxx_mode parameters, build the list of options that are allowed to the end-user + */ + protected function GetUserOptions($sValue) + { + $aRet = array(); + if ($this->Get('enabled_mode') == 'user') + { + $aRet[] = self::USER_OPTION_DISABLED; + } + + if ($this->Get('min_up_mode') == 'user') + { + $aRet[] = self::USER_OPTION_ENABLED_COUNT; + $aRet[] = self::USER_OPTION_ENABLED_PERCENT; + } + else + { + if ($this->GetMinUpType($sValue) == 'count') + { + $aRet[] = self::USER_OPTION_ENABLED_COUNT; + } + else + { + $aRet[] = self::USER_OPTION_ENABLED_PERCENT; + } + } + return $aRet; + } + + /** + * Convert the string representation into one of the existing options + */ + protected function GetCurrentOption($sValue) + { + $sRet = self::USER_OPTION_DISABLED; + if ($this->IsEnabled($sValue)) + { + if ($this->GetMinUpType($sValue) == 'count') + { + $sRet = self::USER_OPTION_ENABLED_COUNT; + } + else + { + $sRet = self::USER_OPTION_ENABLED_PERCENT; + } + } + return $sRet; + } + + /** + * Display an option (form, or current value) + * + * @param string $sCurrentValue + * @param \WebPage $oPage + * @param string $sFormPrefix + * @param bool $bEditMode + * @param string $sUserOption + * @param bool $bSelected + * + * @return string + * @throws \CoreException + * @throws \DictExceptionMissingString + * @throws \Exception + */ + protected function GetDisplayOption($sCurrentValue, $oPage, $sFormPrefix, $bEditMode, $sUserOption, $bSelected = true) + { + $sRet = ''; + + $iCurrentValue = $this->GetMinUpValue($sCurrentValue); + if ($bEditMode) + { + $sValue = null; + $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); + switch ($sUserOption) + { + case self::USER_OPTION_DISABLED: + $sValue = ''; // Empty placeholder + break; + + case self::USER_OPTION_ENABLED_COUNT: + if ($bEditMode) + { + $sName = $sHtmlNamesPrefix.'_min_up_count'; + $sEditValue = $bSelected ? $iCurrentValue : ''; + $sValue = ''; + // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) + $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); + } + else + { + $sValue = $iCurrentValue; + } + break; + + case self::USER_OPTION_ENABLED_PERCENT: + if ($bEditMode) + { + $sName = $sHtmlNamesPrefix.'_min_up_percent'; + $sEditValue = $bSelected ? $iCurrentValue : ''; + $sValue = ''; + // To fix an issue on Firefox: focus set to the option (because the input is within the label for the option) + $oPage->add_ready_script("\$('[name=\"$sName\"]').click(function(){var me=this; setTimeout(function(){\$(me).focus();}, 100);});"); + } + else + { + $sValue = $iCurrentValue; + } + break; + } + $sLabel = sprintf($this->GetUserOptionFormat($sUserOption), $sValue, MetaModel::GetName($this->GetHostClass())); + + $sOptionName = $sHtmlNamesPrefix.'_user_option'; + $sOptionId = $sOptionName.'_'.$sUserOption; + $sChecked = $bSelected ? 'checked' : ''; + $sRet = ' '; + } + else + { + // Read-only: display only the currently selected option + if ($bSelected) + { + $sRet = sprintf($this->GetUserOptionFormat($sUserOption), $iCurrentValue, MetaModel::GetName($this->GetHostClass())); + } + } + return $sRet; + } + + /** + * Makes the string representation out of the values given by the form defined in GetDisplayForm + */ + public function ReadValueFromPostedForm($sFormPrefix) + { + $sHtmlNamesPrefix = 'rddcy_'.$this->Get('relation_code').'_'.$this->Get('from_class').'_'.$this->Get('neighbour_id'); + + $iMinUpCount = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_count', null, 'raw_data'); + $iMinUpPercent = (int) utils::ReadPostedParam($sHtmlNamesPrefix.'_min_up_percent', null, 'raw_data'); + $sSelectedOption = utils::ReadPostedParam($sHtmlNamesPrefix.'_user_option', null, 'raw_data'); + switch ($sSelectedOption) + { + case self::USER_OPTION_ENABLED_COUNT: + $sRet = $iMinUpCount; + break; + + case self::USER_OPTION_ENABLED_PERCENT: + $sRet = $iMinUpPercent.'%'; + break; + + case self::USER_OPTION_DISABLED: + default: + $sRet = 'disabled'; + break; + } + return $sRet; + } +} + +/** + * Custom fields managed by an external implementation + * + * @package iTopORM + */ +class AttributeCustomFields extends AttributeDefinition +{ + const SEARCH_WIDGET_TYPE = self::SEARCH_WIDGET_TYPE_RAW; + + static public function ListExpectedParams() + { + return array_merge(parent::ListExpectedParams(), array("handler_class")); + } + + public function GetEditClass() {return "CustomFields";} + public function IsWritable() {return true;} + static public function LoadFromDB() {return false;} // See ReadValue... + + public function GetDefaultValue(DBObject $oHostObject = null) + { + return new ormCustomFieldsValue($oHostObject, $this->GetCode()); + } + + public function GetBasicFilterOperators() {return array();} + public function GetBasicFilterLooseOperator() {return '';} + public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';} + + /** + * @param DBObject $oHostObject + * @param array|null $aValues + * @return CustomFieldsHandler + */ + public function GetHandler($aValues = null) + { + $sHandlerClass = $this->Get('handler_class'); + $oHandler = new $sHandlerClass($this->GetCode()); + if (!is_null($aValues)) + { + $oHandler->SetCurrentValues($aValues); + } + return $oHandler; + } + + public function GetPrerequisiteAttributes($sClass = null) + { + $sHandlerClass = $this->Get('handler_class'); + return $sHandlerClass::GetPrerequisiteAttributes($sClass); + } + + public function GetEditValue($sValue, $oHostObj = null) + { + return $this->GetForTemplate($sValue, '', $oHostObj, true); + } + + /** + * Makes the string representation out of the values given by the form defined in GetDisplayForm + */ + public function ReadValueFromPostedForm($oHostObject, $sFormPrefix) + { + $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true); + return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData); + } + + public function MakeRealValue($proposedValue, $oHostObject) + { + if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue)) + { + return $proposedValue; + } + elseif (is_string($proposedValue)) + { + $aValues = json_decode($proposedValue, true); + return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); + } + elseif (is_array($proposedValue)) + { + return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $proposedValue); + } + elseif (is_null($proposedValue)) + { + return new ormCustomFieldsValue($oHostObject, $this->GetCode()); + } + throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue)); + } + + static public function GetFormFieldClass() + { + return '\\Combodo\\iTop\\Form\\Field\\SubFormField'; + } + + /** + * Override to build the relevant form field + * + * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behaves more like a Prepare. + */ + public function MakeFormField(DBObject $oObject, $oFormField = null) + { + if ($oFormField === null) + { + $sFormFieldClass = static::GetFormFieldClass(); + $oFormField = new $sFormFieldClass($this->GetCode()); + $oFormField->SetForm($this->GetForm($oObject)); + } + parent::MakeFormField($oObject, $oFormField); + + return $oFormField; + } + + /** + * @param DBObject $oHostObject + * @param null $sFormPrefix + * @return Combodo\iTop\Form\Form + * @throws \Exception + */ + public function GetForm(DBObject $oHostObject, $sFormPrefix = null) + { + try + { + $oValue = $oHostObject->Get($this->GetCode()); + $oHandler = $this->GetHandler($oValue->GetValues()); + $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode(); + $oHandler->BuildForm($oHostObject, $sFormId); + $oForm = $oHandler->GetForm(); + } + catch (Exception $e) + { + $oForm = new \Combodo\iTop\Form\Form(''); + $oField = new \Combodo\iTop\Form\Field\LabelField(''); + $oField->SetLabel('Custom field error: '.$e->getMessage()); + $oForm->AddField($oField); + $oForm->Finalize(); + } + return $oForm; + } + + /** + * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false and LoadInObject returns true + * @param $oHostObject + * @return ormCustomFieldsValue + */ + public function ReadValue($oHostObject) + { + try + { + $oHandler = $this->GetHandler(); + $aValues = $oHandler->ReadValues($oHostObject); + $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); + } + catch (Exception $e) + { + $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode()); + } + return $oRet; + } + + /** + * Record the data (currently in the processing of recording the host object) + * It is assumed that the data has been checked prior to calling Write() + * @param DBObject $oHostObject + * @param ormCustomFieldsValue|null $oValue (null is the default value) + */ + public function WriteValue(DBObject $oHostObject, ormCustomFieldsValue $oValue = null) + { + if (is_null($oValue)) + { + $oHandler = $this->GetHandler(); + $aValues = array(); + } + else + { + // Pass the values through the form to make sure that they are correct + $oHandler = $this->GetHandler($oValue->GetValues()); + $oHandler->BuildForm($oHostObject, ''); + $oForm = $oHandler->GetForm(); + $aValues = $oForm->GetCurrentValues(); + } + return $oHandler->WriteValues($oHostObject, $aValues); + } + + /** + * The part of the current attribute in the object's signature, for the supplied value + * @param ormCustomFieldsValue $value The value of this attribute for the object + * @return string The "signature" for this field/attribute + */ + public function Fingerprint($value) + { + $oHandler = $this->GetHandler($value->GetValues()); + return $oHandler->GetValueFingerprint(); + } + + /** + * Check the validity of the data + * @param DBObject $oHostObject + * @param $value + * @return bool|string true or error message + */ + public function CheckValue(DBObject $oHostObject, $value) + { + try + { + $oHandler = $this->GetHandler($value->GetValues()); + $oHandler->BuildForm($oHostObject, ''); + $oForm = $oHandler->GetForm(); + $oForm->Validate(); + if ($oForm->GetValid()) + { + $ret = true; + } + else + { + $aMessages = array(); + foreach ($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages) + { + $aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages); + } + $ret = 'Invalid value: '.implode(', ', $aMessages); + } + } + catch (Exception $e) + { + $ret = $e->getMessage(); + } + return $ret; + } + + /** + * Cleanup data upon object deletion (object id still available here) + * @param DBObject $oHostObject + * @return + * @throws \CoreException + */ + public function DeleteValue(DBObject $oHostObject) + { + $oValue = $oHostObject->Get($this->GetCode()); + $oHandler = $this->GetHandler($oValue->GetValues()); + return $oHandler->DeleteValues($oHostObject); + } + + public function GetAsHTML($value, $oHostObject = null, $bLocalize = true) + { + try + { + $sRet = $value->GetAsHTML($bLocalize); + } + catch (Exception $e) + { + $sRet = 'Custom field error: '.htmlentities($e->getMessage(), ENT_QUOTES, 'UTF-8'); + } + return $sRet; + } + + public function GetAsXML($value, $oHostObject = null, $bLocalize = true) + { + try + { + $sRet = $value->GetAsXML($bLocalize); + } + catch (Exception $e) + { + $sRet = Str::pure2xml('Custom field error: '.$e->getMessage()); + } + return $sRet; + } + + public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false) + { + try + { + $sRet = $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText); + } + catch (Exception $e) + { + $sFrom = array("\r\n", $sTextQualifier); + $sTo = array("\n", $sTextQualifier.$sTextQualifier); + $sEscaped = str_replace($sFrom, $sTo, 'Custom field error: '.$e->getMessage()); + $sRet = $sTextQualifier.$sEscaped.$sTextQualifier; + } + return $sRet; + } + + /** + * List the available verbs for 'GetForTemplate' + */ + public function EnumTemplateVerbs() + { + $sHandlerClass = $this->Get('handler_class'); + return $sHandlerClass::EnumTemplateVerbs(); + } + + /** + * Get various representations of the value, for insertion into a template (e.g. in Notifications) + * + * @param $value mixed The current value of the field + * @param $sVerb string The verb specifying the representation of the value + * @param $oHostObject DBObject The object + * @param $bLocalize bool Whether or not to localize the value + * + * @return string + */ + public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true) + { + try + { + $sRet = $value->GetForTemplate($sVerb, $bLocalize); + } + catch (Exception $e) + { + $sRet = 'Custom field error: '.$e->getMessage(); + } + return $sRet; + } + + public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null) + { + return null; + } + + /** + * Helper to get a value that will be JSON encoded + * The operation is the opposite to FromJSONToValue + * + * @param $value + * + * @return string + */ + public function GetForJSON($value) + { + return null; + } + + /** + * Helper to form a value, given JSON decoded data + * The operation is the opposite to GetForJSON + * + * @param string $json + * + * @return array + */ + public function FromJSONToValue($json) + { + return null; + } + + public function Equals($val1, $val2) + { + try + { + $bEquals = $val1->Equals($val2); + } + catch (Exception $e) + { + $bEquals = false; + } + return $bEquals; + } +} + +class AttributeArchiveFlag extends AttributeBoolean +{ + public function __construct($sCode) + { + parent::__construct($sCode, array("allowed_values" => null, "sql" => $sCode, "default_value" => false, "is_null_allowed" => false, "depends_on" => array())); + } + public function RequiresIndex() + { + return true; + } + public function CopyOnAllTables() + { + return true; + } + public function IsWritable() + { + return false; + } + public function IsMagic() + { + return true; + } + public function GetLabel($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeArchiveFlag/Label', $sDefault); + return parent::GetLabel($sDefault); + } + public function GetDescription($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeArchiveFlag/Label+', $sDefault); + return parent::GetDescription($sDefault); + } +} +class AttributeArchiveDate extends AttributeDate +{ + public function GetLabel($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeArchiveDate/Label', $sDefault); + return parent::GetLabel($sDefault); + } + public function GetDescription($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeArchiveDate/Label+', $sDefault); + return parent::GetDescription($sDefault); + } +} + +class AttributeObsolescenceFlag extends AttributeBoolean +{ + public function __construct($sCode) + { + parent::__construct($sCode, array("allowed_values"=>null, "sql"=>$sCode, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())); + } + public function IsWritable() + { + return false; + } + public function IsMagic() + { + return true; + } + + static public function IsBasedOnDBColumns() {return false;} + /** + * Returns true if the attribute value is built after other attributes by the mean of an expression (obtained via GetOQLExpression) + * @return bool + */ + static public function IsBasedOnOQLExpression() {return true;} + public function GetOQLExpression() + { + return MetaModel::GetObsolescenceExpression($this->GetHostClass()); + } + + public function GetSQLExpressions($sPrefix = '') + { + return array(); + } + public function GetSQLColumns($bFullSpec = false) {return array();} // returns column/spec pairs (1 in most of the cases), for STRUCTURING (DB creation) + public function GetSQLValues($value) {return array();} // returns column/value pairs (1 in most of the cases), for WRITING (Insert, Update) + + public function GetEditClass() {return "";} + + public function GetValuesDef() {return null;} + public function GetPrerequisiteAttributes($sClass = null) {return $this->GetOptional("depends_on", array());} + + public function IsDirectField() {return true;} + static public function IsScalar() {return true;} + public function GetSQLExpr() + { + return null; + } + + public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);} + public function IsNullAllowed() {return false;} + + public function GetLabel($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label', $sDefault); + return parent::GetLabel($sDefault); + } + public function GetDescription($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeObsolescenceFlag/Label+', $sDefault); + return parent::GetDescription($sDefault); + } +} + +class AttributeObsolescenceDate extends AttributeDate +{ + public function GetLabel($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label', $sDefault); + return parent::GetLabel($sDefault); + } + public function GetDescription($sDefault = null) + { + $sDefault = Dict::S('Core:AttributeObsolescenceDate/Label+', $sDefault); + return parent::GetDescription($sDefault); + } +} diff --git a/core/ormtagset.class.inc.php b/core/ormtagset.class.inc.php index 97a57986fe..d7e8d2603b 100644 --- a/core/ormtagset.class.inc.php +++ b/core/ormtagset.class.inc.php @@ -52,7 +52,7 @@ final class ormTagSet private $aAdded = array(); /** - * @var int[] Removed items + * @var DBObject[] Removed items */ private $aRemoved = array(); @@ -146,15 +146,17 @@ final class ormTagSet return $aValues; } - public function GetLabel() + /** + * @return array of tag labels indexed by code + */ + public function GetTags() { - $aLabels = array(); - $aValues = array(); + $aTags = array(); foreach ($this->aPreserved as $oTag) { try { - $aValues[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + $aTags[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); } catch (CoreException $e) { IssueLog::Error($e->getMessage()); @@ -164,18 +166,115 @@ final class ormTagSet { try { - $aValues[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + $aTags[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); } catch (CoreException $e) { IssueLog::Error($e->getMessage()); } } - ksort($aValues); - foreach($aValues as $sLabel) - { - $aLabels[] = $sLabel; - } - return $aLabels; + ksort($aTags); + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the added tags + */ + public function GetAddedTags() + { + $aTags = array(); + foreach ($this->aAdded as $oTag) + { + try + { + $aTags[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + ksort($aTags); + return $aTags; + } + + /** + * @return array of tag labels indexed by code for only the removed tags + */ + public function GetRemovedTags() + { + $aTags = array(); + foreach ($this->aRemoved as $oTag) + { + try + { + $aTags[$oTag->Get('tag_code')] = $oTag->Get('tag_label'); + } catch (CoreException $e) + { + IssueLog::Error($e->getMessage()); + } + } + ksort($aTags); + return $aTags; + } + + /** Get the delta with another TagSet + * + * $aDelta['added] = array of tag labels indexed by code for only the added tags + * $aDelta['removed'] = array of tag labels indexed by code for only the removed tags + * + * @param \ormTagSet $oOtherTagSet + * + * @return array + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function GetDelta(ormTagSet $oOtherTagSet) + { + $oTag = new ormTagSet($this->sClass, $this->sAttCode); + // Set the initial value + $aOrigTagCodes = $this->GetValue(); + $oTag->SetValue($aOrigTagCodes); + // now remove everything + foreach($aOrigTagCodes as $sTagCode) + { + $oTag->RemoveTag($sTagCode); + } + // now add the tags of the other TagSet + foreach($oOtherTagSet->GetValue() as $sTagCode) + { + $oTag->AddTag($sTagCode); + } + $aDelta = array(); + $aDelta['added'] = $oTag->GetAddedTags(); + $aDelta['removed'] = $oTag->GetRemovedTags(); + + return $aDelta; + } + + /** + * Apply a delta to the current TagSet + * + * @param $aDelta + * + * @throws \CoreException + * @throws \CoreUnexpectedValue + */ + public function ApplyDelta($aDelta) + { + if (isset($aDelta['removed'])) + { + foreach($aDelta['removed'] as $sTagCode => $aTagLabel) + { + $this->RemoveTag($sTagCode); + } + } + if (isset($aDelta['added'])) + { + foreach($aDelta['added'] as $sTagCode => $aTagLabel) + { + $this->AddTag($sTagCode); + } + } } /** diff --git a/test/core/ormTagSetTest.php b/test/core/ormTagSetTest.php index b1bf7fae10..857d13751a 100644 --- a/test/core/ormTagSetTest.php +++ b/test/core/ormTagSetTest.php @@ -152,4 +152,43 @@ use ormTagSet; $oTagSet->RemoveTag('tag2'); static::assertEquals($oTagSet->GetValue(), array()); } + + public function testGetDelta() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + $this->CreateTagData('Ticket', 'tagfield', 'tag3', 'Third'); + $this->CreateTagData('Ticket', 'tagfield', 'tag4', 'Fourth'); + + $oTagSet1 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet1->SetValue(array('tag1', 'tag2')); + + $oTagSet2 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet2->SetValue(array('tag1', 'tag3', 'tag4')); + + $aDelta = $oTagSet1->GetDelta($oTagSet2); + static::assertCount(2, $aDelta); + static::assertCount(2, $aDelta['added']); + static::assertCount(1, $aDelta['removed']); + } + + public function testApplyDelta() + { + $this->CreateTagData('Ticket', 'tagfield', 'tag1', 'First'); + $this->CreateTagData('Ticket', 'tagfield', 'tag2', 'Second'); + $this->CreateTagData('Ticket', 'tagfield', 'tag3', 'Third'); + $this->CreateTagData('Ticket', 'tagfield', 'tag4', 'Fourth'); + + $oTagSet1 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet1->SetValue(array('tag1', 'tag2')); + + $oTagSet2 = new ormTagSet('Ticket', 'tagfield'); + $oTagSet2->SetValue(array('tag1', 'tag3', 'tag4')); + + $aDelta = $oTagSet1->GetDelta($oTagSet2); + + $oTagSet1->ApplyDelta($aDelta); + + static::assertTrue($oTagSet1->Equals($oTagSet2)); + } } From e2c3ea22e40f2c0edcce7bbf8d9ea6d064c1977e Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 6 Sep 2018 15:42:10 +0200 Subject: [PATCH 021/113] =?UTF-8?q?N=C2=B0931:=20Start=20bulk=20modify=20i?= =?UTF-8?q?mplementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/cmdbabstract.class.inc.php | 2033 ++++++++++++++---------- core/attributedef.class.inc.php | 7 +- core/ormtagset.class.inc.php | 10 +- core/tagsetfield.class.inc.php | 16 + 4 files changed, 1177 insertions(+), 889 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index d0edfdbde4..126aa2bc49 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -56,26 +56,28 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay protected $m_iFormId; // The ID of the form used to edit the object (when in edition mode !) static $iGlobalFormId = 1; protected $aFieldsMap; - + /** * If true, bypass IsActionAllowedOnAttribute when writing this object + * * @var bool */ protected $bAllowWrite; /** * Constructor from a row of data (as a hash 'attcode' => value) - * @param hash $aRow + * + * @param array $aRow * @param string $sClassAlias - * @param hash $aAttToLoad - * @param hash $aExtendedDataSpec + * @param array $aAttToLoad + * @param array $aExtendedDataSpec */ public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null) { parent::__construct($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec); $this->bAllowWrite = false; } - + /** * returns what will be the next ID for the forms */ @@ -83,11 +85,12 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay { return 1 + self::$iGlobalFormId; } + public static function GetUIPage() { return 'UI.php'; } - + public static function ReloadAndDisplay($oPage, $oObj, $aParams) { $oAppContext = new ApplicationContext(); @@ -104,7 +107,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay } $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/'.$oObj->GetUIPage().'?'.$sParams.'class='.get_class($oObj).'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink().'&a=1'; $oPage->add_script( -<<IsPrintableVersion()) { // Is there a message for this object ?? @@ -168,18 +173,21 @@ EOF if ($aLockInfo['locked']) { $aRanks[] = 0; - $sName = $aLockInfo['owner']->GetName(); + $sName = $aLockInfo['owner']->GetName(); if ($aLockInfo['owner']->Get('contactid') != 0) { $sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')'; } - $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); $aMessages[] = "
".Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName)."
"; + $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); + $aMessages[] = "
".Dict::Format('UI:CurrentObjectIsLockedBy_User', + $sName)."
"; } } $sMessageKey = get_class($this).'::'.$this->GetKey(); - if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages'])) + if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, + $_SESSION['obj_messages'])) { - foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) + foreach($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData) { $sMsgClass = 'message_'.$aMessageData['severity']; $aMessages[] = "
".$aMessageData['message']."
"; @@ -188,12 +196,12 @@ EOF unset($_SESSION['obj_messages'][$sMessageKey]); } array_multisort($aRanks, $aMessages); - foreach ($aMessages as $sMessage) + foreach($aMessages as $sMessage) { $oPage->add($sMessage); } } - + if (!$oPage->IsPrintableVersion()) { // action menu @@ -216,11 +224,11 @@ EOF if (count($aSyncData) > 0) { $bSynchronized = true; - foreach ($aSyncData as $iSourceId => $aSourceData) + foreach($aSyncData as $iSourceId => $aSourceData) { $oDataSource = $aSourceData['source']; $oReplica = reset($aSourceData['replica']); // Take the first one! - + $sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica); $sLink = $oDataSource->GetName(); if (!empty($sApplicationURL)) @@ -260,13 +268,14 @@ EOF $aMasterSources[$iSourceId]['url'] = $sLink; $aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen'); } - + if (is_object($oCreatorTask)) { $sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url']; if (!$bCanBeDeletedByUser) { - $sTip = "

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

"; + $sTip = "

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

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

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

"; } - + $sTip .= "

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

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

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

"; + $sTip .= "

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

"; } $sLabel = htmlentities(Dict::S('Tag:Synchronized'), ENT_QUOTES, 'UTF-8'); $sSynchroTagId = 'synchro_icon-'.$this->GetKey(); @@ -329,7 +339,7 @@ EOF } $oPage->add( -<<
$sObjectIcon
@@ -356,12 +366,12 @@ EOF function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '', $aExtraParams = array()) { - $aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); + $aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix, $aExtraParams); if (!isset($aExtraParams['disable_plugins']) || !$aExtraParams['disable_plugins']) { - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode); } @@ -381,9 +391,10 @@ EOF return $aFieldsMap; } - + /** * Add a field to the map: attcode => id used when building a form + * * @param string $sAttCode The attribute code of the field being edited * @param string $sInputId The unique ID of the control/widget in the page */ @@ -409,7 +420,10 @@ EOF { $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode); // Display mode - if (!$oAttDef->IsLinkset()) continue; // Process only linkset attributes... + if (!$oAttDef->IsLinkset()) + { + continue; + } // Process only linkset attributes... $sLinkedClass = $oAttDef->GetLinkedClass(); @@ -438,12 +452,14 @@ EOF $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); // n:n links => must be allowed to modify the linking class AND read the target class in order to edit the linkedset - if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) + if (!UserRights::IsActionAllowed($sLinkedClass, + UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) { $iFlags |= OPT_ATT_READONLY; } // n:n links => must be allowed to read the linking class AND the target class in order to display the linkedset - if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) + if (!UserRights::IsActionAllowed($sLinkedClass, + UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) { $iFlags |= OPT_ATT_HIDDEN; } @@ -462,10 +478,13 @@ EOF } } // Non-readable/hidden linkedset... don't display anything - if ($iFlags & OPT_ATT_HIDDEN) continue; - + if ($iFlags & OPT_ATT_HIDDEN) + { + continue; + } + $aArgs = array('this' => $this); - $bReadOnly = ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)); + $bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); if ($bEditMode && (!$bReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sAttCode; @@ -482,8 +501,9 @@ EOF $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription().''); $sDisplayValue = ''; // not used - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; - $this->AddToFieldsMap($sAttCode, $sInputId); + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, + $oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $this->AddToFieldsMap($sAttCode, $sInputId); $oPage->add($sHTMLValue); } else @@ -508,7 +528,7 @@ EOF 'target_attr' => $oAttDef->GetExtKeyToMe(), 'object_id' => $this->GetKey(), 'menu' => MetaModel::GetConfig()->Get('allow_menu_on_linkset'), - //'menu_actions_target' => '_blank', + //'menu_actions_target' => '_blank', 'default' => $aDefaults, 'table_id' => $sClass.'_'.$sAttCode, ); @@ -519,15 +539,15 @@ EOF $oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote()); $sTargetClass = $oLinkingAttDef->GetTargetClass(); $aParams = array( - 'link_attr' => $oAttDef->GetExtKeyToMe(), - 'object_id' => $this->GetKey(), - 'target_attr' => $oAttDef->GetExtKeyToRemote(), - 'view_link' => false, - 'menu' => false, - //'menu_actions_target' => '_blank', - 'display_limit' => true, // By default limit the list to speed up the initial load & display - 'table_id' => $sClass.'_'.$sAttCode, - ); + 'link_attr' => $oAttDef->GetExtKeyToMe(), + 'object_id' => $this->GetKey(), + 'target_attr' => $oAttDef->GetExtKeyToRemote(), + 'view_link' => false, + 'menu' => false, + //'menu_actions_target' => '_blank', + 'display_limit' => true, // By default limit the list to speed up the initial load & display + 'table_id' => $sClass.'_'.$sAttCode, + ); } $oPage->p(MetaModel::GetClassIcon($sTargetClass)." ".$oAttDef->GetDescription()); $oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'list', false); @@ -535,19 +555,21 @@ EOF } if (array_key_exists($sAttCode, $aRedundancySettings)) { - foreach ($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) + foreach($aRedundancySettings[$sAttCode] as $oRedundancyAttDef) { $sRedundancyAttCode = $oRedundancyAttDef->GetCode(); $sValue = $this->Get($sRedundancyAttCode); $iRedundancyFlags = $this->GetFormAttributeFlags($sRedundancyAttCode); - $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)); + $bRedundancyReadOnly = ($iRedundancyFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)); $oPage->add('
'); $oPage->add(''.$oRedundancyAttDef->GetLabel().''); if ($bEditMode && (!$bRedundancyReadOnly)) { $sInputId = $this->m_iFormId.'_'.$sRedundancyAttCode; - $oPage->add("".self::GetFormElementForField($oPage, $sClass, $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, $aArgs).''); + $oPage->add("".self::GetFormElementForField($oPage, $sClass, + $sRedundancyAttCode, $oRedundancyAttDef, $sValue, '', $sInputId, '', $iFlags, + $aArgs).''); } else { @@ -559,7 +581,7 @@ EOF } $oPage->SetCurrentTab(''); - foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) + foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { $oExtensionInstance->OnDisplayRelations($this, $oPage, $bEditMode); } @@ -572,9 +594,9 @@ EOF // $oTriggerSet = new CMDBObjectSet(new DBObjectSearch('Trigger')); $aTriggers = array(); - while($oTrigger = $oTriggerSet->Fetch()) + while ($oTrigger = $oTriggerSet->Fetch()) { - if($oTrigger->IsInScope($this)) + if ($oTrigger->IsInScope($this)) { $aTriggers[] = $oTrigger->GetKey(); } @@ -590,12 +612,12 @@ EOF { $aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN ($sTriggersList) AND Ev.object_id = $iId"); $oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass]); - $iNotifsCount += $oNotifSet->Count(); + $iNotifsCount += $oNotifSet->Count(); } // Display notifications regarding the object: on block per subclass to have the intersting columns $sCount = ($iNotifsCount > 0) ? ' ('.$iNotifsCount.')' : ''; $oPage->SetCurrentTab(Dict::S('UI:NotificationsTab').$sCount); - + foreach($aNotificationClasses as $sNotifClass) { $oPage->p(MetaModel::GetClassIcon($sNotifClass, true).' '.MetaModel::GetName($sNotifClass)); @@ -614,7 +636,8 @@ EOF $aDetails = array(); $sClass = get_class($this); $aDetailsList = MetaModel::GetZListItems($sClass, 'details'); - $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', 'col1', ''); + $aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', + 'col1', ''); // Compute the list of properties to display, first the attributes in the 'details' list, then // all the remaining attributes that are not external fields $sHtml = ''; @@ -649,7 +672,8 @@ EOF } $oPage->SetCurrentTab(Dict::S($sTab)); - $oPage->add(''); + $oPage->add('
'); foreach($aCols as $sColIndex => $aFieldsets) { $oPage->add(''; // Format kept as-is for 100% backward compatibility of the exports - $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports + $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports + $aRow[] = ''; // Format kept as-is for 100% backward compatibility of the exports } } - else if($oAttDef instanceof AttributeCaseLog) - { - $rawValue = $oObj->Get($sAttCodeEx); - $outputValue = str_replace("\n", "
", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); - // Trick for Excel: treat the content as text even if it begins with an equal sign - $aRow[] = ''; - } else { - $rawValue = $oObj->Get($sAttCodeEx); - // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings - // let's fix this and make sure we render an empty string if the key == 0 - if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) + if ($oAttDef instanceof AttributeCaseLog) { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if ($oObj->Get($sKeyAttCode) == 0) - { - $rawValue = ''; - } - } - if ($bLocalize) - { - $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8'); + $rawValue = $oObj->Get($sAttCodeEx); + $outputValue = str_replace("\n", "
", + htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8')); + // Trick for Excel: treat the content as text even if it begins with an equal sign + $aRow[] = ''; } else { - $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + $rawValue = $oObj->Get($sAttCodeEx); + // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings + // let's fix this and make sure we render an empty string if the key == 0 + if ($oAttDef instanceof AttributeExternalField && $oAttDef->IsFriendlyName()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if ($oObj->Get($sKeyAttCode) == 0) + { + $rawValue = ''; + } + } + if ($bLocalize) + { + $outputValue = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, + 'UTF-8'); + } + else + { + $outputValue = htmlentities($rawValue, ENT_QUOTES, 'UTF-8'); + } + $aRow[] = ''; } - $aRow[] = ''; } } } @@ -1460,16 +1531,16 @@ EOF $sHtml .= "\n"; } $sHtml .= "
'); @@ -706,7 +730,13 @@ EOF { // State attribute is always read-only from the UI $sHTMLValue = $this->GetStateLabel(); - $val = array('label' => '', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode); + $val = array( + 'label' => '', + 'value' => $sHTMLValue, + 'comments' => $sComments, + 'infos' => $sInfos, + 'attcode' => $sAttCode + ); } else { @@ -721,8 +751,10 @@ EOF $sTip = ''; foreach($aReasons as $aRow) { - $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); - $sDescription = str_replace(array("\r\n", "\n"), "
", $sDescription); + $sDescription = htmlentities($aRow['description'], ENT_QUOTES, + 'UTF-8'); + $sDescription = str_replace(array("\r\n", "\n"), "
", + $sDescription); $sTip .= "
"; $sTip .= "
Synchronized with {$aRow['name']}
"; $sTip .= "
$sDescription
"; @@ -740,10 +772,18 @@ EOF $sValue = $this->Get($sAttCode); $sDisplayValue = $this->GetEditValue($sAttCode); $aArgs = array('this' => $this, 'formPrefix' => $sPrefix); - $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).''; + $sHTMLValue = "".self::GetFormElementForField($oPage, $sClass, $sAttCode, + $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, + $aArgs).''; } $aFieldsMap[$sAttCode] = $sInputId; - $val = array('label' => ''.$oAttDef->GetLabel().'', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos, 'attcode' => $sAttCode); + $val = array( + 'label' => ''.$oAttDef->GetLabel().'', + 'value' => $sHTMLValue, + 'comments' => $sComments, + 'infos' => $sInfos, + 'attcode' => $sAttCode + ); } } else @@ -761,7 +801,8 @@ EOF // Checking how the field should be rendered // Note: For view mode, this is done in cmdbAbstractObject::GetFieldAsHtml() // Note 2: Shouldn't this be a property of the AttDef instead an array that we have to maintain? - if (in_array($oAttDef->GetEditClass(), array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) + if (in_array($oAttDef->GetEditClass(), + array('Text', 'HTML', 'CaseLog', 'CustomFields', 'OQLExpression'))) { $val['layout'] = 'large'; } @@ -808,7 +849,7 @@ EOF return $aFieldsMap; } - + function DisplayDetails(WebPage $oPage, $bEditMode = false) { $sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate(get_class($this))); @@ -817,7 +858,13 @@ EOF $oTemplate = new DisplayTemplate($sTemplate); // Note: to preserve backward compatibility with home-made templates, the placeholder '$pkey$' has been preserved // but the preferred method is to use '$id$' - $oTemplate->Render($oPage, array('class_name'=> MetaModel::GetName(get_class($this)),'class'=> get_class($this), 'pkey'=> $this->GetKey(), 'id'=> $this->GetKey(), 'name' => $this->GetName())); + $oTemplate->Render($oPage, array( + 'class_name' => MetaModel::GetName(get_class($this)), + 'class' => get_class($this), + 'pkey' => $this->GetKey(), + 'id' => $this->GetKey(), + 'name' => $this->GetName() + )); } else { @@ -832,11 +879,12 @@ EOF $this->DisplayBareRelations($oPage, $bEditMode); //$oPage->SetCurrentTab(Dict::S('UI:HistoryTab')); //$this->DisplayBareHistory($oPage, $bEditMode); - $oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey()); + $oPage->AddAjaxTab(Dict::S('UI:HistoryTab'), + utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=history&class='.get_class($this).'&id='.$this->GetKey()); $oPage->add('
'); } } - + function DisplayPreview(WebPage $oPage) { $aDetails = array(); @@ -844,45 +892,51 @@ EOF $aList = MetaModel::GetZListItems($sClass, 'preview'); foreach($aList as $sAttCode) { - $aDetails[] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'value' =>$this->GetAsHTML($sAttCode)); + $aDetails[] = array( + 'label' => MetaModel::GetLabel($sClass, $sAttCode), + 'value' => $this->GetAsHTML($sAttCode) + ); } - $oPage->details($aDetails); + $oPage->details($aDetails); } - + public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { $oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams)); } - + /** * Simplifed version of GetDisplaySet() with less "decoration" around the table (and no paging) * that fits better into a printed document (like a PDF or a printable view) + * * @param WebPage $oPage * @param DBObjectSet $oSet - * @param hash $aExtraParams + * @param array $aExtraParams + * * @return string The HTML representation of the table */ public static function GetDisplaySetForPrinting(WebPage $oPage, DBObjectSet $oSet, $aExtraParams = array()) { $iListId = empty($aExtraParams['currentId']) ? $oPage->GetUniqueId() : $aExtraParams['currentId']; $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - + $bViewLink = true; $sSelectMode = 'none'; $iListId = $sTableId; $sClassAlias = $oSet->GetClassAlias(); $sClassName = $oSet->GetClass(); $sZListName = 'list'; - $aClassAliases = array( $sClassAlias => $sClassName); + $aClassAliases = array($sClassAlias => $sClassName); $aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - + $oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId); $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); $oSettings->iDefaultPageSize = 0; $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - - return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, $aExtraParams); - + + return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, + $aExtraParams); + } /** @@ -892,10 +946,11 @@ EOF * @param CMDBObjectSet The set of objects to display * @param array $aExtraParams Some extra configuration parameters to tweak the behavior of the display * - * @return String The HTML fragment representing the table of objects. Warning : no JS added to handled pagination or table sorting ! + * @return String The HTML fragment representing the table of objects. Warning : no JS added to handled + * pagination or table sorting ! * * @see DisplayBlock to get a similar table but with the JS for pagination & sorting - */ + */ public static function GetDisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { if ($oPage->IsPrintableVersion() || $oPage->is_pdf()) @@ -911,7 +966,7 @@ EOF { $iListId = $aExtraParams['currentId']; } - + // Initialize and check the parameters $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; $sLinkageAttribute = isset($aExtraParams['link_attr']) ? $aExtraParams['link_attr'] : ''; @@ -919,12 +974,12 @@ EOF $sTargetAttr = isset($aExtraParams['target_attr']) ? $aExtraParams['target_attr'] : ''; if (!empty($sLinkageAttribute)) { - if($iLinkedObjectId == 0) + if ($iLinkedObjectId == 0) { // if 'links' mode is requested the id of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_object_id')); } - if($sTargetAttr == '') + if ($sTargetAttr == '') { // if 'links' mode is requested the d of the object to link to must be specified throw new ApplicationException(Dict::S('UI:Error:MandatoryTemplateParameter_target_attr')); @@ -935,9 +990,10 @@ EOF $bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false; $bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false; - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); $aExtraFields = array(); - foreach ($aExtraFieldsRaw as $sFieldName) + foreach($aExtraFieldsRaw as $sFieldName) { // Ignore attributes not of the main queried class if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) @@ -995,7 +1051,7 @@ EOF foreach($aList as $sLinkAttCode) { $oLinkAttDef = $aAttDefs[$sLinkAttCode]; - if ( (!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField()) ) + if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) { $aDisplayList[] = $sLinkAttCode; } @@ -1005,7 +1061,7 @@ EOF { $oLinkAttDef = $aAttDefs[$sLinkAttCode]; if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute)) - || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode()!=$sLinkageAttribute)) ) + || ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) { $aDisplayList[] = $sLinkAttCode; } @@ -1014,24 +1070,25 @@ EOF // Then display all the attributes linked to the other end of the relationship $aList = $aDisplayList; } - + $sSelectMode = 'none'; if ($bSelectMode) { $sSelectMode = $bSingleSelectMode ? 'single' : 'multiple'; } - + $sClassAlias = $oSet->GetClassAlias(); $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; - + $sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null; - $aClassAliases = array( $sClassAlias => $sClassName); + $aClassAliases = array($sClassAlias => $sClassName); $oDataTable = new DataTable($iListId, $oSet, $aClassAliases, $sTableId); $oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList)); - + if ($bDisplayLimit) { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', + MetaModel::GetConfig()->GetMinDisplayLimit()); $oSettings->iDefaultPageSize = $iDefaultPageSize; } else @@ -1039,10 +1096,10 @@ EOF $oSettings->iDefaultPageSize = 0; } $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - + return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); } - + public static function GetDisplayExtendedSet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array()) { if (empty($aExtraParams['currentId'])) @@ -1054,18 +1111,20 @@ EOF $iListId = $aExtraParams['currentId']; } $aList = array(); - + // Initialize and check the parameters $bViewLink = isset($aExtraParams['view_link']) ? $aExtraParams['view_link'] : true; $bDisplayMenu = isset($aExtraParams['menu']) ? $aExtraParams['menu'] == true : true; // Check if there is a list of aliases to limit the display to... - $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', $aExtraParams['display_aliases']) : array(); + $aDisplayAliases = isset($aExtraParams['display_aliases']) ? explode(',', + $aExtraParams['display_aliases']) : array(); $sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list'; - $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', trim($aExtraParams['extra_fields'])) : array(); + $aExtraFieldsRaw = isset($aExtraParams['extra_fields']) ? explode(',', + trim($aExtraParams['extra_fields'])) : array(); $aExtraFields = array(); $sAttCode = ''; - foreach ($aExtraFieldsRaw as $sFieldName) + foreach($aExtraFieldsRaw as $sFieldName) { // Ignore attributes not of the main queried class if (preg_match('/^(.*)\.(.*)$/', $sFieldName, $aMatches)) @@ -1089,8 +1148,8 @@ EOF $aAuthorizedClasses = array(); foreach($aClasses as $sAlias => $sClassName) { - if ( (UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && - ( (count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases))) ) + if ((UserRights::IsActionAllowed($sClassName, UR_ACTION_READ, $oSet) != UR_ALLOWED_NO) && + ((count($aDisplayAliases) == 0) || (in_array($sAlias, $aDisplayAliases)))) { $aAuthorizedClasses[$sAlias] = $sClassName; } @@ -1109,10 +1168,10 @@ EOF if ($sZListName !== false) { $aDefaultList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName)); - + $aList[$sAlias] = array_merge($aDefaultList, $aList[$sAlias]); } - + // Filter the list to removed linked set since we are not able to display them here foreach($aList[$sAlias] as $index => $sAttCode) { @@ -1122,33 +1181,34 @@ EOF // Removed from the display list unset($aList[$sAlias][$index]); } - } + } } $sSelectMode = 'none'; - + $sClassAlias = $oSet->GetClassAlias(); $oDataTable = new DataTable($iListId, $oSet, $aAuthorizedClasses); $oSettings = DataTableSettings::GetDataModelSettings($aAuthorizedClasses, $bViewLink, $aList); - + $bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true; if ($bDisplayLimit) { - $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit()); + $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', + MetaModel::GetConfig()->GetMinDisplayLimit()); $oSettings->iDefaultPageSize = $iDefaultPageSize; } - + $oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName); - + return $oDataTable->Display($oPage, $oSettings, $bDisplayMenu, $sSelectMode, $bViewLink, $aExtraParams); } - + static function DisplaySetAsCSV(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') { $oPage->add(self::GetSetAsCSV($oSet, $aParams, $sCharset)); } - + static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array(), $sCharset = 'UTF-8') { $sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma @@ -1162,13 +1222,13 @@ EOF $bFieldsAdvanced = false; if (isset($aParams['fields_advanced'])) { - $bFieldsAdvanced = (bool) $aParams['fields_advanced']; + $bFieldsAdvanced = (bool)$aParams['fields_advanced']; } $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $aList = array(); @@ -1197,7 +1257,7 @@ EOF if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; - + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) { if ($bFieldsAdvanced) @@ -1206,11 +1266,12 @@ EOF if ($oAttDef->IsExternalKey(EXTKEY_RELATIVE)) { - $sRemoteClass = $oAttDef->GetTargetClass(); + $sRemoteClass = $oAttDef->GetTargetClass(); foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) - { - $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); - } + { + $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, + $sRemoteAttCode); + } } } } @@ -1236,7 +1297,8 @@ EOF } foreach($aList[$sAlias] as $sAttCodeEx => $oAttDef) { - $aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, isset($aParams['showMandatoryFields'])) : $sAttCodeEx; + $aHeader[] = $bLocalize ? MetaModel::GetLabel($sClassName, $sAttCodeEx, + isset($aParams['showMandatoryFields'])) : $sAttCodeEx; } } $sHtml = implode($sSeparator, $aHeader)."\n"; @@ -1274,19 +1336,19 @@ EOF } $sHtml .= implode($sSeparator, $aRow)."\n"; } - + return $sHtml; } - + static function DisplaySetAsHTMLSpreadsheet(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $oPage->add(self::GetSetAsHTMLSpreadsheet($oSet, $aParams)); } - + /** * Spreadsheet output: designed for end users doing some reporting * Then the ids are excluded and replaced by the corresponding friendlyname - */ + */ static function GetSetAsHTMLSpreadsheet(DBObjectSet $oSet, $aParams = array()) { $aFields = null; @@ -1298,13 +1360,13 @@ EOF $bFieldsAdvanced = false; if (isset($aParams['fields_advanced'])) { - $bFieldsAdvanced = (bool) $aParams['fields_advanced']; + $bFieldsAdvanced = (bool)$aParams['fields_advanced']; } $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $aList = array(); @@ -1333,16 +1395,17 @@ EOF if ($oAttDef->IsScalar() && ($oAttDef->IsWritable() || $oAttDef->IsExternalField())) { $sAttCodeEx = $oAttDef->IsExternalField() ? $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode() : $sAttCode; - + $aList[$sAlias][$sAttCodeEx] = $oAttDef; - if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) - { - $sRemoteClass = $oAttDef->GetTargetClass(); + if ($bFieldsAdvanced && $oAttDef->IsExternalKey(EXTKEY_RELATIVE)) + { + $sRemoteClass = $oAttDef->GetTargetClass(); foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) - { - $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); - } + { + $aList[$sAlias][$sAttCode.'->'.$sRemoteAttCode] = MetaModel::GetAttributeDef($sRemoteClass, + $sRemoteAttCode); + } } } } @@ -1362,7 +1425,8 @@ EOF { unset($aList[$sAlias][$sAttCode]); $sFriendlyNameAttCode = $sAttCode.'_friendlyname'; - if (!array_key_exists($sFriendlyNameAttCode, $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) + if (!array_key_exists($sFriendlyNameAttCode, + $aList[$sAlias]) && MetaModel::IsValidAttCode($sClassName, $sFriendlyNameAttCode)) { $oFriendlyNameAtt = MetaModel::GetAttributeDef($sClassName, $sFriendlyNameAttCode); $aList[$sAlias][$sFriendlyNameAttCode] = $oFriendlyNameAtt; @@ -1419,39 +1483,46 @@ EOF else { $iDate = AttributeDateTime::GetAsUnixSeconds($sDate); - $aRow[] = '
'.date('Y-m-d', $iDate).''.date('H:i:s', $iDate).''.date('Y-m-d', + $iDate).''.date('H:i:s', + $iDate).''.$outputValue.''.$outputValue.''.$outputValue.''.$outputValue.'
\n"; - + return $sHtml; } - + static function DisplaySetAsXML(WebPage $oPage, CMDBObjectSet $oSet, $aParams = array()) { $bLocalize = true; if (isset($aParams['localize_values'])) { - $bLocalize = (bool) $aParams['localize_values']; + $bLocalize = (bool)$aParams['localize_values']; } $oAppContext = new ApplicationContext(); @@ -1491,7 +1562,7 @@ EOF { if (count($aAuthorizedClasses) > 1) { - $oPage->add("\n"); + $oPage->add("\n"); } foreach($aAuthorizedClasses as $sAlias => $sClassName) { @@ -1505,7 +1576,7 @@ EOF $sClassName = get_class($oObj); $oPage->add("<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n"); } - foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef) + foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) { if (is_null($oObj)) { @@ -1527,7 +1598,7 @@ EOF } if (count($aAuthorizedClasses) > 1) { - $oPage->add("\n"); + $oPage->add("\n"); } } $oPage->add("\n"); @@ -1567,10 +1638,13 @@ EOF * @param int $iFlags * @param array $aArgs * @param bool $bPreserveCurrentValue Preserve the current value even if not allowed + * * @return string */ - public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true) - { + public static function GetFormElementForField( + $oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '', $sNameSuffix = '', + $iFlags = 0, $aArgs = array(), $bPreserveCurrentValue = true + ) { static $iInputId = 0; $sFieldPrefix = ''; $sFormPrefix = isset($aArgs['formPrefix']) ? $aArgs['formPrefix'] : ''; @@ -1599,7 +1673,7 @@ EOF if (!$oAttDef->IsExternalField()) { $bMandatory = 'false'; - if ( (!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) + if ((!$oAttDef->IsNullAllowed()) || ($iFlags & OPT_ATT_MANDATORY)) { $bMandatory = 'true'; } @@ -1607,55 +1681,62 @@ EOF $sReloadSpan = ""; $sHelpText = htmlentities($oAttDef->GetHelpOnEdition(), ENT_QUOTES, 'UTF-8'); $aEventsList = array(); - switch($oAttDef->GetEditClass()) + switch ($oAttDef->GetEditClass()) { case 'Date': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; + $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDate::GetFormat()->ToPlaceholder(), + ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; case 'DateTime': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; - $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), ENT_QUOTES, 'UTF-8').'"'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; + $sPlaceholderValue = 'placeholder="'.htmlentities(AttributeDateTime::GetFormat()->ToPlaceholder(), + ENT_QUOTES, 'UTF-8').'"'; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; case 'Duration': - $aEventsList[] ='validate'; - $aEventsList[] ='change'; - $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); - $aVal = AttributeDuration::SplitDuration($value); - $sDays = ""; - $sHours = ""; - $sMinutes = ""; - $sSeconds = ""; - $sHidden = ""; - $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; - $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); - break; - + $aEventsList[] = 'validate'; + $aEventsList[] = 'change'; + $oPage->add_ready_script("$('#{$iId}_d').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_h').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_m').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $oPage->add_ready_script("$('#{$iId}_s').bind('keyup change', function(evt, sFormId) { return UpdateDuration('$iId'); });"); + $aVal = AttributeDuration::SplitDuration($value); + $sDays = ""; + $sHours = ""; + $sMinutes = ""; + $sSeconds = ""; + $sHidden = ""; + $sHTMLValue = Dict::Format('UI:DurationForm_Days_Hours_Minutes_Seconds', $sDays, $sHours, $sMinutes, + $sSeconds).$sHidden." ".$sValidationSpan.$sReloadSpan; + $oPage->add_ready_script("$('#{$iId}').bind('update', function(evt, sFormId) { return ToggleDurationField('$iId'); });"); + break; + case 'Password': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; - $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; - break; - + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; + $sHTMLValue = "
{$sValidationSpan}{$sReloadSpan}"; + break; + case 'OQLExpression': case 'Text': - $aEventsList[] ='validate'; - $aEventsList[] ='keyup'; - $aEventsList[] ='change'; + $aEventsList[] = 'validate'; + $aEventsList[] = 'keyup'; + $aEventsList[] = 'change'; $sEditValue = $oAttDef->GetEditValue($value); $aStyles = array(); @@ -1688,10 +1769,11 @@ EOF $sAdditionalStuff = ""; } // Ok, the text area is drawn here - $sHTMLValue = "
$sAdditionalStuff
{$sValidationSpan}{$sReloadSpan}"; + $sHTMLValue = "
$sAdditionalStuff
{$sValidationSpan}{$sReloadSpan}"; - $oPage->add_ready_script( -<<add_ready_script( + <<