mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-05 00:54:12 +01:00
Compare commits
121 Commits
issue/6716
...
feature/69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e844b4c3ce | ||
|
|
3eee03d504 | ||
|
|
eaa80c5396 | ||
|
|
3aec6bff79 | ||
|
|
83313ce1d5 | ||
|
|
8aa3dcdaa7 | ||
|
|
306d8136ef | ||
|
|
cbb37f27d7 | ||
|
|
e78fa18359 | ||
|
|
1385a3dc03 | ||
|
|
789f0c826b | ||
|
|
ac070b0cbe | ||
|
|
e21dc4d21c | ||
|
|
a49a4e6c2b | ||
|
|
95aa4afe75 | ||
|
|
74004fa375 | ||
|
|
d2fc87c6f9 | ||
|
|
5264a1f10d | ||
|
|
f0199a4cf2 | ||
|
|
f8877ef3e7 | ||
|
|
00b1156526 | ||
|
|
39d2ba8d1b | ||
|
|
322adcb180 | ||
|
|
77e7685c90 | ||
|
|
3c14e5e032 | ||
|
|
f58ec7e38e | ||
|
|
73fd0b06b2 | ||
|
|
8fa9336568 | ||
|
|
15148f7d1d | ||
|
|
7e8589ba95 | ||
|
|
fba668207f | ||
|
|
798cd10d6b | ||
|
|
442721bcb5 | ||
|
|
1a9049d277 | ||
|
|
c0931af91a | ||
|
|
29e9a06dc1 | ||
|
|
d6415042ae | ||
|
|
90006667fe | ||
|
|
fd351df08b | ||
|
|
7419749ba6 | ||
|
|
73bed04555 | ||
|
|
8893cdac1d | ||
|
|
7f245a15be | ||
|
|
b5c46ccd4a | ||
|
|
7fbc211c43 | ||
|
|
cf774cdb90 | ||
|
|
722a58491c | ||
|
|
037dfe1df6 | ||
|
|
0b26d45014 | ||
|
|
44e826543d | ||
|
|
50f5ab6be4 | ||
|
|
941412a365 | ||
|
|
a9bd62dc43 | ||
|
|
4f336abeb8 | ||
|
|
40d1ae0b1c | ||
|
|
b9c566238a | ||
|
|
4fd8177165 | ||
|
|
c597c34e5c | ||
|
|
0cc0f39d9e | ||
|
|
a2cdf214f0 | ||
|
|
189fd1d9e3 | ||
|
|
4f75d012e5 | ||
|
|
013173019f | ||
|
|
ed8d4df5ef | ||
|
|
31a1370028 | ||
|
|
f36f3aa05b | ||
|
|
d0d90d7c69 | ||
|
|
aa618468d1 | ||
|
|
fadfd94bac | ||
|
|
9469681a0c | ||
|
|
d4ab55dd9a | ||
|
|
da27ddba82 | ||
|
|
c72cb7e70e | ||
|
|
9df92665e0 | ||
|
|
819baa3951 | ||
|
|
c78024394e | ||
|
|
4267f2b855 | ||
|
|
ba8f18e1d4 | ||
|
|
ba13d24206 | ||
|
|
239c51bb53 | ||
|
|
ab3a4a2468 | ||
|
|
3647291475 | ||
|
|
a472d83e3d | ||
|
|
6dc6392fab | ||
|
|
e793b02f8b | ||
|
|
c5cb84f976 | ||
|
|
12c0edc530 | ||
|
|
61565b25a3 | ||
|
|
7619d055dd | ||
|
|
f01997f2ad | ||
|
|
fc6e98b534 | ||
|
|
8ecebee511 | ||
|
|
b5e26061e1 | ||
|
|
14fa20b428 | ||
|
|
2690fa3315 | ||
|
|
35cd965360 | ||
|
|
133fc29ad5 | ||
|
|
83a5b98f82 | ||
|
|
e5dd51f637 | ||
|
|
2f6bcc3534 | ||
|
|
4923418f58 | ||
|
|
0a6c82dfe1 | ||
|
|
f89d843ab3 | ||
|
|
2dd7f5cada | ||
|
|
24c0f4950f | ||
|
|
d4dbbc59d4 | ||
|
|
dc0cd44c79 | ||
|
|
d7df249586 | ||
|
|
f3c4fcb0f5 | ||
|
|
9a8e9a0b01 | ||
|
|
f6a7e6b4e1 | ||
|
|
2829fb291f | ||
|
|
6110abfc7f | ||
|
|
6046f44f56 | ||
|
|
6c6131ce03 | ||
|
|
178c922407 | ||
|
|
343e87a8d4 | ||
|
|
d7e5d6fb7f | ||
|
|
993606f102 | ||
|
|
bdf0b4daa9 | ||
|
|
cc4af0a027 |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -37,7 +37,9 @@ tests/*/vendor/*
|
||||
|
||||
# iTop extensions
|
||||
/extensions/**
|
||||
!/extensions/.htaccess
|
||||
!/extensions/readme.txt
|
||||
!/extensions/web.config
|
||||
|
||||
# all logs but listing prevention
|
||||
/log/**
|
||||
@@ -45,8 +47,10 @@ tests/*/vendor/*
|
||||
!/log/index.php
|
||||
!/log/web.config
|
||||
|
||||
# PHPUnit cache file
|
||||
# PHPUnit: Cache file, local XML working copies
|
||||
/tests/php-unit-tests/.phpunit.result.cache
|
||||
/tests/php-unit-tests/phpunit.xml
|
||||
/tests/php-unit-tests/postbuild_integration.xml
|
||||
|
||||
|
||||
# Jetbrains
|
||||
|
||||
@@ -161,4 +161,4 @@ We have one sticker per contribution type. You might get multiple stickers with
|
||||
|
||||
Here is the design of each stickers for year 2022:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -502,6 +502,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
}
|
||||
|
||||
protected $m_aUserOrgs = array(); // userid -> array of orgid
|
||||
protected $m_aAdministrators = null; // [user id]
|
||||
|
||||
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
|
||||
protected $m_aObjectActionGrants = array();
|
||||
@@ -558,6 +559,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
|
||||
// Cache
|
||||
$this->m_aObjectActionGrants = array();
|
||||
$this->m_aAdministrators = null;
|
||||
}
|
||||
|
||||
public function LoadCache()
|
||||
@@ -700,12 +702,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
*/
|
||||
private function GetAdministrators()
|
||||
{
|
||||
static $aAdministrators = null;
|
||||
|
||||
if ($aAdministrators === null)
|
||||
if ($this->m_aAdministrators === null)
|
||||
{
|
||||
// Find all administrators
|
||||
$aAdministrators = array();
|
||||
$this->m_aAdministrators = array();
|
||||
$oAdministratorsFilter = new DBObjectSearch('User');
|
||||
$oLnkFilter = new DBObjectSearch('URP_UserProfile');
|
||||
$oExpression = new FieldExpression('profileid', 'URP_UserProfile');
|
||||
@@ -718,10 +718,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$oSet->OptimizeColumnLoad(array('User' => array('login')));
|
||||
while($oUser = $oSet->Fetch())
|
||||
{
|
||||
$aAdministrators[] = $oUser->GetKey();
|
||||
$this->m_aAdministrators[] = $oUser->GetKey();
|
||||
}
|
||||
}
|
||||
return $aAdministrators;
|
||||
return $this->m_aAdministrators;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/AjaxPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/AjaxPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CaptureWebPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CaptureWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CLIPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CLIPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -744,7 +744,11 @@ HTML
|
||||
$oPage->SetCurrentTab($sTabCode, $oAttDef->GetLabel().$sCount, $sTabDescription);
|
||||
|
||||
$aArgs = array('this' => $this);
|
||||
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE));
|
||||
|
||||
$sEditWhen = $oAttDef->GetEditWhen();
|
||||
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS || $sEditWhen === LINKSET_EDITWHEN_ON_HOST_EDITION);
|
||||
|
||||
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) || !$bIsEditableBasedOnEditWhen;
|
||||
if ($bEditMode && (!$bReadOnly)) {
|
||||
$sInputId = $this->m_iFormId.'_'.$sAttCode;
|
||||
$sDisplayValue = ''; // not used
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CSVPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CSVPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -918,7 +918,7 @@ class RuntimeDashboard extends Dashboard
|
||||
{
|
||||
$bCustomized = false;
|
||||
|
||||
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
|
||||
$sDashboardFileSanitized = utils::RealPath(APPROOT.$sDashboardFile, APPROOT);
|
||||
if (false === $sDashboardFileSanitized) {
|
||||
throw new SecurityException('Invalid dashboard file !');
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ JS
|
||||
$oToolbar->AddSubBlock($oActionButton);
|
||||
|
||||
$aActions = array();
|
||||
$sFile = addslashes($this->sDefinitionFile);
|
||||
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
|
||||
$sJSExtraParams = json_encode($aExtraParams);
|
||||
if ($this->HasCustomDashboard()) {
|
||||
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
|
||||
@@ -1264,12 +1264,12 @@ EOF
|
||||
$sOkButtonLabel = Dict::S('UI:Button:Save');
|
||||
$sCancelButtonLabel = Dict::S('UI:Button:Cancel');
|
||||
|
||||
$sId = addslashes($this->sId);
|
||||
$sLayoutClass = addslashes($this->sLayoutClass);
|
||||
$sId = utils::HtmlEntities($this->sId);
|
||||
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass);
|
||||
$sAutoReload = $this->bAutoReload ? 'true' : 'false';
|
||||
$sAutoReloadSec = (string) $this->iAutoReloadSec;
|
||||
$sTitle = addslashes($this->sTitle);
|
||||
$sFile = addslashes($this->GetDefinitionFile());
|
||||
$sTitle = utils::HtmlEntities($this->sTitle);
|
||||
$sFile = utils::HtmlEntities($this->GetDefinitionFile());
|
||||
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
|
||||
$sReloadURL = $this->GetReloadURL();
|
||||
|
||||
|
||||
@@ -2042,7 +2042,7 @@ class MenuBlock extends DisplayBlock
|
||||
}
|
||||
|
||||
//----------------------------------------------------
|
||||
// Any style but NOT "listInObject" (linksets) actions
|
||||
// Any style but NOT \DisplayBlock::ENUM_STYLE_LIST_IN_OBJECT (linksets) actions
|
||||
//----------------------------------------------------
|
||||
if ($this->m_sStyle !== static::ENUM_STYLE_LIST_IN_OBJECT) {
|
||||
switch ($iSetCount) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/ErrorPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/ErrorPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWebPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWizardWebPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWizardWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
@@ -38,7 +38,7 @@ class LoginWebPage extends NiceWebPage
|
||||
const EXIT_PROMPT = 0;
|
||||
const EXIT_HTTP_401 = 1;
|
||||
const EXIT_RETURN = 2;
|
||||
|
||||
|
||||
const EXIT_CODE_OK = 0;
|
||||
const EXIT_CODE_MISSINGLOGIN = 1;
|
||||
const EXIT_CODE_MISSINGPASSWORD = 2;
|
||||
@@ -80,7 +80,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
|
||||
protected static $m_sLoginFailedMessage = '';
|
||||
|
||||
|
||||
public function __construct($sTitle = null)
|
||||
{
|
||||
if ($sTitle === null) {
|
||||
@@ -92,7 +92,7 @@ class LoginWebPage extends NiceWebPage
|
||||
$this->no_cache();
|
||||
$this->add_xframe_options();
|
||||
}
|
||||
|
||||
|
||||
public function SetStyleSheet()
|
||||
{
|
||||
$this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css');
|
||||
@@ -867,12 +867,12 @@ class LoginWebPage extends NiceWebPage
|
||||
* @api
|
||||
*
|
||||
* @param string $sAuthUser
|
||||
* @param Person $oPerson
|
||||
* @param Person|null $oPerson
|
||||
* @param array $aRequestedProfiles profiles to add to the new user
|
||||
*
|
||||
* @return \UserExternal|null
|
||||
*/
|
||||
public static function ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles)
|
||||
public static function ProvisionUser(string $sAuthUser, ?Person $oPerson, array $aRequestedProfiles)
|
||||
{
|
||||
if (!MetaModel::IsValidClass('URP_Profiles'))
|
||||
{
|
||||
@@ -897,7 +897,9 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
$oUser = MetaModel::NewObject('UserExternal');
|
||||
$oUser->Set('login', $sAuthUser);
|
||||
$oUser->Set('contactid', $oPerson->GetKey());
|
||||
if (! is_null($oPerson)){
|
||||
$oUser->Set('contactid', $oPerson->GetKey());
|
||||
}
|
||||
$oUser->Set('language', MetaModel::GetConfig()->GetDefaultLanguage());
|
||||
}
|
||||
|
||||
@@ -949,7 +951,7 @@ class LoginWebPage extends NiceWebPage
|
||||
* Overridable: depending on the user, head toward a dedicated portal
|
||||
* @param string|null $sRequestedPortalId
|
||||
* @param int $iOnExit How to complete the call: redirect or return a code
|
||||
*/
|
||||
*/
|
||||
protected static function ChangeLocation($sRequestedPortalId = null, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
$ret = call_user_func(array(self::$sHandlerClass, 'Dispatch'), $sRequestedPortalId);
|
||||
@@ -1010,9 +1012,9 @@ class LoginWebPage extends NiceWebPage
|
||||
static function DoLoginEx($sRequestedPortalId = null, $bMustBeAdmin = false, $iOnExit = self::EXIT_PROMPT)
|
||||
{
|
||||
$operation = utils::ReadParam('loginop', '');
|
||||
|
||||
|
||||
$sMessage = self::HandleOperations($operation); // May exit directly
|
||||
|
||||
|
||||
$iRet = self::Login($iOnExit);
|
||||
if ($iRet == self::EXIT_CODE_OK)
|
||||
{
|
||||
@@ -1042,7 +1044,7 @@ class LoginWebPage extends NiceWebPage
|
||||
{
|
||||
return $sMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected static function HandleOperations($operation)
|
||||
{
|
||||
$sMessage = ''; // most of the operations never return, but some can return a message to be displayed
|
||||
@@ -1156,11 +1158,11 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
return $sMessage;
|
||||
}
|
||||
|
||||
|
||||
protected static function Dispatch($sRequestedPortalId)
|
||||
{
|
||||
if ($sRequestedPortalId === null) return true; // allowed to any portal => return true
|
||||
|
||||
|
||||
$aPortalsConf = PortalDispatcherData::GetData();
|
||||
$aDispatchers = array();
|
||||
foreach($aPortalsConf as $sPortalId => $aConf)
|
||||
@@ -1168,7 +1170,7 @@ class LoginWebPage extends NiceWebPage
|
||||
$sHandlerClass = $aConf['handler'];
|
||||
$aDispatchers[$sPortalId] = new $sHandlerClass($sPortalId);
|
||||
}
|
||||
|
||||
|
||||
if (array_key_exists($sRequestedPortalId, $aDispatchers) && $aDispatchers[$sRequestedPortalId]->IsUserAllowed())
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/NiceWebPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/NiceWebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/PDFPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/PDFPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -163,7 +163,7 @@ class UIExtKeyWidget
|
||||
$oPage->add_linked_script('../js/extkeywidget.js');
|
||||
$oPage->add_linked_script('../js/forms-json-utils.js');
|
||||
|
||||
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
|
||||
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_MODIFY) && $bAllowTargetCreation);
|
||||
$bExtensions = true;
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
||||
@@ -975,6 +975,10 @@ HTML
|
||||
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
|
||||
FormHelper::DisableAttributeBlobInputs($this->sTargetClass, $aFormExtraParams);
|
||||
|
||||
if(FormHelper::HasMandatoryAttributeBlobInputs($oNewObj)){
|
||||
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal());
|
||||
}
|
||||
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), $aFormExtraParams);
|
||||
$oPage->add(<<<HTML
|
||||
</div>
|
||||
|
||||
@@ -143,6 +143,10 @@ JS
|
||||
|
||||
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
|
||||
FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams);
|
||||
|
||||
if(FormHelper::HasMandatoryAttributeBlobInputs($oObj)){
|
||||
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal());
|
||||
}
|
||||
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aFormExtraParams);
|
||||
}
|
||||
|
||||
@@ -52,22 +52,31 @@ class utils
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
|
||||
/**
|
||||
* Datamodel class
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 update PHPDoc
|
||||
* @uses MetaModel::IsValidClass()
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606
|
||||
* @uses class_exists()
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_PHP_CLASS = 'php_class';
|
||||
/**
|
||||
* @var string
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_STRING = 'string';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
|
||||
/**
|
||||
@@ -82,22 +91,22 @@ class utils
|
||||
public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id';
|
||||
/**
|
||||
* @var string For XML / HTML node identifiers
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
|
||||
/**
|
||||
@@ -107,12 +116,13 @@ class utils
|
||||
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.0
|
||||
* @since 2.7.10 3.0.0
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.0.2, 3.1.0 N°4899
|
||||
* @since 3.0.2 3.1.0 N°4899
|
||||
* @since 2.7.10 N°6606
|
||||
*/
|
||||
public const ENUM_SANITIZATION_FILTER_URL = 'url';
|
||||
|
||||
@@ -155,6 +165,8 @@ class utils
|
||||
|
||||
private static $iNextId = 0;
|
||||
|
||||
private static $m_sAppRootUrl = null;
|
||||
|
||||
protected static function LoadParamFile($sParamFile)
|
||||
{
|
||||
if (!file_exists($sParamFile)) {
|
||||
@@ -396,6 +408,10 @@ class utils
|
||||
* @since 2.7.0 new 'element_identifier' filter
|
||||
* @since 3.0.0 new utils::ENUM_SANITIZATION_* const
|
||||
* @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
|
||||
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
|
||||
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
|
||||
*
|
||||
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
|
||||
*/
|
||||
protected static function Sanitize_Internal($value, $sSanitizationFilter)
|
||||
{
|
||||
@@ -416,6 +432,13 @@ class utils
|
||||
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_PHP_CLASS:
|
||||
$retValue = $value;
|
||||
if (!class_exists($value)) {
|
||||
$retValue = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
|
||||
case static::ENUM_SANITIZATION_FILTER_ROUTE:
|
||||
case static::ENUM_SANITIZATION_FILTER_OPERATION:
|
||||
@@ -481,6 +504,7 @@ class utils
|
||||
|
||||
// For URL
|
||||
case static::ENUM_SANITIZATION_FILTER_URL:
|
||||
// N°6350 - returns only valid URLs
|
||||
$retValue = filter_var($value, FILTER_VALIDATE_URL);
|
||||
break;
|
||||
|
||||
@@ -1024,7 +1048,7 @@ class utils
|
||||
*/
|
||||
public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false)
|
||||
{
|
||||
static $sUrl = null;
|
||||
$sUrl = static::$m_sAppRootUrl;
|
||||
if ($sUrl === null || $bForceTrustProxy)
|
||||
{
|
||||
$sUrl = self::GetConfig()->Get('app_root_url');
|
||||
@@ -1045,8 +1069,9 @@ class utils
|
||||
}
|
||||
$sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
|
||||
}
|
||||
static::$m_sAppRootUrl = $sUrl;
|
||||
}
|
||||
return $sUrl;
|
||||
return static::$m_sAppRootUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/WebPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/WebPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -351,6 +351,7 @@ class WizardHelper
|
||||
/**
|
||||
* @return string JS code to be executed for fields update
|
||||
* @since 3.0.0 N°3198
|
||||
* @deprecated 3.0.3-2 3.0.4 3.1.1 3.2.0 Use {@see \WizardHelper::AddJsForUpdateFields()} instead
|
||||
*/
|
||||
public function GetJsForUpdateFields()
|
||||
{
|
||||
@@ -363,6 +364,32 @@ class WizardHelper
|
||||
JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add necessary JS snippets (to the page) to be executed for fields update
|
||||
*
|
||||
* @param \WebPage $oPage
|
||||
* @return void
|
||||
* @since 3.0.3-2 3.0.4 3.1.1 3.2.0 N°6766
|
||||
*/
|
||||
public function AddJsForUpdateFields(WebPage $oPage)
|
||||
{
|
||||
$sWizardHelperJsVar = (!is_null($this->m_aData['m_sWizHelperJsVarName'])) ? utils::Sanitize($this->m_aData['m_sWizHelperJsVarName'], '', utils::ENUM_SANITIZATION_FILTER_PARAMETER) : 'oWizardHelper'.$this->GetFormPrefix();
|
||||
$sWizardHelperJson = $this->ToJSON();
|
||||
|
||||
$oPage->add_script(<<<JS
|
||||
{$sWizardHelperJsVar}.m_oData = {$sWizardHelperJson};
|
||||
{$sWizardHelperJsVar}.UpdateFields();
|
||||
JS
|
||||
);
|
||||
$oPage->add_ready_script(<<<JS
|
||||
if ({$sWizardHelperJsVar}.m_oDependenciesUpdatedPromiseResolve !== null){
|
||||
{$sWizardHelperJsVar}.m_oDependenciesUpdatedPromiseResolve();
|
||||
}
|
||||
JS
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Function with an old pattern of code
|
||||
* @deprecated 3.1.0
|
||||
@@ -371,11 +398,9 @@ JS;
|
||||
{
|
||||
$aSet = json_decode($sJsonSet, true); // true means hash array instead of object
|
||||
$oSet = CMDBObjectSet::FromScratch($sLinkClass);
|
||||
foreach ($aSet as $aLinkObj)
|
||||
{
|
||||
foreach ($aSet as $aLinkObj) {
|
||||
$oLink = MetaModel::NewObject($sLinkClass);
|
||||
foreach ($aLinkObj as $sAttCode => $value)
|
||||
{
|
||||
foreach ($aLinkObj as $sAttCode => $value) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
|
||||
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/XMLPage.php, now loadable using autoloader
|
||||
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/XMLPage.php, now loadable using autoloader
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
*/
|
||||
|
||||
@@ -60,6 +60,7 @@ class DbConnectionWrapper
|
||||
*
|
||||
* @param \mysqli|null $oMysqli
|
||||
* @since 3.0.4 3.1.1 3.2.0 Param $oMysqli becomes nullable
|
||||
* @since 3.1.0-4 N°6848 backport of restoring cnx on null parameter value
|
||||
*/
|
||||
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli = null): void
|
||||
{
|
||||
|
||||
@@ -91,6 +91,12 @@ 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
|
||||
|
||||
define('LINKSET_EDITWHEN_NEVER', 0); // The linkset cannot be edited at all from inside this object
|
||||
define('LINKSET_EDITWHEN_ON_HOST_EDITION', 1); // The only possible action is to open a new window to create a new object
|
||||
define('LINKSET_EDITWHEN_ON_HOST_DISPLAY', 2); // Show the usual 'Actions' popup menu
|
||||
define('LINKSET_EDITWHEN_ALWAYS', 3); // Show the usual 'Actions' popup menu
|
||||
|
||||
|
||||
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
|
||||
define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
|
||||
|
||||
@@ -791,7 +797,7 @@ abstract class AttributeDefinition
|
||||
public function HasAValue($proposedValue): bool
|
||||
{
|
||||
// Default implementation, we don't really know what type $proposedValue will be
|
||||
return is_null($proposedValue);
|
||||
return !(is_null($proposedValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1703,6 +1709,15 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
public function GetEditMode()
|
||||
{
|
||||
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int see LINKSET_EDITWHEN_* constants
|
||||
* @since 3.1.1 3.2.0 N°6385
|
||||
*/
|
||||
public function GetEditWhen(): int
|
||||
{
|
||||
return $this->GetOptional('edit_when', LINKSET_EDITWHEN_ALWAYS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,7 +149,9 @@ abstract class BulkExport
|
||||
$this->oSearch = null;
|
||||
$this->iChunkSize = 0;
|
||||
$this->sFormatCode = null;
|
||||
$this->aStatusInfo = array();
|
||||
$this->aStatusInfo = [
|
||||
'show_obsolete_data' => utils::ShowObsoleteData(),
|
||||
];
|
||||
$this->oBulkExportResult = null;
|
||||
$this->sTmpFile = '';
|
||||
$this->bLocalizeOutput = false;
|
||||
@@ -203,15 +205,17 @@ abstract class BulkExport
|
||||
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
|
||||
{
|
||||
$sFormatCode = $oInfo->Get('format');
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
$aStatusInfo = json_decode($oInfo->Get('status_info'),true);
|
||||
|
||||
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
|
||||
$oSearch->SetShowObsoleteData($aStatusInfo['show_obsolete_data']);
|
||||
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
|
||||
if ($oBulkExporter)
|
||||
{
|
||||
$oBulkExporter->SetFormat($sFormatCode);
|
||||
$oBulkExporter->SetObjectList($oSearch);
|
||||
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
|
||||
$oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
|
||||
$oBulkExporter->SetStatusInfo($aStatusInfo);
|
||||
|
||||
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
|
||||
|
||||
@@ -289,6 +293,7 @@ abstract class BulkExport
|
||||
*/
|
||||
public function SetObjectList(DBSearch $oSearch)
|
||||
{
|
||||
$oSearch->SetShowObsoleteData($this->aStatusInfo['show_obsolete_data']);
|
||||
$this->oSearch = $oSearch;
|
||||
}
|
||||
|
||||
|
||||
@@ -3582,7 +3582,8 @@ abstract class DBObject implements iDisplay
|
||||
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
|
||||
|
||||
// - TriggerOnObjectUpdate
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
|
||||
$aClassList = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
|
||||
$aParams = array('class_list' => $aClassList);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)'),
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
@@ -3596,6 +3597,44 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$sClass = get_class($this);
|
||||
if (MetaModel::HasLifecycle($sClass))
|
||||
{
|
||||
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
|
||||
if (isset($this->m_aPreviousValuesForUpdatedAttributes[$sStateAttCode])) {
|
||||
$sPreviousState = $this->m_aPreviousValuesForUpdatedAttributes[$sStateAttCode];
|
||||
// Change state triggers...
|
||||
$aParams = array(
|
||||
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
|
||||
'previous_state' => $sPreviousState,
|
||||
'new_state' => $this->Get($sStateAttCode),
|
||||
);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state'), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateLeave $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state'), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateEnter $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Activate any existing trigger
|
||||
// - TriggerOnObjectMention
|
||||
// Forgotten by the fix of N°3245
|
||||
@@ -4291,36 +4330,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWrite();
|
||||
}
|
||||
|
||||
// Change state triggers...
|
||||
$aParams = array(
|
||||
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
|
||||
'previous_state' => $sPreviousState,
|
||||
'new_state' => $sNewState,
|
||||
);
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateLeave $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnStateEnter $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->FireEvent(EVENT_DB_AFTER_APPLY_STIMULUS, $aEventData);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1671,6 +1671,8 @@ class ExceptionLog extends LogAPI
|
||||
*/
|
||||
private static function GetLastEventIssue()
|
||||
{
|
||||
return self::$oLastEventIssue;
|
||||
$oRet = self::$oLastEventIssue;
|
||||
self::$oLastEventIssue = null;
|
||||
return $oRet;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1241,7 +1241,7 @@ abstract class MetaModel
|
||||
}
|
||||
$sTable = self::DBGetTable($sClass);
|
||||
|
||||
// Could be completed later with all the classes that are using a given table
|
||||
// Could be completed later with all the classes that are using a given table
|
||||
if (!array_key_exists($sTable, $aTables)) {
|
||||
$aTables[$sTable] = array();
|
||||
}
|
||||
@@ -3522,7 +3522,7 @@ abstract class MetaModel
|
||||
}
|
||||
|
||||
// Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well
|
||||
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
|
||||
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
|
||||
$oAtt->SetHostClass($sTargetClass);
|
||||
|
||||
// Some attributes could refer to a class
|
||||
@@ -3564,7 +3564,7 @@ abstract class MetaModel
|
||||
|
||||
self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt;
|
||||
self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass;
|
||||
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
|
||||
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3764,7 +3764,7 @@ abstract class MetaModel
|
||||
self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus;
|
||||
|
||||
// I wanted to simplify the syntax of the declaration of objects in the biz model
|
||||
// Therefore, the reference to the host class is set there
|
||||
// Therefore, the reference to the host class is set there
|
||||
$oStimulus->SetHostClass($sTargetClass);
|
||||
}
|
||||
|
||||
@@ -4219,40 +4219,77 @@ abstract class MetaModel
|
||||
}
|
||||
else
|
||||
{
|
||||
$aCurrentUser = array();
|
||||
$aCurrentContact = array();
|
||||
$aCurrentUser = [];
|
||||
$aCurrentContact = [];
|
||||
foreach ($aExpectedArgs as $expression)
|
||||
{
|
||||
$aName = explode('->', $expression->GetName());
|
||||
if ($aName[0] == 'current_contact_id') {
|
||||
$aPlaceholders['current_contact_id'] = UserRights::GetContactId();
|
||||
}
|
||||
if ($aName[0] == 'current_user') {
|
||||
} else if ($aName[0] == 'current_user') {
|
||||
array_push($aCurrentUser, $aName[1]);
|
||||
}
|
||||
if ($aName[0] == 'current_contact') {
|
||||
} else if ($aName[0] == 'current_contact') {
|
||||
array_push($aCurrentContact, $aName[1]);
|
||||
}
|
||||
}
|
||||
if (count($aCurrentUser) > 0) {
|
||||
$oUser = UserRights::GetUserObject();
|
||||
$aPlaceholders['current_user->object()'] = $oUser;
|
||||
foreach ($aCurrentUser as $sField) {
|
||||
$aPlaceholders['current_user->'.$sField] = $oUser->Get($sField);
|
||||
}
|
||||
static::FillObjectPlaceholders($aPlaceholders, 'current_user', UserRights::GetUserObject(), $aCurrentUser);
|
||||
}
|
||||
if (count($aCurrentContact) > 0) {
|
||||
$oPerson = UserRights::GetContactObject();
|
||||
$aPlaceholders['current_contact->object()'] = $oPerson;
|
||||
foreach ($aCurrentContact as $sField) {
|
||||
$aPlaceholders['current_contact->'.$sField] = $oPerson->Get($sField);
|
||||
}
|
||||
static::FillObjectPlaceholders($aPlaceholders, 'current_contact', UserRights::GetContactObject(), $aCurrentContact);
|
||||
}
|
||||
}
|
||||
|
||||
return $aPlaceholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.1 N°6824
|
||||
* @param array $aPlaceholders
|
||||
* @param string $sPlaceHolderPrefix
|
||||
* @param ?\DBObject $oObject
|
||||
* @param array $aCurrentUser
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
private static function FillObjectPlaceholders(array &$aPlaceholders, string $sPlaceHolderPrefix, ?\DBObject $oObject, array $aCurrentUser) : void {
|
||||
$sPlaceHolderKey = $sPlaceHolderPrefix."->object()";
|
||||
if (is_null($oObject)){
|
||||
$aContext = [
|
||||
"current_user_id" => UserRights::GetUserId(),
|
||||
"null object type" => $sPlaceHolderPrefix,
|
||||
"fields" => $aCurrentUser,
|
||||
];
|
||||
IssueLog::Warning("Unresolved placeholders due to null object in current context", null,
|
||||
$aContext);
|
||||
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
|
||||
foreach ($aCurrentUser as $sField) {
|
||||
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
|
||||
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
|
||||
}
|
||||
} else {
|
||||
$aPlaceholders[$sPlaceHolderKey] = $oObject;
|
||||
foreach ($aCurrentUser as $sField) {
|
||||
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
|
||||
if (false === MetaModel::IsValidAttCode(get_class($oObject), $sField)){
|
||||
$aContext = [
|
||||
"current_user_id" => UserRights::GetUserId(),
|
||||
"obj_class" => get_class($oObject),
|
||||
"placeholder" => $sPlaceHolderKey,
|
||||
"invalid_field" => $sField,
|
||||
];
|
||||
IssueLog::Warning("Unresolved placeholder due to invalid attribute", null,
|
||||
$aContext);
|
||||
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
$aPlaceholders[$sPlaceHolderKey] = $oObject->Get($sField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DBSearch $oFilter
|
||||
*
|
||||
@@ -6479,7 +6516,7 @@ abstract class MetaModel
|
||||
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
|
||||
$aCache['m_Category2Class'] = self::$m_Category2Class;
|
||||
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
|
||||
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
|
||||
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
|
||||
$aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass")
|
||||
$aCache['m_aClassParams'] = self::$m_aClassParams; // array of ("classname" => array of class information)
|
||||
$aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes)
|
||||
@@ -6737,7 +6774,13 @@ abstract class MetaModel
|
||||
|
||||
if ($bMustBeFound && empty($aRow))
|
||||
{
|
||||
throw new CoreException("No result for the single row query: '$sSQL'");
|
||||
$sNotFoundErrorMessage = "No result for the single row query";
|
||||
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
|
||||
'class' => $sClass,
|
||||
'key' => $iKey,
|
||||
'sql_query' => $sSQL,
|
||||
]);
|
||||
throw new CoreException($sNotFoundErrorMessage);
|
||||
}
|
||||
|
||||
return $aRow;
|
||||
|
||||
@@ -13,6 +13,9 @@ $body-overflow-x: hidden !default;
|
||||
$body-overflow-y: auto !default;
|
||||
|
||||
|
||||
/*N°5786 - Avoid strong text to always be grey (default Bulma color for this var.) so strong text can keep its color. This is mostly for text within the .ibo-is-html-content. */
|
||||
$text-strong: inherit !default;
|
||||
|
||||
/**
|
||||
* customize Bulma content variables
|
||||
* See https://bulma.io/documentation/elements/content/
|
||||
|
||||
@@ -355,4 +355,26 @@
|
||||
</presentation>
|
||||
</class>
|
||||
</classes>
|
||||
<meta>
|
||||
<classes>
|
||||
<class id="UserInternal" _delta="define_if_not_exists">
|
||||
<fields>
|
||||
<field id="contactid" xsi:type="AttributeExternalKey">
|
||||
<target_class>Contact</target_class>
|
||||
</field>
|
||||
<field id="first_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="last_name" xsi:type="AttributeExternalField"/>
|
||||
<field id="status" xsi:type="AttributeEnum"/>
|
||||
<field id="org_id" xsi:type="AttributeExternalField"/>
|
||||
<field id="email" xsi:type="AttributeExternalField"/>
|
||||
<field id="login" xsi:type="AttributeString"/>
|
||||
<field id="language" xsi:type="AttributeApplicationLanguage"/>
|
||||
<field id="status" xsi:type="AttributeEnum"/>
|
||||
<field id="allowed_org_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="profile_list" xsi:type="AttributeLinkedSetIndirect"/>
|
||||
<field id="log" xsi:type="AttributeCaseLog"/>
|
||||
</fields>
|
||||
</class>
|
||||
</classes>
|
||||
</meta>
|
||||
</itop_design>
|
||||
|
||||
@@ -6354,17 +6354,7 @@
|
||||
<attribute id="connectableci_id"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<uniqueness_rules>
|
||||
<rule id="no_duplicate">
|
||||
<attributes>
|
||||
<attribute id="networkdevice_id"/>
|
||||
<attribute id="connectableci_id"/>
|
||||
</attributes>
|
||||
<filter><![CDATA[]]></filter>
|
||||
<disabled>false</disabled>
|
||||
<is_blocking>true</is_blocking>
|
||||
</rule>
|
||||
</uniqueness_rules>
|
||||
<uniqueness_rules/>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="networkdevice_id" xsi:type="AttributeExternalKey">
|
||||
|
||||
@@ -88,7 +88,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
|
||||
// Errors
|
||||
'iTopUpdate:Error:MissingFunction' => 'Lehetetlen elindítani a frissítést, hiányzó funkció',
|
||||
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$',
|
||||
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$s',
|
||||
'iTopUpdate:Error:CorruptedFile' => 'A %1$s fájl sérült',
|
||||
'iTopUpdate:Error:BadFileFormat' => 'A frissítési fájl nem zip fájl',
|
||||
'iTopUpdate:Error:BadFileContent' => 'A frissítési fájl nem alkalmazás archívum',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1803,6 +1803,11 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Tippy: Handle multi-line content */
|
||||
.tippy-content {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/**********************************************************/
|
||||
/* Shameful area (things that should be refactored soon) */
|
||||
/**********************************************************/
|
||||
|
||||
@@ -1105,7 +1105,11 @@ class ObjectController extends BrickController
|
||||
// When reaching to an Attachment, we have to check security on its host object instead of the Attachment itself
|
||||
if ($sObjectClass === 'Attachment')
|
||||
{
|
||||
$oAttachment = MetaModel::GetObject($sObjectClass, $sObjectId, true, true);
|
||||
|
||||
$oAttachment = MetaModel::GetObject($sObjectClass, $sObjectId, false, true);
|
||||
if ($oAttachment === null) {
|
||||
throw new HttpException(Response::HTTP_NOT_FOUND, Dict::S('UI:ObjectDoesNotExist'));
|
||||
}
|
||||
$sHostClass = $oAttachment->Get('item_class');
|
||||
$sHostId = $oAttachment->Get('item_id');
|
||||
|
||||
@@ -1384,7 +1388,7 @@ class ObjectController extends BrickController
|
||||
$aObjectIds = $oRequestManipulator->ReadParam('aObjectIds', array(), FILTER_UNSAFE_RAW);
|
||||
$aObjectAttCodes = $oRequestManipulator->ReadParam('aObjectAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$aLinkAttCodes = $oRequestManipulator->ReadParam('aLinkAttCodes', array(), FILTER_UNSAFE_RAW);
|
||||
$sDateTimePickerWidgetParent = $oRequestManipulator->ReadParam('sDateTimePickerWidgetParent', array(), FILTER_SANITIZE_STRING);
|
||||
$sDateTimePickerWidgetParent = $oRequestManipulator->ReadParam('sDateTimePickerWidgetParent', array(), FILTER_UNSAFE_RAW);
|
||||
|
||||
if (empty($sObjectClass) || empty($aObjectIds) || empty($aObjectAttCodes)) {
|
||||
IssueLog::Info(__METHOD__.' at line '.__LINE__.' : sObjectClass, aObjectIds and aObjectAttCodes expected, "'.$sObjectClass.'", "'.implode('/',
|
||||
|
||||
@@ -1748,48 +1748,15 @@ public function PrefillSearchForm(&$aContextParam)
|
||||
<ext_key_to_remote>slt_id</ext_key_to_remote>
|
||||
<duplicates/>
|
||||
</field>
|
||||
<field id="customercontracts_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
<field id="customercontracts_list" xsi:type="AttributeLinkedSet">
|
||||
<linked_class>lnkCustomerContractToService</linked_class>
|
||||
<ext_key_to_me>sla_id</ext_key_to_me>
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
<ext_key_to_remote>customercontract_id</ext_key_to_remote>
|
||||
<duplicates>true</duplicates>
|
||||
<edit_mode>none</edit_mode>
|
||||
</field>
|
||||
</fields>
|
||||
<methods>
|
||||
<method id="DoCheckToWrite">
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
<code><![CDATA[
|
||||
public function DoCheckToWrite()
|
||||
{
|
||||
parent::DoCheckToWrite();
|
||||
|
||||
$aCustomerContracts = $this->Get("customercontracts_list");
|
||||
foreach ($aCustomerContracts as $sAttCode => $oCustomerContracts)
|
||||
{
|
||||
// Recurse inside the subdirectories
|
||||
$sOql = "SELECT lnkCustomerContractToService AS ccs WHERE ccs.customercontract_id=:customercontract_id AND ccs.service_id=:service_id";
|
||||
$aQueryParams['customercontract_id'] = $oCustomerContracts->Get("customercontract_id");
|
||||
$aQueryParams['service_id'] = $oCustomerContracts->Get("service_id");
|
||||
if ($this->Get("id") != null)
|
||||
{
|
||||
$sOql = $sOql." AND ccs.sla_id!=:sla_id";
|
||||
$aQueryParams['sla_id'] = $this->Get("id");
|
||||
}
|
||||
$oQuery = DBSearch::FromOQL($sOql, $aQueryParams);
|
||||
$oResultSql = new DBObjectSet($oQuery);
|
||||
$oResultSql->OptimizeColumnLoad(['ccs' => ['customercontract_name','service_name']]);
|
||||
if ($aCurrentRow = $oResultSql->Fetch())
|
||||
{
|
||||
$this->m_aCheckIssues[] = Dict::Format('Class:SLA/Error:UniqueLnkCustomerContractToService',$aCurrentRow->Get('customercontract_name'),$aCurrentRow->Get('service_name'));
|
||||
}
|
||||
}
|
||||
}
|
||||
]]></code>
|
||||
</method>
|
||||
</methods>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<details>
|
||||
<items>
|
||||
|
||||
@@ -713,6 +713,7 @@
|
||||
<linked_class>User</linked_class>
|
||||
<ext_key_to_me>contactid</ext_key_to_me>
|
||||
<edit_mode>add_only</edit_mode>
|
||||
<edit_when>on_host_display</edit_when>
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
</field>
|
||||
|
||||
@@ -49,6 +49,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Core:AttributeTagSet' => 'List of tags',
|
||||
'Core:AttributeTagSet+' => '',
|
||||
'Core:AttributeSet:placeholder' => 'click to add',
|
||||
'Core:Placeholder:CannotBeResolved' => '(%1$s : cannot be resolved)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)',
|
||||
|
||||
@@ -39,6 +39,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
|
||||
'Core:AttributeTagSet+' => '',
|
||||
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
|
||||
'Core:Placeholder:CannotBeResolved' => '(%1$s : non remplacé)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de la classe %3$s)',
|
||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s d\'une sous-classe)',
|
||||
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'UI:Object:Modal:Title' => 'Ein Objekt erstellen',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -19,4 +19,5 @@
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'Ce formulaire contient un attribut fichier obligatoire qui n\'est pas supporté en mode pop-up. La création/modification de cet objet risque d\'être incomplète et pourra être complété dans un formulaire en pleine page.',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -18,4 +18,5 @@
|
||||
*/
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'UI:Object:Modal:Title' => 'Create an object~~',
|
||||
'UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text' => 'This form contains a mandatory file attribute which is not supported in modal mode. The creation/modification of this object may be incomplete and may be completed in a full-page form.~~',
|
||||
));
|
||||
@@ -332,11 +332,14 @@ $(function()
|
||||
oParams.dashlet_class = sDashletClass;
|
||||
oParams.dashlet_id = sDashletId;
|
||||
oParams.dashlet_type = options.dashlet_type;
|
||||
oParams.ajax_promise_id = 'ajax_promise_' + sDashletId;
|
||||
var me = this;
|
||||
$.post(this.options.render_to, oParams, function (data) {
|
||||
me.ajax_div.html(data);
|
||||
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
|
||||
me.mark_as_modified();
|
||||
window[oParams.ajax_promise_id].then(function(){
|
||||
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
|
||||
me.mark_as_modified();
|
||||
});
|
||||
});
|
||||
},
|
||||
on_dashlet_moved: function (oDashlet, oReceiver, bRefresh) {
|
||||
|
||||
@@ -152,7 +152,7 @@ $(function()
|
||||
_initChooseDefaultOperator: function()
|
||||
{
|
||||
//if the class has an index, in order to maximize the performance, we force the default operator to "equal"
|
||||
if (this.options.field.has_index && typeof this.options.available_operators['='] == 'object' && this.options.values.length == 0)
|
||||
if (this.options.field.has_index && this.options.available_operators['='] != null && typeof this.options.available_operators['='] == 'object' && this.options.values.length == 0)
|
||||
{
|
||||
this.options.operator = '=';
|
||||
this.options.available_operators['='].rank = -1;//we want it to be the first displayed
|
||||
|
||||
@@ -154,6 +154,11 @@ Selectize.define("combodo_multi_values_synthesis", function (aOptions) {
|
||||
// Listen item element click event
|
||||
$item.on('click', function(){
|
||||
|
||||
// input disabled
|
||||
if(oSelf.$input.is(':disabled')){
|
||||
return;
|
||||
}
|
||||
|
||||
// If element has operation
|
||||
if(aOperations[sItem] === OPERATIONS.add || aOperations[sItem] === OPERATIONS.remove) {
|
||||
|
||||
|
||||
@@ -43,6 +43,24 @@ Selectize.define("combodo_update_operations", function (aOptions) {
|
||||
};
|
||||
})();
|
||||
|
||||
// Override enable function
|
||||
oSelf.enable = (function () {
|
||||
let oOriginal = oSelf.enable;
|
||||
return function () {
|
||||
oOriginal.apply(oSelf, arguments);
|
||||
oSelf.$operationsInput.prop('disabled', false);
|
||||
}
|
||||
})();
|
||||
|
||||
// Override disable function
|
||||
oSelf.disable = (function () {
|
||||
let oOriginal = oSelf.disable;
|
||||
return function () {
|
||||
oOriginal.apply(oSelf, arguments);
|
||||
oSelf.$operationsInput.prop('disabled', true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Override addItem function
|
||||
oSelf.addItem = (function () {
|
||||
let oOriginal = oSelf.addItem;
|
||||
|
||||
@@ -74,6 +74,11 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) {
|
||||
'm_sWizHelperJsVarName': null // if set will use this name when server returns JS code in \WizardHelper::GetJsForUpdateFields
|
||||
};
|
||||
this.m_oData.m_sClass = sClass;
|
||||
/**
|
||||
* Promise resolve callback when dependencies have been updated
|
||||
* @since 3.0.3-2 3.0.4 3.1.1 3.2.0 N°6766
|
||||
* */
|
||||
this.m_oDependenciesUpdatedPromiseResolve = null;
|
||||
|
||||
// Setting optional transition data
|
||||
if (sInitialState !== undefined)
|
||||
@@ -152,6 +157,7 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) {
|
||||
};
|
||||
|
||||
this.UpdateFields = function () {
|
||||
const me = this;
|
||||
var aRefreshed = [];
|
||||
//console.log('** UpdateFields **');
|
||||
// Set the full HTML for the input field
|
||||
@@ -185,10 +191,17 @@ function WizardHelper(sClass, sFormPrefix, sState, sInitialState, sStimulus) {
|
||||
}
|
||||
}
|
||||
// For each "refreshed" field, asynchronously trigger a change in case there are dependent fields to update
|
||||
for (i = 0; i < aRefreshed.length; i++)
|
||||
{
|
||||
for (i = 0; i < aRefreshed.length; i++) {
|
||||
var sString = "$('#"+aRefreshed[i]+"').trigger('change').trigger('update');";
|
||||
window.setTimeout(sString, 1); // Synchronous 'trigger' does nothing, call it asynchronously
|
||||
const oPromise = new Promise(function (resolve) {
|
||||
// Store the resolve callback so we can call it later from outside
|
||||
me.m_oDependenciesUpdatedPromiseResolve = resolve;
|
||||
});
|
||||
oPromise.then(function () {
|
||||
window.setTimeout(sString, 1); // Synchronous 'trigger' does nothing, call it asynchronously
|
||||
// Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry
|
||||
me.m_oDependenciesUpdatedPromiseResolve = null;
|
||||
});
|
||||
}
|
||||
if($('[data-field-status="blocked"]').length === 0) {
|
||||
$('.disabledDuringFieldLoading').prop("disabled", false).removeClass('disabledDuringFieldLoading');
|
||||
|
||||
@@ -561,7 +561,7 @@ try
|
||||
}
|
||||
}
|
||||
}
|
||||
$oPage->add_script($oWizardHelper->GetJsForUpdateFields());
|
||||
$oWizardHelper->AddJsForUpdateFields($oPage);
|
||||
break;
|
||||
|
||||
case 'obj_creation_form':
|
||||
@@ -1030,9 +1030,9 @@ EOF
|
||||
case 'new_dashlet_id':
|
||||
$sDashboardDivId = utils::ReadParam("dashboardid");
|
||||
$bIsCustomized = true; // Only called at runtime when customizing a dashboard
|
||||
$iRow = utils::ReadParam("iRow");
|
||||
$iCol = utils::ReadParam("iCol");
|
||||
$sDashletIdOrig = utils::ReadParam("dashletid");
|
||||
$iRow = utils::ReadParam("iRow", 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
||||
$iCol = utils::ReadParam("iCol", 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
||||
$sDashletIdOrig = utils::ReadParam("dashletid", '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
$sFinalDashletId = Dashboard::GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletIdOrig);
|
||||
$oPage = new AjaxPage('');
|
||||
$oPage->SetOutputDataOnly(true);
|
||||
@@ -1042,8 +1042,8 @@ EOF
|
||||
case 'new_dashlet':
|
||||
require_once(APPROOT.'application/forms.class.inc.php');
|
||||
require_once(APPROOT.'application/dashlet.class.inc.php');
|
||||
$sDashletClass = utils::ReadParam('dashlet_class', '');
|
||||
$sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data');
|
||||
$sDashletClass = utils::ReadParam('dashlet_class', '', false, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS);
|
||||
$sDashletId = utils::ReadParam('dashlet_id', '', false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
if (is_subclass_of($sDashletClass, 'Dashlet')) {
|
||||
$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
|
||||
$offset = $oPage->start_capture();
|
||||
@@ -1065,13 +1065,14 @@ EOF
|
||||
case 'update_dashlet_property':
|
||||
require_once(APPROOT.'application/forms.class.inc.php');
|
||||
require_once(APPROOT.'application/dashlet.class.inc.php');
|
||||
$aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data');
|
||||
$aParams = utils::ReadParam('params', '', false, 'raw_data');
|
||||
$sDashletClass = $aParams['attr_dashlet_class'];
|
||||
$sDashletType = $aParams['attr_dashlet_type'];
|
||||
$sDashletId = utils::HtmlEntities($aParams['attr_dashlet_id']);
|
||||
$aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc...
|
||||
$aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value'
|
||||
$aExtraParams = utils::ReadParam('extra_params', array(), false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
||||
$aParams = utils::ReadParam('params', [], false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); // raw_data because we need different filter depending on the options
|
||||
$sDashletClass = utils::Sanitize($aParams['attr_dashlet_class'], DashletUnknown::class, utils::ENUM_SANITIZATION_FILTER_PHP_CLASS); // Dashlet PHP class or DashletUnknown if impl isn't present in the installed extensions
|
||||
$sDashletType = utils::Sanitize($aParams['attr_dashlet_type'], '', utils::ENUM_SANITIZATION_FILTER_PHP_CLASS); // original Dashlet PHP class, could be non-existing on the iTop instance (XML definition loading)
|
||||
$sDashletId = utils::Sanitize($aParams['attr_dashlet_id'], '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER);
|
||||
$aUpdatedProperties = utils::Sanitize($aParams['updated'], [], utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy' etc
|
||||
$aPreviousValues = utils::Sanitize($aParams['previous_values'], [], utils::ENUM_SANITIZATION_FILTER_RAW_DATA); // hash array: 'attr_xxx' => 'old_value' - no sanitization as values will be handled in the Dashlet object
|
||||
|
||||
if (is_subclass_of($sDashletClass, 'Dashlet')) {
|
||||
/** @var \Dashlet $oDashlet */
|
||||
$oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId);
|
||||
@@ -1208,12 +1209,13 @@ EOF
|
||||
'base/layouts/navigation-menu/menu-node'
|
||||
);
|
||||
|
||||
// Important: Mind the back ticks to avoid line breaks to break the JS
|
||||
$oPage->add_script(<<<JS
|
||||
$MenuNameEscaped = utils::HtmlEntities($aValues['name']);
|
||||
// Important: Mind the back ticks to avoid line breaks to break the JS
|
||||
$oPage->add_script(<<<JS
|
||||
$('body').trigger('add_shortcut_node.navigation_menu.itop', {
|
||||
parent_menu_node_id: '{$sMenuGroupId}',
|
||||
new_menu_node_html_rendering: `{$sHtml}`,
|
||||
new_menu_name: `{$aValues['name']}`
|
||||
new_menu_name: `{$MenuNameEscaped}`
|
||||
});
|
||||
JS
|
||||
);
|
||||
|
||||
@@ -938,6 +938,30 @@ EOF
|
||||
return $aXmlToPHP[$sEditMode];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to format the edit-when for direct linkset
|
||||
*
|
||||
* @param string $sEditWhen Value set from within the XML
|
||||
* @return string PHP flag
|
||||
*
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function EditWhenToPHP($sEditWhen): string
|
||||
{
|
||||
static $aXmlToPHP = array(
|
||||
'never' => 'LINKSET_EDITWHEN_NEVER',
|
||||
'on_host_edition' => 'LINKSET_EDITWHEN_ON_HOST_EDITION',
|
||||
'on_host_display' => 'LINKSET_EDITWHEN_ON_HOST_DISPLAY',
|
||||
'always' => 'LINKSET_EDITWHEN_ALWAYS',
|
||||
);
|
||||
|
||||
if (!array_key_exists($sEditWhen, $aXmlToPHP))
|
||||
{
|
||||
throw new DOMFormatException("Edit mode: unknown value '$sEditWhen'");
|
||||
}
|
||||
return $aXmlToPHP[$sEditWhen];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a path (file or url) as an absolute path or relative to the module or the app
|
||||
@@ -2054,6 +2078,7 @@ EOF
|
||||
$this->CompileCommonProperty('duplicates', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('edit_when', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
@@ -2064,6 +2089,7 @@ EOF
|
||||
$this->CompileCommonProperty('count_max', $oField, $aParameters, $sModuleRelativeDir, 0);
|
||||
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('edit_mode', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('edit_when', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
@@ -2289,6 +2315,12 @@ EOF
|
||||
$aParameters['edit_mode'] = $this->EditModeToPHP($sEditMode);
|
||||
}
|
||||
break;
|
||||
case 'edit_when':
|
||||
$sEditWhen = $oField->GetChildText('edit_when');
|
||||
if(!is_null($sEditWhen)){
|
||||
$aParameters['edit_when'] = $this->EditWhenToPHP($sEditWhen);
|
||||
}
|
||||
break;
|
||||
case 'mappings':
|
||||
$oMappings = $oField->GetUniqueElement('mappings');
|
||||
$oMappingNodes = $oMappings->getElementsByTagName('mapping');
|
||||
|
||||
@@ -1466,7 +1466,7 @@ EOF
|
||||
switch ($sAlteration) {
|
||||
case '':
|
||||
if ($oNodeClone->hasAttribute('id')) {
|
||||
$oNodeClone->setAttribute('_delta', 'must_exist');
|
||||
//$oNodeClone->setAttribute('_delta', 'merge');
|
||||
}
|
||||
break;
|
||||
case 'added':
|
||||
|
||||
@@ -76,7 +76,7 @@ class ModuleDiscovery
|
||||
'doc.manual_setup' => 'url',
|
||||
'doc.more_information' => 'url',
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Cache the results and the source directories
|
||||
protected static $m_aSearchDirs = null;
|
||||
@@ -148,7 +148,7 @@ class ModuleDiscovery
|
||||
self::$m_aModuleVersionByName[$sModuleName]['version'] = $sModuleVersion;
|
||||
self::$m_aModuleVersionByName[$sModuleName]['id'] = $sId;
|
||||
}
|
||||
|
||||
|
||||
self::$m_aModules[$sId] = $aArgs;
|
||||
|
||||
// Now keep the relative paths, as provided
|
||||
@@ -220,8 +220,8 @@ class ModuleDiscovery
|
||||
public static function OrderModulesByDependencies($aModules, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
|
||||
{
|
||||
// Order the modules to take into account their inter-dependencies
|
||||
$aDependencies = array();
|
||||
$aSelectedModules = array();
|
||||
$aDependencies = [];
|
||||
$aSelectedModules = [];
|
||||
foreach($aModules as $sId => $aModule)
|
||||
{
|
||||
list($sModuleName, ) = self::GetModuleName($sId);
|
||||
@@ -232,7 +232,7 @@ class ModuleDiscovery
|
||||
}
|
||||
}
|
||||
ksort($aDependencies);
|
||||
$aOrderedModules = array();
|
||||
$aOrderedModules = [];
|
||||
$iLoopCount = 1;
|
||||
while(($iLoopCount < count($aModules)) && (count($aDependencies) > 0) )
|
||||
{
|
||||
@@ -256,13 +256,24 @@ class ModuleDiscovery
|
||||
}
|
||||
if ($bAbortOnMissingDependency && count($aDependencies) > 0)
|
||||
{
|
||||
$aModulesInfo = array();
|
||||
$aModuleDeps = array();
|
||||
$aModulesInfo = [];
|
||||
$aModuleDeps = [];
|
||||
foreach($aDependencies as $sId => $aDeps)
|
||||
{
|
||||
$aModule = $aModules[$sId];
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDeps);
|
||||
$aModulesInfo[$sId] = array('module' => $aModule, 'dependencies' => $aDeps);
|
||||
$aDepsWithIcons = [];
|
||||
foreach($aDeps as $sIndex => $sDepId)
|
||||
{
|
||||
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules))
|
||||
{
|
||||
$aDepsWithIcons[$sIndex] = '✅ ' . $sDepId;
|
||||
} else
|
||||
{
|
||||
$aDepsWithIcons[$sIndex] = '❌ ' . $sDepId;
|
||||
}
|
||||
}
|
||||
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
|
||||
$aModulesInfo[$sId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons);
|
||||
}
|
||||
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
|
||||
$oException = new MissingDependencyException($sMessage);
|
||||
@@ -289,7 +300,7 @@ class ModuleDiscovery
|
||||
// The de-duplication is now done directly by the AddModule method
|
||||
return $aModules;
|
||||
}
|
||||
|
||||
|
||||
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
|
||||
{
|
||||
$bResult = false;
|
||||
@@ -336,12 +347,12 @@ class ModuleDiscovery
|
||||
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator))
|
||||
{
|
||||
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
else
|
||||
{
|
||||
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
|
||||
// a function call that results in a runtime fatal error
|
||||
// a function call that results in a runtime fatal error
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -400,20 +411,20 @@ class ModuleDiscovery
|
||||
{
|
||||
self::ResetCache();
|
||||
}
|
||||
|
||||
|
||||
if (is_null(self::$m_aSearchDirs))
|
||||
{
|
||||
self::$m_aSearchDirs = $aSearchDirs;
|
||||
|
||||
|
||||
// Not in cache, let's scan the disk
|
||||
foreach($aSearchDirs as $sSearchDir)
|
||||
{
|
||||
$sLookupDir = realpath($sSearchDir);
|
||||
$sLookupDir = realpath($sSearchDir);
|
||||
if ($sLookupDir == '')
|
||||
{
|
||||
throw new Exception("Invalid directory '$sSearchDir'");
|
||||
}
|
||||
|
||||
|
||||
clearstatcache();
|
||||
self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir));
|
||||
}
|
||||
@@ -425,7 +436,7 @@ class ModuleDiscovery
|
||||
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function ResetCache()
|
||||
{
|
||||
self::$m_aSearchDirs = null;
|
||||
@@ -437,7 +448,7 @@ class ModuleDiscovery
|
||||
* Helper function to interpret the name of a module
|
||||
* @param $sModuleId string Identifier of the module, in the form 'name/version'
|
||||
* @return array(name, version)
|
||||
*/
|
||||
*/
|
||||
public static function GetModuleName($sModuleId)
|
||||
{
|
||||
$aMatches = array();
|
||||
@@ -466,7 +477,7 @@ class ModuleDiscovery
|
||||
{
|
||||
static $iDummyClassIndex = 0;
|
||||
$sDirectory = $sRootDir.'/'.$sRelDir;
|
||||
|
||||
|
||||
if ($hDir = opendir($sDirectory))
|
||||
{
|
||||
// This is the correct way to loop over the directory. (according to the documentation)
|
||||
@@ -502,12 +513,12 @@ class ModuleDiscovery
|
||||
$idx++;
|
||||
}
|
||||
$bRet = eval($sModuleFileContents);
|
||||
|
||||
|
||||
if ($bRet === false)
|
||||
{
|
||||
SetupLog::Warning("Eval of $sRelDir/$sFile returned false");
|
||||
}
|
||||
|
||||
|
||||
//echo "<p>Done.</p>\n";
|
||||
}
|
||||
catch(ParseError $e)
|
||||
@@ -535,7 +546,7 @@ class ModuleDiscovery
|
||||
/** Alias for backward compatibility with old module files in which
|
||||
* the declaration of a module invokes SetupWebPage::AddModule()
|
||||
* whereas the new form is ModuleDiscovery::AddModule()
|
||||
*/
|
||||
*/
|
||||
class SetupWebPage extends ModuleDiscovery
|
||||
{
|
||||
// For backward compatibility with old modules...
|
||||
@@ -562,9 +573,9 @@ class SetupWebPage extends ModuleDiscovery
|
||||
public static function log($sText)
|
||||
{
|
||||
SetupLog::Ok($sText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Ugly patch !!!
|
||||
* In order to be able to analyse / load several times
|
||||
* the same module file, we rename the class (to avoid duplicate class definitions)
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
namespace Combodo\iTop\Application\Helper;
|
||||
|
||||
use AttributeBlob;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Alert\Alert;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
|
||||
use DBObject;
|
||||
use Dict;
|
||||
use MetaModel;
|
||||
@@ -56,6 +58,39 @@ class FormHelper
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the object has a mandatory attribute blob
|
||||
*
|
||||
* @see N°6861 - Display warning when creating/editing a mandatory blob in modal
|
||||
*
|
||||
* @param \DBObject $oObject
|
||||
*
|
||||
* @return bool
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public static function HasMandatoryAttributeBlobInputs(DBObject $oObject): bool
|
||||
{
|
||||
foreach (MetaModel::ListAttributeDefs(get_class($oObject)) as $sAttCode => $oAttDef) {
|
||||
if ($oAttDef instanceof AttributeBlob && (!$oAttDef->IsNullAllowed() || ($oObject->GetFormAttributeFlags($sAttCode) & OPT_ATT_MANDATORY))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Alert explaining what will happen when a mandatory attribute blob is displayed in a form
|
||||
*
|
||||
* @see N°6861 - Display warning when creating/editing a mandatory blob in modal
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Component\Alert\Alert
|
||||
*/
|
||||
public static function GetAlertForMandatoryAttributeBlobInputsInModal(): Alert
|
||||
{
|
||||
$oAlert = AlertUIBlockFactory::MakeForWarning('',Dict::S('UI:Object:Modal:MandatoryAttributeBlobInputs:Warning:Text'));
|
||||
return $oAlert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update flags to be sent to form with url parameters
|
||||
* For now only supports "readonly" param
|
||||
|
||||
@@ -35,6 +35,7 @@ use ReflectionClass;
|
||||
use SetupPage;
|
||||
use SetupUtils;
|
||||
use Twig\Error\Error;
|
||||
use Twig\Error\SyntaxError;
|
||||
use utils;
|
||||
use WebPage;
|
||||
use ZipArchive;
|
||||
@@ -662,6 +663,9 @@ abstract class Controller extends AbstractController
|
||||
{
|
||||
return $this->m_oTwig->render($sName.'.'.$sTemplateFileExtension.'.twig', $aParams);
|
||||
}
|
||||
catch (SyntaxError $e) {
|
||||
IssueLog::Error($e->getMessage().' - file: '.$e->getFile().'('.$e->getLine().')');
|
||||
}
|
||||
catch (Error $e) {
|
||||
if (strpos($e->getMessage(), 'Unable to find template') === false)
|
||||
{
|
||||
|
||||
@@ -200,7 +200,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$sSubTitle = Dict::Format('UI:Pagination:HeaderSelection', $sCountHtml, '<span class="ibo-datatable--selected-count">0</span>');
|
||||
} else {
|
||||
$sSubTitle = Dict::Format('UI:Pagination:HeaderNoSelection', $sCountHtml);
|
||||
}
|
||||
}
|
||||
|
||||
if (utils::IsNotNullOrEmptyString($sFilterListUrl)) {
|
||||
$sSubTitle = '<a href="'.$sFilterListUrl.'" title="'.Dict::S('UI:Menu:FilterList').'">'.$sSubTitle.'</a>';
|
||||
@@ -211,7 +211,6 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
if (isset($aExtraParams['panel_icon']) && strlen($aExtraParams['panel_icon']) > 0) {
|
||||
$oContainer->SetIcon($aExtraParams['panel_icon']);
|
||||
}
|
||||
|
||||
$oContainer->AddToolbarBlock($oBlockMenu);
|
||||
$oContainer->AddMainBlock($oDataTable);
|
||||
} else {
|
||||
@@ -333,7 +332,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
} else {
|
||||
$aExtraFields['*'][] = $sFieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aClassAliases = $oSet->GetFilter()->GetSelectedClasses();
|
||||
$aAuthorizedClasses = array();
|
||||
@@ -352,59 +351,59 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
// Regular use case, dispatch fields to their corresponding aliases
|
||||
else if (array_key_exists($sAlias, $aExtraFields)) {
|
||||
$aLists[$sAlias] = $aExtraFields[$sAlias];
|
||||
}
|
||||
}
|
||||
// Finally, if unknown alias, ignore fields
|
||||
else {
|
||||
$aLists[$sAlias] = array();
|
||||
}
|
||||
|
||||
// If zlist specified, merge its fields with the currently present
|
||||
if ($sZListName !== false) {
|
||||
if ($sZListName !== false) {
|
||||
$aDefaultList = MetaModel::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
|
||||
$aLists[$sAlias] = array_merge($aDefaultList, $aLists[$sAlias]);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the list to removed linked set since we are not able to display them here
|
||||
// Filter the list to removed linked set since we are not able to display them here
|
||||
foreach ($aLists[$sAlias] as $index => $sAttCode) {
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeLinkedSet) {
|
||||
// Removed from the display list
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeLinkedSet) {
|
||||
// Removed from the display list
|
||||
unset($aLists[$sAlias][$index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($aLists[$sAlias])) {
|
||||
unset($aLists[$sAlias], $aAuthorizedClasses[$sAlias]);
|
||||
}
|
||||
}
|
||||
|
||||
// Only for main class
|
||||
if (!empty($sLinkageAttribute) && $sClassName === $oSet->GetFilter()->GetClass()) {
|
||||
// The set to display is in fact a set of links between the object specified in the $sLinkageAttribute
|
||||
// and other objects...
|
||||
// The display will then group all the attributes related to the link itself:
|
||||
// | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n |
|
||||
$aDisplayList = array();
|
||||
$aAttDefs = MetaModel::ListAttributeDefs($sClassName);
|
||||
assert(isset($aAttDefs[$sLinkageAttribute]));
|
||||
$oAttDef = $aAttDefs[$sLinkageAttribute];
|
||||
assert($oAttDef->IsExternalKey());
|
||||
// First display all the attributes specific to the link record
|
||||
// The set to display is in fact a set of links between the object specified in the $sLinkageAttribute
|
||||
// and other objects...
|
||||
// The display will then group all the attributes related to the link itself:
|
||||
// | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n |
|
||||
$aDisplayList = array();
|
||||
$aAttDefs = MetaModel::ListAttributeDefs($sClassName);
|
||||
assert(isset($aAttDefs[$sLinkageAttribute]));
|
||||
$oAttDef = $aAttDefs[$sLinkageAttribute];
|
||||
assert($oAttDef->IsExternalKey());
|
||||
// First display all the attributes specific to the link record
|
||||
foreach ($aLists[$sAlias] as $sLinkAttCode) {
|
||||
$oLinkAttDef = $aAttDefs[$sLinkAttCode];
|
||||
if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) {
|
||||
$aDisplayList[] = $sLinkAttCode;
|
||||
}
|
||||
$oLinkAttDef = $aAttDefs[$sLinkAttCode];
|
||||
if ((!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField())) {
|
||||
$aDisplayList[] = $sLinkAttCode;
|
||||
}
|
||||
// Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant)
|
||||
}
|
||||
// Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant)
|
||||
foreach ($aLists[$sAlias] as $sLinkAttCode) {
|
||||
$oLinkAttDef = $aAttDefs[$sLinkAttCode];
|
||||
if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute))
|
||||
|| ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) {
|
||||
$aDisplayList[] = $sLinkAttCode;
|
||||
}
|
||||
$oLinkAttDef = $aAttDefs[$sLinkAttCode];
|
||||
if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute))
|
||||
|| ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode() != $sLinkageAttribute))) {
|
||||
$aDisplayList[] = $sLinkAttCode;
|
||||
}
|
||||
// First display all the attributes specific to the link
|
||||
// Then display all the attributes linked to the other end of the relationship
|
||||
}
|
||||
// First display all the attributes specific to the link
|
||||
// Then display all the attributes linked to the other end of the relationship
|
||||
$aLists[$sAlias] = $aDisplayList;
|
||||
}
|
||||
}
|
||||
@@ -489,7 +488,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$oSet->OptimizeColumnLoad($aColumnsToLoad);
|
||||
|
||||
$aColumnDefinition = [];
|
||||
$iIndexColumn = 0;
|
||||
$iIndexColumn=0;
|
||||
|
||||
$bSelectMode = isset($aExtraParams['selection_mode']) ? $aExtraParams['selection_mode'] == true : false;
|
||||
$bSingleSelectMode = isset($aExtraParams['selection_type']) ? ($aExtraParams['selection_type'] == 'single') : false;
|
||||
@@ -510,7 +509,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$sCode = ($aData['code'] == '_key_') ? 'friendlyname' : $aData['code'];
|
||||
if ($aData['sort'] != 'none') {
|
||||
$aSortOrder[$sClassAlias.'.'.$sCode] = ($aData['sort'] == 'asc'); // true for ascending, false for descending
|
||||
$aSortDatable = [$iIndexColumn, $aData['sort']];
|
||||
$aSortDatable=[$iIndexColumn,$aData['sort']];
|
||||
}
|
||||
elseif (isset($oCustomSettings->aSortOrder[$sAttCode])){
|
||||
$aSortOrder[$sClassAlias.'.'.$sCode] = $oCustomSettings->aSortOrder[$sAttCode]; // true for ascending, false for descending
|
||||
@@ -596,15 +595,14 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$aOptions['bUseCustomSettings'] = $bUseCustomSettings;
|
||||
$aOptions['bViewLink'] = $bViewLink;
|
||||
$aOptions['oClassAliases'] = json_encode($aClassAliases);
|
||||
|
||||
if (isset($aExtraParams['selected_rows']) && !empty($aExtraParams['selected_rows'])) {
|
||||
$aOptions['sSelectedRows'] = json_encode($aExtraParams['selected_rows']);
|
||||
} else {
|
||||
$aOptions['sSelectedRows'] = '[]';
|
||||
}
|
||||
$aExtraParams['table_id']=$sTableId;
|
||||
$aExtraParams['list_id']=$sListId;
|
||||
|
||||
$aExtraParams['table_id'] = $sTableId;
|
||||
$aExtraParams['list_id'] = $sListId;
|
||||
|
||||
$oDataTable->SetOptions($aOptions);
|
||||
$oDataTable->SetAjaxUrl(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php");
|
||||
@@ -628,7 +626,7 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
if (isset($aExtraParams['creation_in_modal_js_handler'])){
|
||||
$oDataTable->SetModalCreationHandler($aExtraParams['creation_in_modal_js_handler']);
|
||||
}
|
||||
|
||||
|
||||
return $oDataTable;
|
||||
}
|
||||
|
||||
@@ -714,14 +712,14 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$aColumnDefinition["type"] = "html";
|
||||
|
||||
if ($sAttCode == '_key_') {
|
||||
$sAttrLabel = $aData['alias'];
|
||||
$sAttLabel = $aData['alias'];
|
||||
$aColumnDefinition["title"] = $aData['alias'];
|
||||
$aColumnDefinition['metadata'] = [
|
||||
'object_class' => $sClassName,
|
||||
'class_alias' => $sClassAlias,
|
||||
'attribute_code' => $sAttCode,
|
||||
'attribute_type' => '_key_',
|
||||
'attribute_label' => $sAttrLabel,
|
||||
'attribute_label' => $sAttLabel,
|
||||
];
|
||||
$aColumnDefinition["data"] = $sClassAlias."/".$sAttCode;
|
||||
$aColumnDefinition["render"] = [
|
||||
@@ -729,8 +727,8 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
"_" => $sClassAlias."/".$sAttCode,
|
||||
];
|
||||
$aColumnDefinition["createdCell"] = <<<JS
|
||||
$(td).attr('data-object-class', '$sClassName');
|
||||
$(td).attr('data-attribute-label', '$sAttrLabel');
|
||||
$(td).attr('data-object-class', `$sClassName`);
|
||||
$(td).attr('data-attribute-label', `$sAttLabel`);
|
||||
if (rowData["$sClassAlias/$sAttCode/raw"]) {
|
||||
$(td).attr('data-value-raw', rowData["$sClassAlias/$sAttCode/raw"]);
|
||||
}
|
||||
@@ -759,10 +757,10 @@ JS;
|
||||
"_" => $sClassAlias."/".$sAttCode,
|
||||
];
|
||||
$aColumnDefinition["createdCell"] = <<<JS
|
||||
$(td).attr('data-object-class', '$sClassName');
|
||||
$(td).attr('data-attribute-label', '$sAttLabel');
|
||||
$(td).attr('data-attribute-code', '$sAttCode');
|
||||
$(td).attr('data-attribute-type', '$sAttDefClass');
|
||||
$(td).attr('data-object-class', `$sClassName`);
|
||||
$(td).attr('data-attribute-label', `$sAttLabel`);
|
||||
$(td).attr('data-attribute-code', `$sAttCode`);
|
||||
$(td).attr('data-attribute-type', `$sAttDefClass`);
|
||||
if (rowData["$sClassAlias/$sAttCode/raw"]) {
|
||||
$(td).attr('data-value-raw', rowData["$sClassAlias/$sAttCode/raw"]);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
$oLinkSet = $oOrmLinkSet->ToDBObjectSet(utils::ShowObsoleteData());
|
||||
|
||||
// add list block
|
||||
$oBlock = new DisplayBlock($oLinkSet->GetFilter(), 'listInObject', false);
|
||||
$oBlock = new DisplayBlock($oLinkSet->GetFilter(), DisplayBlock::ENUM_STYLE_LIST_IN_OBJECT, false);
|
||||
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
*/
|
||||
private function InitIsAttEditable(): void
|
||||
{
|
||||
$iFlags = 0;
|
||||
$iFlags = OPT_ATT_NORMAL;
|
||||
|
||||
if ($this->oDbObject->IsNew())
|
||||
{
|
||||
@@ -216,8 +216,20 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
|
||||
{
|
||||
$iFlags = $this->oDbObject->GetAttributeFlags($this->sAttCode);
|
||||
}
|
||||
|
||||
$bEditWhen = $this->IsEditableBasedOnEditWhen();
|
||||
|
||||
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN));
|
||||
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN)) && $bEditWhen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares Linkset attribute edit_when values with its usage requirements
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.1.1 3.2.0 N°6385
|
||||
*/
|
||||
protected function IsEditableBasedOnEditWhen(): bool{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -121,10 +121,13 @@ class BlockDirectLinkSetEditTable extends UIContentBlock
|
||||
{
|
||||
$this->oAttributeLinkedSet = MetaModel::GetAttributeDef($this->oUILinksDirectWidget->GetClass(), $this->oUILinksDirectWidget->GetAttCode());
|
||||
|
||||
$sEditWhen = $this->oAttributeLinkedSet->GetEditWhen();
|
||||
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS || $sEditWhen === LINKSET_EDITWHEN_ON_HOST_EDITION);
|
||||
|
||||
// User rights
|
||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttributeLinkedSet->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -179,4 +179,13 @@ class BlockDirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
|
||||
|
||||
return $aDefaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function IsEditableBasedOnEditWhen(): bool
|
||||
{
|
||||
$sEditWhen = $this->oAttDef->GetEditWhen();
|
||||
return $sEditWhen === LINKSET_EDITWHEN_ALWAYS || $sEditWhen === LINKSET_EDITWHEN_ON_HOST_DISPLAY;
|
||||
}
|
||||
}
|
||||
@@ -107,10 +107,13 @@ class BlockIndirectLinkSetEditTable extends UIContentBlock
|
||||
{
|
||||
$this->oAttributeLinkedSetIndirect = MetaModel::GetAttributeDef($this->oUILinksWidget->GetClass(), $this->oUILinksWidget->GetAttCode());
|
||||
|
||||
$sEditWhen = $this->oAttributeLinkedSetIndirect->GetEditWhen();
|
||||
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS || $sEditWhen === LINKSET_EDITWHEN_ON_HOST_EDITION);
|
||||
|
||||
// User rights
|
||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
|
||||
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttributeLinkedSetIndirect->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES && $bIsEditableBasedOnEditWhen;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +281,7 @@ EOF
|
||||
|
||||
if ($bReadOnly) {
|
||||
$aRow['form::checkbox'] = "";
|
||||
foreach ($this->m_aEditableFields as $sFieldCode) {
|
||||
foreach ($this->oUILinksWidget->GetEditableFields() as $sFieldCode) {
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$aRow[$sFieldCode] = $sDisplayValue;
|
||||
}
|
||||
@@ -432,7 +435,7 @@ JS
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->oUILinksWidget->GetLinkedClass(), $sFieldCode);
|
||||
|
||||
if ($bReadOnlyField) {
|
||||
$sFieldForHtml = $sDisplayValue;
|
||||
$sFieldForHtml = $oAttDef->GetAsHTML($sValue);
|
||||
} else {
|
||||
$sFieldForHtml = cmdbAbstractObject::GetFormElementForField(
|
||||
$oP,
|
||||
|
||||
@@ -126,4 +126,13 @@ class BlockIndirectLinkSetViewTable extends AbstractBlockLinkSetViewTable
|
||||
|
||||
return $sAttCodesToDisplay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function IsEditableBasedOnEditWhen(): bool
|
||||
{
|
||||
$sEditWhen = $this->oAttDef->GetEditWhen();
|
||||
return $sEditWhen === LINKSET_EDITWHEN_ALWAYS || $sEditWhen === LINKSET_EDITWHEN_ON_HOST_DISPLAY;
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class LinkSetUIBlockFactory extends SetUIBlockFactory
|
||||
$oSetUIBlock->GetDataProvider()->SetOptions(array_values($aInitialOptions));
|
||||
// Set value
|
||||
$oSetUIBlock->SetValue(json_encode($aCurrentValues));
|
||||
$oSetUIBlock->SetInitialValue(json_encode($aInitialValues));
|
||||
$oSetUIBlock->SetInitialValue(json_encode(array_merge($aInitialValues, $aCurrentValues)));
|
||||
} else {
|
||||
$oSetUIBlock->SetHasError(true);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,8 @@ class iTopComposer
|
||||
$APPROOT_WITH_SLASHES.'lib/symfony/web-profiler-bundle/Tests',
|
||||
$APPROOT_WITH_SLASHES.'lib/symfony/yaml/Tests',
|
||||
|
||||
$APPROOT_WITH_SLASHES.'lib/thenetworg/oauth2-azure/tests',
|
||||
|
||||
$APPROOT_WITH_SLASHES.'lib/twig/twig/src/Test',
|
||||
$APPROOT_WITH_SLASHES.'lib/twig/twig/lib/Twig/Test',
|
||||
$APPROOT_WITH_SLASHES.'lib/twig/twig/doc/tests',
|
||||
|
||||
@@ -170,6 +170,10 @@ JS;
|
||||
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
|
||||
FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams);
|
||||
|
||||
if(FormHelper::HasMandatoryAttributeBlobInputs($oObjToClone)){
|
||||
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal());
|
||||
}
|
||||
|
||||
$aFormExtraParams['js_handlers']['cancel_button_on_click'] =
|
||||
<<<JS
|
||||
function() {
|
||||
@@ -293,6 +297,9 @@ JS;
|
||||
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
|
||||
FormHelper::DisableAttributeBlobInputs($sClass, $aFormExtraParams);
|
||||
|
||||
if(FormHelper::HasMandatoryAttributeBlobInputs($oObj)){
|
||||
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal());
|
||||
}
|
||||
} else {
|
||||
$oPage = new iTopWebPage('', $bPrintable);
|
||||
$oPage->DisableBreadCrumb();
|
||||
|
||||
@@ -228,6 +228,10 @@ JS
|
||||
|
||||
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
|
||||
FormHelper::DisableAttributeBlobInputs($sRealClass, $aExtraParams);
|
||||
|
||||
if(FormHelper::HasMandatoryAttributeBlobInputs($oObj)){
|
||||
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal());
|
||||
}
|
||||
|
||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aExtraParams);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,13 @@ class BsLinkedSetFieldRenderer extends BsFieldRenderer
|
||||
// Retrieve link and remote attributes
|
||||
$aAttributesToDisplay = $this->oField->GetAttributesToDisplay();
|
||||
$aLnkAttributesToDisplay = $this->oField->GetLnkAttributesToDisplay();
|
||||
$iLinkAttributesToDisplayCount = count($this->oField->GetLnkAttributesToDisplay()) + 1;
|
||||
|
||||
// we sort the table on the first non link column
|
||||
$iSortColumnIndex = count($this->oField->GetLnkAttributesToDisplay());
|
||||
// if we are in edition mode, we skip the first column (selection checkbox column)
|
||||
if(!$this->oField->GetReadOnly()){
|
||||
$iSortColumnIndex++;
|
||||
}
|
||||
|
||||
// Vars to build the table
|
||||
$sAttributesToDisplayAsJson = json_encode($aAttributesToDisplay);
|
||||
@@ -275,7 +281,7 @@ EOF
|
||||
// We would just have to override / complete the necessary elements
|
||||
var buildTable_{$this->oField->GetGlobalId()} = function()
|
||||
{
|
||||
var iDefaultOrderColumnIndex = {$iLinkAttributesToDisplayCount};
|
||||
var iDefaultOrderColumnIndex = {$iSortColumnIndex};
|
||||
|
||||
// Instantiates datatables
|
||||
oTable_{$this->oField->GetGlobalId()} = $('#{$sTableId}').DataTable({
|
||||
|
||||
@@ -40,6 +40,11 @@ class Router
|
||||
return static::$oSingleton;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var bool $bUseCache
|
||||
*/
|
||||
protected $bUseCache = null;
|
||||
|
||||
/**********************/
|
||||
/* Non-static methods */
|
||||
/**********************/
|
||||
@@ -54,6 +59,14 @@ class Router
|
||||
// Don't do anything, we don't want to be initialized
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool|null $bUseCache Force cache usage for testing purposes, or leave it null for the default behavior
|
||||
*/
|
||||
public function SetUseCache(?bool $bUseCache): void
|
||||
{
|
||||
$this->bUseCache = $bUseCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a complete URL for a specific route and optional parameters
|
||||
*
|
||||
@@ -137,7 +150,7 @@ class Router
|
||||
public function GetRoutes(): array
|
||||
{
|
||||
$aRoutes = [];
|
||||
$bUseCache = false === utils::IsDevelopmentEnvironment();
|
||||
$bUseCache = is_null($this->bUseCache) ? (false === utils::IsDevelopmentEnvironment()) : $this->bUseCache;
|
||||
$bMustWriteCache = false;
|
||||
$sCacheFilePath = $this->GetCacheFileAbsPath();
|
||||
|
||||
|
||||
@@ -46,13 +46,20 @@
|
||||
{% if aPage.aJsFiles is not empty %}
|
||||
{% block iboPageJsFiles %}
|
||||
<script type="text/javascript">
|
||||
{% if bHasOnInitOrOnDomReadyScripts == false %}
|
||||
// Define a dummy empty callback if there's no script to execute
|
||||
let fOnJsFilesLoaded{{ sId }} = function (fResolve) {
|
||||
fResolve();
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
window['{{ sPromiseId }}'] = new Promise(function (fAllJsFilesResolve, fAllJsFilesReject) {
|
||||
/**
|
||||
* @type {Array} aJsFilesToLoad Files required by the current \AjaxPage
|
||||
*
|
||||
* For each file:
|
||||
* - "id": Used as an identifier to check if file is already being handled
|
||||
* - "url" is the URL that will be use for loading. It should include any relevant query args, including the cache buster
|
||||
* - "url" is the URL that will be used for loading. It should include any relevant query args, including the cache buster
|
||||
*
|
||||
* ```
|
||||
* [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[infra]
|
||||
php_version=7.4-apache
|
||||
; STS version : testing greatest PHP version possible
|
||||
php_version=8.2-apache
|
||||
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
|
||||
db_version=5.7
|
||||
|
||||
|
||||
@@ -151,6 +151,12 @@ class DictionariesConsistencyAfterSetupTest extends ItopTestCase
|
||||
$aMismatchedKeys = [];
|
||||
|
||||
foreach ($aKeyArgsCountMap[$sReferenceLangCode] as $sKey => $iExpectedNbOfArgs){
|
||||
if (0 === $iExpectedNbOfArgs){
|
||||
//no arg needed in EN.
|
||||
//let s assume job has been done correctly in EN to simplify
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($sKey, self::$aLabelCodeNotToCheck)){
|
||||
//false positive: do not test
|
||||
continue;
|
||||
|
||||
@@ -17,6 +17,18 @@ namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper to load dictionnary files without altering the main dictionnary
|
||||
* Eval will be called within the current namespace (this is done by adding a "namespace" statement)
|
||||
*/
|
||||
class Dict
|
||||
{
|
||||
public static function Add($sLanguageCode, $sEnglishLanguageDesc, $sLocalizedLanguageDesc, $aEntries)
|
||||
{
|
||||
}
|
||||
}
|
||||
/**
|
||||
* For tests on compiled dict files, see {@see CompiledDictionariesConsistencyTest}
|
||||
* @group beforeSetup
|
||||
@@ -141,17 +153,40 @@ class DictionariesConsistencyTest extends ItopTestCase
|
||||
/**
|
||||
* @param string $sDictFile complete path for the file to check
|
||||
* @param bool $bIsSyntaxValid expected assert value
|
||||
*
|
||||
* @uses `php -l`
|
||||
* @uses \assertEquals()
|
||||
*/
|
||||
private function CheckDictionarySyntax(string $sDictFile, $bIsSyntaxValid = true): void
|
||||
{
|
||||
exec("php -l {$sDictFile}", $output, $return);
|
||||
|
||||
$bDictFileSyntaxOk = ($return === 0);
|
||||
|
||||
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
||||
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
||||
$sPHP = file_get_contents($sDictFile);
|
||||
// Strip php tag to allow "eval"
|
||||
$sPHP = substr(trim($sPHP), strlen('<?php'));
|
||||
// Make sure the Dict class is the one declared in the current file
|
||||
$sPHP = 'namespace '.__NAMESPACE__.";\n".$sPHP;
|
||||
$iLineShift = 1; // Cope with the shift due to the namespace statement
|
||||
$sPHP = str_replace(
|
||||
['ITOP_APPLICATION_SHORT', 'ITOP_APPLICATION', 'ITOP_VERSION_NAME'],
|
||||
['\'itop\'', '\'itop\'', '\'1.2.3\''],
|
||||
$sPHP
|
||||
);
|
||||
try {
|
||||
eval($sPHP);
|
||||
// Reaching this point => No syntax error
|
||||
if (!$bIsSyntaxValid) {
|
||||
$this->fail("Failed to detect syntax error in dictionary `{$sDictFile}` (which is known as being INCORRECT)");
|
||||
}
|
||||
}
|
||||
catch (\Error $e) {
|
||||
if ($bIsSyntaxValid) {
|
||||
$iLine = $e->getLine() - $iLineShift;
|
||||
$this->fail("Invalid dictionary: {$e->getMessage()} in {$sDictFile}:{$iLine}");
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
if ($bIsSyntaxValid) {
|
||||
$iLine = $e->getLine() - $iLineShift;
|
||||
$sExceptionClass = get_class($e);
|
||||
$this->fail("Exception thrown from dictionary: '$sExceptionClass: {$e->getMessage()}' in {$sDictFile}:{$iLine}");
|
||||
}
|
||||
}
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @used-by \Combodo\iTop\Test\UnitTest\Integration\DictionariesConsistencyTest::testPlaygroundDictionariesPhpSyntax
|
||||
*/
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'MyDictKey1' => 'l\'échappement : bon exemple 1',
|
||||
'MyDictKey1' => 'l\'échappement : bon exemple 1'.ITOP_APPLICATION,
|
||||
'MyDictKey2' => 'l\'échappement : bon exemple 2',
|
||||
'MyDictKey3' => 'l\'échappement : bon exemple 3',
|
||||
));
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||
bootstrap="unittestautoload.php"
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
columns="120"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnRisky="false"
|
||||
stopOnSkipped="false"
|
||||
verbose="true"
|
||||
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||
>
|
||||
|
||||
<extensions>
|
||||
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||
</extensions>
|
||||
|
||||
<php>
|
||||
<ini name="memory_limit" value="512M"/>
|
||||
<ini name="error_reporting" value="E_ALL"/>
|
||||
<ini name="display_errors" value="On"/>
|
||||
<ini name="log_errors" value="On"/>
|
||||
<ini name="html_errors" value="Off"/>
|
||||
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<!-- Unitary tests -->
|
||||
<testsuite name="Perf">
|
||||
<directory>perf-tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<!-- Code coverage white list -->
|
||||
<filter>
|
||||
<whitelist>
|
||||
<file>../../core/apc-emulation.php</file>
|
||||
<file>../../core/ormlinkset.class.inc.php</file>
|
||||
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
||||
@@ -1,93 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class BulkDBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testInsertAndReadPersonObjects()
|
||||
{
|
||||
echo "Part 1: Insert Persons\n";
|
||||
|
||||
$sOrgId = $this->getTestOrgId();
|
||||
$idx = 1;
|
||||
$fStart = microtime(true);
|
||||
$fMaxExecutionTimeAllowed = 40.0;
|
||||
$iInitialPeak = 0;
|
||||
$sInitialPeak = '';
|
||||
$fStartLoop = $fStart;
|
||||
for ($i = 0; $i < 3000; $i++) {
|
||||
$oPerson = $this->CreateObject(Person::class, ['org_id' => $sOrgId, 'name' => "Person_$i", 'first_name' => 'John']);
|
||||
if (0 == ($idx % 100)) {
|
||||
$fDuration = microtime(true) - $fStartLoop;
|
||||
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||
if ($iInitialPeak === 0) {
|
||||
$iInitialPeak = $iMemoryPeakUsage;
|
||||
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||
}
|
||||
|
||||
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx insert loops"); $fStartLoop = microtime(true);
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx insert loops)");
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
echo "Total duration: $sTotalDuration\n\n";
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||
|
||||
//////////////////////
|
||||
// Part 2 Fetch all the created persons
|
||||
echo "Part 1: Fetch Persons\n";
|
||||
|
||||
$oSearch = DBSearch::FromOQL('SELECT Person WHERE org_id=:org_id');
|
||||
$oSet = new DBObjectSet($oSearch, [], ['org_id' => $sOrgId]);
|
||||
$idx = 1;
|
||||
$iInitialPeak = 0;
|
||||
$sInitialPeak = '';
|
||||
$fMaxExecutionTimeAllowed = 0.5;
|
||||
$fStart = microtime(true);
|
||||
$fStartLoop = $fStart;
|
||||
while ($oContact = $oSet->Fetch()) {
|
||||
if (0 == ($idx % 100)) {
|
||||
$fDuration = microtime(true) - $fStartLoop;
|
||||
$iMemoryPeakUsage = memory_get_peak_usage();
|
||||
if ($iInitialPeak === 0) {
|
||||
$iInitialPeak = $iMemoryPeakUsage;
|
||||
$sInitialPeak = \utils::BytesToFriendlyFormat($iInitialPeak, 4);
|
||||
}
|
||||
|
||||
$sCurrPeak = \utils::BytesToFriendlyFormat($iMemoryPeakUsage, 4);
|
||||
echo "$idx ".sprintf('%.1f ms', $fDuration * 1000)." - Peak Memory Usage: $sCurrPeak\n";
|
||||
$this->assertTrue($iMemoryPeakUsage === $iInitialPeak, "Peak memory changed from $sInitialPeak to $sCurrPeak after $idx fetch loops");
|
||||
$fStartLoop = microtime(true);
|
||||
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed ($idx fetch loops)");
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
$fTotalDuration = microtime(true) - $fStart;
|
||||
$sTotalDuration = sprintf('%.3f s', $fTotalDuration);
|
||||
echo "Total duration: $sTotalDuration\n\n";
|
||||
$this->assertTrue($fTotalDuration < $fMaxExecutionTimeAllowed, "Total execution time $sTotalDuration should be < $fMaxExecutionTimeAllowed");
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||
bootstrap="unittestautoload.php"
|
||||
bootstrap="vendor/autoload.php"
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
columns="120"
|
||||
@@ -34,9 +34,6 @@
|
||||
|
||||
<testsuites>
|
||||
<!-- Unitary tests -->
|
||||
<testsuite name="Perf">
|
||||
<directory>perf-tests</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Application">
|
||||
<directory>unitary-tests/application</directory>
|
||||
</testsuite>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||
bootstrap="unittestautoload.php"
|
||||
bootstrap="vendor/autoload.php"
|
||||
backupGlobals="true"
|
||||
colors="true"
|
||||
columns="120"
|
||||
|
||||
@@ -87,7 +87,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
/**
|
||||
* @return string Absolute path to the {@see \Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase::GetTestEnvironment()} folder
|
||||
*/
|
||||
final private function GetTestEnvironmentFolderAbsPath(): string
|
||||
final protected function GetTestEnvironmentFolderAbsPath(): string
|
||||
{
|
||||
return APPROOT.'env-'.$this->GetTestEnvironment().'/';
|
||||
}
|
||||
@@ -97,7 +97,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final private function MarkEnvironmentReady(): void
|
||||
private function MarkEnvironmentReady(): void
|
||||
{
|
||||
if (false === $this->IsEnvironmentReady()) {
|
||||
touch(static::GetTestEnvironmentFolderAbsPath());
|
||||
@@ -109,7 +109,7 @@ abstract class ItopCustomDatamodelTestCase extends ItopDataTestCase
|
||||
*
|
||||
* @details Having the environment ready means that it has been compiled for this global tests run, not that it is a relic from a previous global tests run
|
||||
*/
|
||||
final private function IsEnvironmentReady(): bool
|
||||
final protected function IsEnvironmentReady(): bool
|
||||
{
|
||||
// As these test cases run in separate processes, the best way we found to let know a process if its environment was already prepared for **this run** was to compare the modification times of:
|
||||
// - its own env-<ENV> folder
|
||||
|
||||
@@ -146,6 +146,8 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
EventService::UnRegisterListener($sListenerId);
|
||||
}
|
||||
|
||||
CMDBObject::SetCurrentChange(null);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@@ -267,9 +269,7 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
$oMyObj->DBInsert();
|
||||
$iKey = $oMyObj->GetKey();
|
||||
$this->debug("Created $sClass::$iKey");
|
||||
if (!static::USE_TRANSACTION) {
|
||||
$this->aCreatedObjects[] = $oMyObj;
|
||||
}
|
||||
$this->aCreatedObjects[] = $oMyObj;
|
||||
|
||||
return $oMyObj;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ use SetupUtils;
|
||||
use const DEBUG_BACKTRACE_IGNORE_ARGS;
|
||||
|
||||
/**
|
||||
* Class ItopTestCase
|
||||
*
|
||||
* Helper class to extend for tests that DO NOT need to access the DataModel or the Database
|
||||
*
|
||||
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
|
||||
@@ -21,6 +23,7 @@ abstract class ItopTestCase extends TestCase
|
||||
{
|
||||
public const TEST_LOG_DIR = 'test';
|
||||
public static $DEBUG_UNIT_TEST = false;
|
||||
protected static $aBackupStaticProperties = [];
|
||||
|
||||
/**
|
||||
* @link https://docs.phpunit.de/en/9.6/annotations.html#preserveglobalstate PHPUnit `preserveGlobalState` annotation documentation
|
||||
@@ -302,6 +305,42 @@ abstract class ItopTestCase extends TestCase
|
||||
return $oProperty->getValue($oObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup every static property of the class (even protected ones)
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static function BackupStaticProperties($sClass)
|
||||
{
|
||||
$class = new \ReflectionClass($sClass);
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if (!$property->isStatic()) continue;
|
||||
$property->setAccessible(true);
|
||||
static::$aBackupStaticProperties[$sClass][$property->getName()] = $property->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore every static property of the class (even protected ones)
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public static function RestoreStaticProperties($sClass)
|
||||
{
|
||||
$class = new \ReflectionClass($sClass);
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if (!$property->isStatic()) continue;
|
||||
$property->setAccessible(true);
|
||||
$property->setValue(static::$aBackupStaticProperties[$sClass][$property->getName()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.7.10 3.1.0
|
||||
*/
|
||||
@@ -337,14 +376,14 @@ abstract class ItopTestCase extends TestCase
|
||||
$oProperty->setValue($value);
|
||||
}
|
||||
|
||||
public function RecurseRmdir($dir)
|
||||
public static function RecurseRmdir($dir)
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
if (is_dir($dir.DIRECTORY_SEPARATOR.$object)) {
|
||||
$this->RecurseRmdir($dir.DIRECTORY_SEPARATOR.$object);
|
||||
static::RecurseRmdir($dir.DIRECTORY_SEPARATOR.$object);
|
||||
} else {
|
||||
unlink($dir.DIRECTORY_SEPARATOR.$object);
|
||||
}
|
||||
@@ -354,7 +393,7 @@ abstract class ItopTestCase extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function CreateTmpdir() {
|
||||
public static function CreateTmpdir() {
|
||||
$sTmpDir=tempnam(sys_get_temp_dir(),'');
|
||||
if (file_exists($sTmpDir))
|
||||
{
|
||||
@@ -369,7 +408,7 @@ abstract class ItopTestCase extends TestCase
|
||||
return sys_get_temp_dir();
|
||||
}
|
||||
|
||||
public function RecurseMkdir($sDir){
|
||||
public static function RecurseMkdir($sDir){
|
||||
if (strpos($sDir, DIRECTORY_SEPARATOR) === 0){
|
||||
$sPath = DIRECTORY_SEPARATOR;
|
||||
} else {
|
||||
@@ -394,13 +433,13 @@ abstract class ItopTestCase extends TestCase
|
||||
|
||||
}
|
||||
|
||||
public function RecurseCopy($src,$dst) {
|
||||
public static function RecurseCopy($src,$dst) {
|
||||
$dir = opendir($src);
|
||||
@mkdir($dst);
|
||||
while(false !== ( $file = readdir($dir)) ) {
|
||||
if (( $file != '.' ) && ( $file != '..' )) {
|
||||
if ( is_dir($src . '/' . $file) ) {
|
||||
$this->RecurseCopy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
|
||||
static::RecurseCopy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
else {
|
||||
copy($src . DIRECTORY_SEPARATOR . $file,$dst . DIRECTORY_SEPARATOR . $file);
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
*/
|
||||
class ApplicationObjectExtensionTest extends \Combodo\iTop\Test\UnitTest\ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Application;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
* @covers LoginWebPage
|
||||
*/
|
||||
class LoginWebPageTest extends ItopDataTestCase
|
||||
{
|
||||
public function testProvisionUserOnly(){
|
||||
$iNum = uniqid();
|
||||
$sEmail = "ProvisionUser".$iNum . "@gabuzomeu.shadok";
|
||||
$oUser = \LoginWebPage::ProvisionUser($sEmail, null, [ 'Portal User']);
|
||||
|
||||
$this->assertNotNull($oUser);
|
||||
|
||||
$oUser = \MetaModel::GetObject(\UserExternal::class, $oUser->GetKey());
|
||||
$this->assertNotNull($oUser);
|
||||
$this->assertEquals($sEmail, $oUser->Get('login'));
|
||||
$this->assertEquals(\MetaModel::GetConfig()->GetDefaultLanguage(), $oUser->Get('language'));
|
||||
$this->assertEquals(0, $oUser->Get('contactid'));
|
||||
}
|
||||
|
||||
public function testProvisionUserWithPerson(){
|
||||
$iNum = uniqid();
|
||||
$this->CreateTestOrganization();
|
||||
$oPerson = $this->CreatePerson($iNum);
|
||||
|
||||
$sEmail = "ProvisionUser".$iNum . "@gabuzomeu.shadok";
|
||||
$oUser = \LoginWebPage::ProvisionUser($sEmail, $oPerson, [ 'Portal User']);
|
||||
|
||||
$this->assertNotNull($oUser);
|
||||
|
||||
$oUser = \MetaModel::GetObject(\UserExternal::class, $oUser->GetKey());
|
||||
$this->assertNotNull($oUser);
|
||||
$this->assertEquals($sEmail, $oUser->Get('login'));
|
||||
$this->assertEquals(\MetaModel::GetConfig()->GetDefaultLanguage(), $oUser->Get('language'));
|
||||
$this->assertEquals($oPerson->GetKey(), $oUser->Get('contactid'));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @runClassInSeparateProcess Required because PHPUnit outputs something earlier, thus causing the headers to be sent
|
||||
*/
|
||||
class SessionTest extends ItopTestCase
|
||||
{
|
||||
@@ -24,18 +24,9 @@ class SessionTest extends ItopTestCase
|
||||
|
||||
/**
|
||||
* @covers \Combodo\iTop\Application\Helper\Session::Start
|
||||
*/
|
||||
public function testStart()
|
||||
{
|
||||
$this->assertNull(Session::$iSessionId);
|
||||
Session::Start();
|
||||
$this->assertNotNull(Session::$iSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Combodo\iTop\Application\Helper\Session::WriteClose
|
||||
*/
|
||||
public function testWriteClose()
|
||||
public function testStartWriteClose()
|
||||
{
|
||||
$this->assertNull(Session::$iSessionId);
|
||||
Session::Start();
|
||||
|
||||
@@ -12,46 +12,65 @@ use ThemeHandler;
|
||||
class ThemeHandlerTest extends ItopTestCase
|
||||
{
|
||||
const PATTERN = '|\\\/var[^"]+testimages|';
|
||||
|
||||
|
||||
private $oCompileCSSServiceMock;
|
||||
private $sCompiledThemesDirAbsPath;
|
||||
private $sCssAbsPath;
|
||||
private $sDmCssAbsPath;
|
||||
private $sJsonThemeParamFile;
|
||||
private $sTmpDir;
|
||||
private $aDirsToCleanup= [];
|
||||
static private $sTmpDir = null;
|
||||
static private $aDirsToCleanup = [];
|
||||
static private $sAbsoluteImagePath;
|
||||
|
||||
|
||||
static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
|
||||
static::$sTmpDir = static::CreateTmpdir().'/';
|
||||
static::$aDirsToCleanup[] = static::$sTmpDir;
|
||||
|
||||
static::$sAbsoluteImagePath = APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/copied/testimages/';
|
||||
static::RecurseMkdir(static::$sAbsoluteImagePath);
|
||||
|
||||
// Required by testCompileThemesxxx - copy images in test dir
|
||||
static::RecurseCopy(APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/expected/testimages/', static::$sAbsoluteImagePath);
|
||||
|
||||
static::$aDirsToCleanup[] = dirname(static::$sAbsoluteImagePath);
|
||||
}
|
||||
|
||||
static function tearDownAfterClass(): void
|
||||
{
|
||||
foreach (static::$aDirsToCleanup as $sDir)
|
||||
{
|
||||
static::RecurseRmdir($sDir);
|
||||
}
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
|
||||
protected static function InitCSSDirectory()
|
||||
{
|
||||
static::RecurseCopy(APPROOT."/tests/php-unit-tests/unitary-tests/application/theme-handler/expected/css", static::$sTmpDir."/branding/css");
|
||||
}
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('application/themehandler.class.inc.php');
|
||||
$this->RequireOnceItopFile('setup/modelfactory.class.inc.php');
|
||||
$this->RequireOnceUnitTestFile('../setup/SubMFCompiler.php');
|
||||
|
||||
$this->oCompileCSSServiceMock = $this->createMock('CompileCSSService');
|
||||
ThemeHandler::mockCompileCSSService($this->oCompileCSSServiceMock);
|
||||
|
||||
$this->sTmpDir = $this->CreateTmpdir().'/';
|
||||
$this->aDirsToCleanup[] = $this->sTmpDir;
|
||||
|
||||
|
||||
$this->sCompiledThemesDirAbsPath = $this->sTmpDir."branding/themes/";
|
||||
$this->recurseMkdir($this->sCompiledThemesDirAbsPath."basque-red/");
|
||||
$this->sCompiledThemesDirAbsPath = static::$sTmpDir."branding/themes/";
|
||||
static::RecurseMkdir($this->sCompiledThemesDirAbsPath."basque-red/");
|
||||
$this->sCssAbsPath = $this->sCompiledThemesDirAbsPath.'basque-red/main.css';
|
||||
$this->sDmCssAbsPath = $this->sCompiledThemesDirAbsPath.'datamodel-compiled-scss-rules.scss';
|
||||
$this->sJsonThemeParamFile = $this->sCompiledThemesDirAbsPath.'basque-red/theme-parameters.json';
|
||||
$this->RecurseCopy(APPROOT."/tests/php-unit-tests/unitary-tests/application/theme-handler/expected/css", $this->sTmpDir."/branding/css");
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
foreach ($this->aDirsToCleanup as $sDir)
|
||||
{
|
||||
echo $sDir;
|
||||
$this->RecurseRmdir($sDir);
|
||||
}
|
||||
}
|
||||
|
||||
function KeepSignatureDiff($sSignature1, $sSignature2) : string {
|
||||
@@ -83,14 +102,14 @@ class ThemeHandlerTest extends ItopTestCase
|
||||
$aDiffOuput[$sKey] = $aCurrentDiffVal;
|
||||
}
|
||||
} else if ($oVal1 !== $aSignature2[$sKey]){
|
||||
$aDiffOuput[$sKey] = "expected:$oVal1 | actual:$aSignature2[$sKey]";
|
||||
$aDiffOuput[$sKey] = "expected:$oVal1 | actual:$aSignature2[$sKey]";
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode($aDiffOuput, true);
|
||||
}
|
||||
|
||||
function recurseMkdir($dir)
|
||||
public static function RecurseMkdir($dir)
|
||||
{
|
||||
if (is_dir($dir))
|
||||
{
|
||||
@@ -98,7 +117,7 @@ class ThemeHandlerTest extends ItopTestCase
|
||||
}
|
||||
|
||||
$sParentDir = dirname($dir);
|
||||
if (!$this->recurseMkdir($sParentDir))
|
||||
if (!static::RecurseMkdir($sParentDir))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -141,6 +160,8 @@ JSON;
|
||||
*/
|
||||
public function testCompileThemeWithoutCssFile_FocusOnParamAttribute($readFromParamAttributeFromJson=false)
|
||||
{
|
||||
static::InitCSSDirectory();
|
||||
|
||||
$sExpectJsonFilePath = APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/expected/themes/basque-red/theme-parameters.json';
|
||||
$sExpectedThemeParamJson = file_get_contents($sExpectJsonFilePath);
|
||||
$aThemeParameters = json_decode($sExpectedThemeParamJson, true);
|
||||
@@ -160,11 +181,11 @@ JSON;
|
||||
if($readFromParamAttributeFromJson)
|
||||
{
|
||||
copy($sExpectJsonFilePath, $this->sJsonThemeParamFile);
|
||||
$this->assertTrue(ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", null, [$this->sTmpDir.'/branding/themes/'], $this->sTmpDir));
|
||||
$this->assertTrue(ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", null, [static::$sTmpDir.'/branding/themes/'], static::$sTmpDir));
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->assertTrue(ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", $aThemeParameters, [$this->sTmpDir.'/branding/themes/'], $this->sTmpDir));
|
||||
$this->assertTrue(ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", $aThemeParameters, [static::$sTmpDir.'/branding/themes/'], static::$sTmpDir));
|
||||
}
|
||||
$this->assertTrue(is_file($this->sCssAbsPath));
|
||||
$this->assertEquals($sExpectedThemeParamJson, file_get_contents($this->sJsonThemeParamFile));
|
||||
@@ -189,14 +210,14 @@ JSON;
|
||||
*/
|
||||
public function testCompileThemesEmptyArray($ThemeParametersJson, $CompileCount=0)
|
||||
{
|
||||
$sCssPath = $this->sTmpDir . '/branding/themes/basque-red/main.css';
|
||||
$sCssPath = static::$sTmpDir . '/branding/themes/basque-red/main.css';
|
||||
copy(APPROOT . 'tests/php-unit-tests/unitary-tests/application/theme-handler/expected/themes/basque-red/main.css', $sCssPath);
|
||||
|
||||
$this->oCompileCSSServiceMock->expects($this->exactly($CompileCount))
|
||||
->method("CompileCSSFromSASS")
|
||||
->willReturn("====CSSCOMPILEDCONTENT====");
|
||||
|
||||
$this->assertEquals($CompileCount!=0,ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", json_decode($ThemeParametersJson, true), [$this->sTmpDir.'/branding/themes/'], $this->sTmpDir));
|
||||
$this->assertEquals($CompileCount!=0,ThemeHandler::CompileTheme('basque-red', true, "COMPILATIONTIMESTAMP", json_decode($ThemeParametersJson, true), [static::$sTmpDir.'/branding/themes/'], static::$sTmpDir));
|
||||
}
|
||||
|
||||
public function CompileThemesProviderEmptyArray()
|
||||
@@ -213,6 +234,14 @@ JSON;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* mixed $ThemeParametersJson
|
||||
* int $iCompileCSSFromSASSCount
|
||||
* boolean $bMissingFile
|
||||
* boolean $bFilesTouchedRecently
|
||||
* boolean $bFileMd5sumModified
|
||||
* null $sFileToTest
|
||||
* null $sExpectedMainCssPath
|
||||
* bool $bSetup
|
||||
*/
|
||||
public function CompileThemesProvider()
|
||||
{
|
||||
@@ -260,26 +289,24 @@ JSON;
|
||||
*/
|
||||
public function testCompileThemes($ThemeParametersJson, $iCompileCSSFromSASSCount, $bMissingFile=false, $bFilesTouchedRecently=false, $bFileMd5sumModified=false, $sFileToTest=null, $sExpectedMainCssPath=null, $bSetup=true)
|
||||
{
|
||||
static::InitCSSDirectory();
|
||||
|
||||
$sAfterReplacementCssVariableMd5sum='';
|
||||
if (is_file($this->sTmpDir.'/'.$sFileToTest))
|
||||
if (is_file(static::$sTmpDir.'/'.$sFileToTest))
|
||||
{
|
||||
$sFileToTest = $this->sTmpDir.'/'.$sFileToTest;
|
||||
$sFileToTest = static::$sTmpDir.'/'.$sFileToTest;
|
||||
} else {
|
||||
$sFileToTest = APPROOT.'/'.$sFileToTest;
|
||||
}
|
||||
|
||||
//copy images in test dir
|
||||
$sAbsoluteImagePath = APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/copied/testimages/';
|
||||
$this->recurseMkdir($sAbsoluteImagePath);
|
||||
$this->aDirsToCleanup[] = dirname($sAbsoluteImagePath);
|
||||
$this->RecurseCopy(APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/expected/testimages/', $sAbsoluteImagePath);
|
||||
// Backup the file to test
|
||||
copy($sFileToTest, static::$sTmpDir.'/file-to-test-backup');
|
||||
|
||||
//change approot-relative in css-variable to use absolute path
|
||||
$sCssVarPath = $this->sTmpDir."/branding/css/DO_NOT_CHANGE.css-variables.scss";
|
||||
$sCssVarPath = static::$sTmpDir."/branding/css/DO_NOT_CHANGE.css-variables.scss";
|
||||
$sBeforeReplacementCssVariableMd5sum = md5_file($sCssVarPath);
|
||||
echo 'BEFORE :'.$sBeforeReplacementCssVariableMd5sum.' '.$sCssVarPath.' ';
|
||||
$sCssVariableContent = file_get_contents($sCssVarPath);
|
||||
$sLine = '$approot-relative: "'.$sAbsoluteImagePath.'" !default;';
|
||||
$sLine = '$approot-relative: "'.static::$sAbsoluteImagePath.'" !default;';
|
||||
$sCssVariableContent = preg_replace("/\\\$approot-relative: \"(.*)\"/", $sLine, $sCssVariableContent);
|
||||
file_put_contents($sCssVarPath, $sCssVariableContent);
|
||||
if ($bMissingFile)
|
||||
@@ -296,30 +323,23 @@ JSON;
|
||||
//change cssvar md5sum + image absolute paths
|
||||
$sMainCssContent = file_get_contents(APPROOT."tests/php-unit-tests/unitary-tests/application/theme-handler/expected/themes/basque-red/main_testcompilethemes.css");
|
||||
$sMainCssContent = preg_replace('/MD5SUM/', $sAfterReplacementCssVariableMd5sum, $sMainCssContent);
|
||||
$sReplacement = rtrim($sAbsoluteImagePath, '/');
|
||||
$sReplacement = rtrim(static::$sAbsoluteImagePath, '/');
|
||||
$sReplacement=preg_replace('|\/|', '\/', $sReplacement);
|
||||
$sMainCssContent = preg_replace(static::PATTERN, $sReplacement, $sMainCssContent);
|
||||
$cssPath = $this->sTmpDir . '/branding/themes/basque-red/main.css';
|
||||
echo 'PUT md5sum: '.$sAfterReplacementCssVariableMd5sum.' in '.$cssPath.' ';
|
||||
$cssPath = static::$sTmpDir . '/branding/themes/basque-red/main.css';
|
||||
file_put_contents($cssPath, $sMainCssContent);
|
||||
|
||||
//should be after main.css modification to make sure precompilation check will be performed
|
||||
if ($bFilesTouchedRecently)
|
||||
{
|
||||
sleep(1);
|
||||
touch($sFileToTest);
|
||||
touch($sFileToTest, time() + 2, time() + 2);
|
||||
}
|
||||
|
||||
//same: it should be after main.css modification
|
||||
if ($bFileMd5sumModified)
|
||||
{
|
||||
$sMd5sum = md5_file($sFileToTest);
|
||||
echo ' BEFORE touch: ' . $sMd5sum .' ' . $sFileToTest;
|
||||
sleep(1);
|
||||
file_put_contents($sFileToTest, "###\n".file_get_contents($sFileToTest));
|
||||
|
||||
$sMd5sum = md5_file($sFileToTest);
|
||||
echo ' AFTER touch: ' . $sMd5sum .' ' . $sFileToTest;
|
||||
touch($sFileToTest, time() + 2, time() + 2);
|
||||
}
|
||||
|
||||
if (is_file($sCssVarPath))
|
||||
@@ -332,7 +352,7 @@ JSON;
|
||||
->willReturn("====CSSCOMPILEDCONTENT====");
|
||||
|
||||
$aThemeParameters = json_decode($ThemeParametersJson, true);
|
||||
$this->assertEquals($iCompileCSSFromSASSCount!=0, ThemeHandler::CompileTheme('basque-red', $bSetup, "COMPILATIONTIMESTAMP", $aThemeParameters, [$this->sTmpDir.'/branding/themes/'], $this->sTmpDir));
|
||||
$this->assertEquals($iCompileCSSFromSASSCount!=0, ThemeHandler::CompileTheme('basque-red', $bSetup, "COMPILATIONTIMESTAMP", $aThemeParameters, [static::$sTmpDir.'/branding/themes/'], static::$sTmpDir));
|
||||
|
||||
if ($iCompileCSSFromSASSCount==1)
|
||||
{
|
||||
@@ -347,9 +367,11 @@ JSON;
|
||||
$aReplacements = [$sReplacement, $sAfterReplacementCssVariableMd5sum];
|
||||
$aReplacements[] = md5(json_encode($aThemeParameters['variables']));
|
||||
$aReplacements[] = $sAfterReplacementCssVariableMd5sum;
|
||||
var_dump($aReplacements);
|
||||
$this->DoInnerJsonValidation($sExpectedMainCssFile, $cssPath, $aPatterns, $aReplacements);
|
||||
}
|
||||
|
||||
// Restore the file to test (possible improvement: do that in tearDown)
|
||||
copy(static::$sTmpDir.'/file-to-test-backup', $sFileToTest);
|
||||
}
|
||||
|
||||
public function DoInnerJsonValidation($sExpectedCssFile, $sActualCssFile, $aPatterns, $aReplacements)
|
||||
@@ -396,8 +418,8 @@ JSON;
|
||||
'\'abc/\'+ $approot-relative + "css/ui-lightness/images/toutou.png?v=" + $version',
|
||||
"\$approot-relative + \"css/ui-lightness/images/toto.png?v=\" + \$version",
|
||||
'$approot-relative + \'css/ui-lightness/images/titi.gif?v=\' + $version1',
|
||||
'"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7?v=" + $version',
|
||||
'$approot-relative + \'node_modules/raleway-webfont/fonts/Raleway-Thin.jpeg\'',
|
||||
'"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7?v=" + $version',
|
||||
'$approot-relative + \'node_modules/raleway-webfont/fonts/Raleway-Thin.jpeg\'',
|
||||
];
|
||||
|
||||
$aIncludedUrls['aCompleteUrls'];
|
||||
@@ -432,13 +454,13 @@ SCSS;
|
||||
$aExpectedFoundVariables = [
|
||||
'gabu' => 'zomeu',
|
||||
'toto' => 'titi',
|
||||
'approot-relative' => '../../../../../',
|
||||
'approot-relative2' => '../../',
|
||||
'gray-base' => '#000',
|
||||
'a' => 'b',
|
||||
'content-color' => '#eeeeee',
|
||||
'default-font-family' => 'Trebuchet MS,Tahoma,Verdana,Arial,sans-serif',
|
||||
'icons-filter' => 'hue-rotate(0deg)',
|
||||
'approot-relative' => '../../../../../',
|
||||
'approot-relative2' => '../../',
|
||||
'gray-base' => '#000',
|
||||
'a' => 'b',
|
||||
'content-color' => '#eeeeee',
|
||||
'default-font-family' => 'Trebuchet MS,Tahoma,Verdana,Arial,sans-serif',
|
||||
'icons-filter' => 'hue-rotate(0deg)',
|
||||
'toto' => 'titi',
|
||||
];
|
||||
$this->assertEquals($aExpectedFoundVariables, $aFoundVariables);
|
||||
@@ -460,10 +482,10 @@ $icons-filter: hue-rotate(0deg) !default;
|
||||
$toto : titi;
|
||||
SCSS;
|
||||
|
||||
file_put_contents($this->sTmpDir . DIRECTORY_SEPARATOR . 'css-variable.scss', $sContent);
|
||||
file_put_contents(static::$sTmpDir . DIRECTORY_SEPARATOR . 'css-variable.scss', $sContent);
|
||||
$aVariables = ThemeHandler::GetVariablesFromFile(
|
||||
[ 'css-variable.scss' ],
|
||||
[ $this->sTmpDir ]
|
||||
[ static::$sTmpDir ]
|
||||
);
|
||||
|
||||
$aExpectedVariables = [
|
||||
@@ -512,8 +534,10 @@ SCSS;
|
||||
|
||||
public function testGetIncludedImages()
|
||||
{
|
||||
$aStylesheetFile=glob($this->sTmpDir."/branding/css/*.scss");
|
||||
$aStylesheetFile[]=$this->sTmpDir."/branding/css/ui-lightness/DO_NOT_CHANGE.jqueryui.scss";
|
||||
static::InitCSSDirectory();
|
||||
|
||||
$aStylesheetFile=glob(static::$sTmpDir."/branding/css/*.scss");
|
||||
$aStylesheetFile[]=static::$sTmpDir."/branding/css/ui-lightness/DO_NOT_CHANGE.jqueryui.scss";
|
||||
$expectJsonFilePath = APPROOT.'tests/php-unit-tests/unitary-tests/application/theme-handler/expected/themes/basque-red/theme-parameters.json';
|
||||
$expectedThemeParamJson = file_get_contents($expectJsonFilePath);
|
||||
$aThemeParametersVariables = json_decode($expectedThemeParamJson, true);
|
||||
@@ -538,7 +562,7 @@ SCSS;
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testFindStylesheetFile(string $sFileToFind, array $aAllImports){
|
||||
$sImportsPath = $this->sTmpDir.'branding/';
|
||||
$sImportsPath = static::$sTmpDir.'branding/';
|
||||
|
||||
// Windows compat O:)
|
||||
$sFileToFind = $this->UpdateDirSep($sFileToFind);
|
||||
|
||||
@@ -36,20 +36,18 @@ class ApplicationExtensionTest extends ItopCustomDatamodelTestCase
|
||||
* - Add the API to the provider
|
||||
* - Add a class extending / implementing the API in ./Delta/application-extension-usages-in-snippets.xml
|
||||
*
|
||||
* @param string $sAPIFQCN
|
||||
* @param string $sCallMethod
|
||||
*
|
||||
* @return void
|
||||
* @dataProvider ExtensionAPIRegisteredAndCalledProvider
|
||||
*/
|
||||
public function testExtensionAPIRegisteredAndCalled(string $sAPIFQCN, string $sCallMethod)
|
||||
public function testExtensionAPIRegisteredAndCalled()
|
||||
{
|
||||
if ($sCallMethod === static::ENUM_API_CALL_METHOD_ENUMPLUGINS) {
|
||||
$iExtendingClassesCount = count(MetaModel::EnumPlugins($sAPIFQCN));
|
||||
} else {
|
||||
$iExtendingClassesCount = count(utils::GetClassesForInterface($sAPIFQCN, '', ['[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]']));
|
||||
foreach ($this->ExtensionAPIRegisteredAndCalledProvider() as list($sAPIFQCN, $sCallMethod)) {
|
||||
if ($sCallMethod === static::ENUM_API_CALL_METHOD_ENUMPLUGINS) {
|
||||
$iExtendingClassesCount = count(MetaModel::EnumPlugins($sAPIFQCN));
|
||||
} else {
|
||||
$iExtendingClassesCount = count(utils::GetClassesForInterface($sAPIFQCN, '', ['[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]']));
|
||||
}
|
||||
$this->assertGreaterThan(0, $iExtendingClassesCount, "Found no class extending the $sAPIFQCN API");
|
||||
}
|
||||
$this->assertGreaterThan(0, $iExtendingClassesCount, "Found no class extending the $sAPIFQCN API");
|
||||
}
|
||||
|
||||
public function ExtensionAPIRegisteredAndCalledProvider(): array
|
||||
|
||||
@@ -77,9 +77,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*/
|
||||
public function testProcessClassIdDeferredUpdate()
|
||||
{
|
||||
// Create the team
|
||||
@@ -157,7 +154,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are not sent to the current updated/created object (Team)
|
||||
* the events are sent to the other side (Person)
|
||||
*
|
||||
@@ -201,8 +197,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
*
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when creating a new lnk object
|
||||
*
|
||||
* @return void
|
||||
@@ -238,7 +232,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when updating an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
@@ -281,7 +274,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that when a link changes from an object to another, then both objects are notified
|
||||
*
|
||||
* @return void
|
||||
@@ -325,7 +317,6 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when deleting an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
|
||||
@@ -25,7 +25,6 @@ use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use utils;
|
||||
|
||||
/**
|
||||
* @runClassInSeparateProcess
|
||||
* @covers utils
|
||||
*/
|
||||
class utilsTest extends ItopTestCase
|
||||
@@ -245,7 +244,6 @@ class utilsTest extends ItopTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @runInSeparateProcess
|
||||
* @dataProvider GetAbsoluteUrlAppRootPersistency
|
||||
*/
|
||||
public function testGetAbsoluteUrlAppRootPersistency($bBehindReverseProxy,$bForceTrustProxy1 ,$sExpectedAppRootUrl1,$bForceTrustProxy2 , $sExpectedAppRootUrl2,$bForceTrustProxy3 , $sExpectedAppRootUrl3)
|
||||
@@ -274,6 +272,9 @@ class utilsTest extends ItopTestCase
|
||||
$this->assertEquals($sExpectedAppRootUrl2, utils::GetAbsoluteUrlAppRoot($bForceTrustProxy2));
|
||||
|
||||
$this->assertEquals($sExpectedAppRootUrl3, utils::GetAbsoluteUrlAppRoot($bForceTrustProxy3));
|
||||
|
||||
// Leave the place clean
|
||||
static::SetNonPublicStaticProperty('utils', 'm_sAppRootUrl', null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Change;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
use UserRequest;
|
||||
|
||||
class AttributeDefinitionTest extends ItopDataTestCase {
|
||||
const CREATE_TEST_ORG = true;
|
||||
@@ -55,6 +57,23 @@ class AttributeDefinitionTest extends ItopDataTestCase {
|
||||
{
|
||||
// Note: This is test is not great as we are datamodel dependent and don't have a class with all the attribute types
|
||||
return [
|
||||
'AttributeDateTime' => [
|
||||
Change::class,
|
||||
'start_date', // no default value on this field
|
||||
<<<PHP
|
||||
\$oObject->Set('start_date', '2023-09-06 12:26:00');
|
||||
PHP
|
||||
,
|
||||
false,
|
||||
true,
|
||||
],
|
||||
'AttributeFrienlyName' => [
|
||||
UserRequest::class,
|
||||
'friendlyname',
|
||||
'',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'AttributeDashboard' => [
|
||||
'Organization',
|
||||
'overview',
|
||||
@@ -158,9 +177,9 @@ PHP
|
||||
'AttributeSubItem' => [
|
||||
'UserRequest',
|
||||
'tto_escalation_deadline',
|
||||
'',
|
||||
true,
|
||||
true,
|
||||
'', // read-only attribute
|
||||
false,
|
||||
false,
|
||||
],
|
||||
'AttributeOneWayPassword' => [
|
||||
'UserLocal',
|
||||
|
||||
@@ -6,10 +6,8 @@ use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
/**
|
||||
* @runClassInSeparateProcess
|
||||
*/
|
||||
class BulkChangeTest extends ItopDataTestCase {
|
||||
class BulkChangeTest extends ItopDataTestCase
|
||||
{
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
protected function setUp(): void
|
||||
@@ -57,8 +55,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
true // localize
|
||||
);
|
||||
|
||||
$oChange = \CMDBObject::GetCurrentChange();
|
||||
$aRes = $oBulk->Process($oChange);
|
||||
$aRes = $oBulk->Process();
|
||||
static::assertNotNull($aRes);
|
||||
|
||||
foreach ($aRes as $aRow) {
|
||||
@@ -94,8 +91,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
true // localize
|
||||
);
|
||||
|
||||
$oChange = \CMDBObject::GetCurrentChange();
|
||||
$aRes = $oBulk->Process($oChange);
|
||||
$aRes = $oBulk->Process();
|
||||
static::assertNotNull($aRes);
|
||||
|
||||
foreach ($aRes as $aRow) {
|
||||
@@ -175,7 +171,6 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
* @param $aReconcilKeys
|
||||
*/
|
||||
public function testCas1BulkChangeIssue($aInitData, $aCsvData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
@@ -193,8 +188,6 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||
}
|
||||
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
|
||||
$oBulk = new \BulkChange(
|
||||
"Server",
|
||||
$aCsvData,
|
||||
@@ -206,13 +199,8 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
"Y-m-d H:i:s", // date format
|
||||
true // localize
|
||||
);
|
||||
$this->debug("BulkChange:");
|
||||
$oChange = \CMDBObject::GetCurrentChange();
|
||||
$this->debug("GetCurrentChange:");
|
||||
$aRes = $oBulk->Process($oChange);
|
||||
$this->debug("Process:");
|
||||
$aRes = $oBulk->Process();
|
||||
static::assertNotNull($aRes);
|
||||
$this->debug("assertNotNull:");
|
||||
foreach ($aRes as $aRow) {
|
||||
if (array_key_exists('__STATUS__', $aRow)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
@@ -232,7 +220,6 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
||||
}
|
||||
}
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',$db_core_transactions_enabled);
|
||||
}
|
||||
|
||||
@@ -378,7 +365,6 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
* @param $aReconcilKeys
|
||||
*/
|
||||
public function testCas2BulkChangeIssue($aInitData, $aCsvData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
@@ -409,8 +395,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
"Y-m-d H:i:s", // date format
|
||||
true // localize
|
||||
);
|
||||
$oChange = \CMDBObject::GetCurrentChange();
|
||||
$aRes = $oBulk->Process($oChange);
|
||||
$aRes = $oBulk->Process();
|
||||
static::assertNotNull($aRes);
|
||||
foreach ($aRes as $aRow) {
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
@@ -429,7 +414,6 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
}
|
||||
$this->assertEquals($aResult[0], $aRow[0]->GetDisplayableValue());
|
||||
}
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',$db_core_transactions_enabled);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user