Compare commits

..

3 Commits

784 changed files with 31601 additions and 47607 deletions

View File

@@ -3,14 +3,14 @@
```mermaid
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true,'mainBranchName': 'develop','rotateCommitLabel': true}} }%%
gitGraph
commit id: "2016-07-06" tag: "2.3.0" type: HIGHLIGHT
commit id: "2016-07-06" tag: "2.3.0"
branch support/2.3 order: 900
commit id: "2016-07-08" tag: "2.3.1"
commit id: "2016-12-22" tag: "2.3.3"
commit id: "2017-04-14" tag: "2.3.4"
checkout develop
commit id: "2017-07-12" tag: "2.4.0-beta" type: REVERSE
commit id: "2017-11-16" tag: "2.4.0" type: HIGHLIGHT
commit id: "2017-11-16" tag: "2.4.0"
branch support/2.4 order: 890
commit id: "2018-02-14" tag: "2.4.1"
checkout develop
@@ -18,10 +18,10 @@ gitGraph
checkout support/2.4
commit id: "2018-06-14" tag: "2.4.2"
checkout develop
commit id: "2018-06-27" tag: "2.5.0" type: HIGHLIGHT
commit id: "2018-06-27" tag: "2.5.0"
branch support/2.5 order: 880
checkout develop
commit id: "2019-01-09" tag: "2.6.0" type: HIGHLIGHT
commit id: "2019-01-09" tag: "2.6.0"
branch support/2.6 order: 870
commit id: "2019-03-28" tag: "2.6.1"
checkout develop
@@ -32,11 +32,11 @@ gitGraph
commit id: "2020-01-23" tag: "2.6.3"
checkout develop
commit id: "2020-01-29" tag: "2.7.0-beta2" type: REVERSE
commit id: "2020-04-01" tag: "2.7.0-1" type: HIGHLIGHT
branch support/2.7 order: 860
commit id: "2020-04-01" tag: "2.7.0-1"
checkout support/2.6
commit id: "2020-04-22" tag: "2.6.4"
checkout develop
branch support/2.7 order: 860
checkout support/2.7
commit id: "2020-06-26" tag: "2.7.1"
checkout support/2.7
commit id: "2020-12-09" tag: "2.7.3"
@@ -50,7 +50,7 @@ gitGraph
checkout support/2.7
commit id: "2021-12-17" tag: "2.7.6"
checkout develop
commit id: "2022-01-04" tag: "3.0.0" type: HIGHLIGHT
commit id: "2022-01-04" tag: "3.0.0"
branch support/3.0 order: 850
commit id: "2022-04-08" tag: "3.0.1"
checkout support/2.7
@@ -58,8 +58,4 @@ gitGraph
checkout support/3.0
commit id: "2022-09-12" tag: "3.0.2-1"
checkout develop
checkout support/2.7
commit id: "2022-12-28" tag: "2.7.8"
```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

11
.gitignore vendored
View File

@@ -19,7 +19,7 @@
# composer reserver directory, from sources, populate/update using "composer install"
vendor/*
tests/*/vendor/*
test/vendor/*
# all conf but listing prevention
/conf/**
@@ -46,7 +46,7 @@ tests/*/vendor/*
!/log/web.config
# PHPUnit cache file
/tests/php-unit-tests/.phpunit.result.cache
/test/.phpunit.result.cache
# Jetbrains
@@ -146,10 +146,3 @@ local.properties
.cache-main
.scala_dependencies
.worksheet
# Mac
.DS_Store
# Windows
Thumbs.db

View File

@@ -6,19 +6,11 @@
* Will update version in the following files :
*
* datamodels/2.x/.../datamodel.*.xml
* application/*.xml
* core/*.xml
*
* Usage :
* `php .make\release\update-xml.php "1.7"`
* `php .make\release\update-xml.php`
*
* If no parameter provided then the current XML version will be used as target version
*
* @since 2.7.0 simple version change using regexp (not doing conversion)
* @since 3.1.0 N°5405 now does a real conversion
* @since 3.1.0 N°5633 allow to use without parameter
* @since 3.1.0 N°5633 add /application and /core XML files
* @since 2.7.0
******************************************************************************/
@@ -30,12 +22,10 @@ require_once (__DIR__.DIRECTORY_SEPARATOR.'update.classes.inc.php');
if (count($argv) === 1)
{
echo '/!\ No version passed: assuming target XML version is current XML version ('.ITOP_DESIGN_LATEST_VERSION.")\n";
$sVersionLabel = ITOP_DESIGN_LATEST_VERSION;
} else {
$sVersionLabel = $argv[1];
echo '/!\ You must pass the new version as parameter';
exit(1);
}
$sVersionLabel = $argv[1];
if (empty($sVersionLabel))
{
echo 'Version passed as parameter is empty !';

View File

@@ -125,31 +125,16 @@ class iTopVersionFileUpdater extends AbstractSingleFileVersionUpdater
abstract class AbstractGlobFileVersionUpdater extends FileVersionUpdater
{
/** @var array|string glob patterns to seek for files to modify */
protected $globPattern;
protected $sGlobPattern;
public function __construct($globPattern)
public function __construct($sGlobPattern)
{
$this->globPattern = $globPattern;
$this->sGlobPattern = $sGlobPattern;
}
public function GetFiles()
{
$aGlobPatterns = (is_array($this->globPattern))
? $this->globPattern
: [$this->globPattern];
$aFiles = [];
foreach ($aGlobPatterns as $sGlobPattern) {
$result = glob($sGlobPattern);
if (false === $result) {
continue;
}
/** @noinspection SlowArrayOperationsInLoopInspection */
$aFiles = array_merge($aFiles, $result);
}
return $aFiles;
return glob($this->sGlobPattern);
}
}
@@ -181,11 +166,7 @@ class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
{
public function __construct()
{
parent::__construct([
APPROOT.'datamodels/2.x/*/datamodel.*.xml',
APPROOT.'application/*.xml',
APPROOT.'core/*.xml',
]);
parent::__construct(APPROOT.'datamodels/2.x/*/datamodel.*.xml');
}
/**
@@ -193,40 +174,10 @@ class DatamodelsXmlFiles extends AbstractGlobFileVersionUpdater
*/
public function UpdateFileContent($sVersionLabel, $sFileContent, $sFileFullPath)
{
require_once APPROOT.'setup/itopdesignformat.class.inc.php';
$oFileXml = new DOMDocument();
/** @noinspection PhpComposerExtensionStubsInspection */
libxml_clear_errors();
$oFileXml->formatOutput = true;
$oFileXml->preserveWhiteSpace = false;
$oFileXml->loadXML($sFileContent);
$oFileItopFormat = new iTopDesignFormat($oFileXml);
$sDesignVersionToSet = static::GetDesignVersionToSet($oFileItopFormat->GetVersion());
if (false === is_null($sDesignVersionToSet)) {
// N°5779 if same as target version, we will try to convert from version below
$oFileItopFormat->GetITopDesignNode()->setAttribute('version', $sDesignVersionToSet);
}
$bConversionResult = $oFileItopFormat->Convert($sVersionLabel);
if (false === $bConversionResult) {
throw new Exception("Error when converting $sFileFullPath");
}
return $oFileItopFormat->GetXmlAsString();
}
/**
* @return ?string version to use : if file version is same as current version then return previous version, else return null
* @since 3.1.0 N°5779
*/
protected static function GetDesignVersionToSet($sFileDesignVersion):?string {
if ($sFileDesignVersion !== ITOP_DESIGN_LATEST_VERSION) {
return null;
}
return iTopDesignFormat::GetPreviousDesignVersion(ITOP_DESIGN_LATEST_VERSION);
return preg_replace(
'/(<itop_design .* version=")[^"]+(">)/',
'${1}'.$sVersionLabel.'${2}',
$sFileContent
);
}
}

View File

@@ -134,16 +134,15 @@ Our tests are located in the `test/` directory, containing a PHPUnit config file
When your code is working, please:
* Squash as much as possible your commits,
* Rebase your branch on our repo last commit,
* Create a pull request. _Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/)_.
* Pull request description: mind to add all the information useful to understand why you're suggesting this modification and anything necessary to dive into your work. Especially:
- Bugfixes: exact steps to reproduce the bug (given/when/then), description of the bug cause and what solution is implemented
- Enhancements: use cases, implementation details if needed
* Mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option !
* stash as much as possible your commits,
* rebase your branch on our repo last commit,
* create a pull request
* mind to check the "[Allow edits from maintainers](https://docs.github.com/en/github-ae@latest/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)" option !
Detailed procedure to work on fork and create PR is available [in GitHub help pages](https://help.github.com/articles/creating-a-pull-request-from-a-fork/).
## 🙏 We are thankful
### 🙏 We are thankful
We are thankful for all your contributions to the iTop universe! As a thank you gift, we will send stickers to every iTop (& extensions) contributors!

8
Jenkinsfile vendored
View File

@@ -1,14 +1,6 @@
def infra
node(){
properties([
buildDiscarder(
logRotator(
daysToKeepStr: "28",
numToKeepStr: "500")
)
])
checkout scm
infra = load '/var/lib/jenkins/workspace/itop-test-infra_master/src/Infra.groovy'

View File

@@ -3,9 +3,9 @@
</a></p>
iTop stands for IT Operations Portal. It is a complete open source and web-based IT service management platform, including a fully customizable CMDB, a helpdesk system, and a document management tool. It is ITIL compliant and easily customizable and extensible thanks to a high number of add-ons and web services to integrate with your IT.
iTop stands for IT Operations Portal. It is a complete open source and web based IT service management platform including a fully customizable CMDB, a helpdesk system and a document management tool. It is ITIL compliant and easily customizable and extensible thanks to a high number of adds-on and web services to integrate with your IT.
iTop also offers mass import tools to help you become even more efficient.
iTop also offers mass import tools to help you being even more efficient.
## Features
- Fully configurable [Configuration Management (CMDB)][10]
@@ -65,7 +65,7 @@ iTop also offers mass import tools to help you become even more efficient.
## About Us
iTop development is sponsored, led, and supported by [Combodo][0].
iTop development is sponsored, led and supported by [Combodo][0].
[0]: https://www.combodo.com

View File

@@ -18,7 +18,8 @@ to [itop-security@combodo.com](mailto:itop-security@combodo.com).
## 🔍 Combodo acknowledgment and investigation
## 📆 Disclosure Policy
Report sent to us will be acknowledged within the week.
Then, a Combodo developer will be assigned to the reported issue and will:
@@ -33,12 +34,3 @@ Then, a Combodo developer will be assigned to the reported issue and will:
Security issues always take precedence over bug fixes and feature work.
The assignee will keep you informed of the resolution progress, and may ask you for additional information or guidance.
## 📆 Disclosure Policy
Once the fix is done and acknowledged by every stakeholder, it will be included in the next iTop version.
Mind we have at least 2 active branches (LTS and STS, see [iTop Community Releases [iTop Documentation]](https://www.itophub.io/wiki/page?id=latest:release:start))
The release communications will include the information of the vulnerability fix.
Corresponding GitHub advisories and CVE will be published 3 months after the iTop version release date so that iTop instances can be updated.

View File

@@ -12,7 +12,6 @@ require_once(APPROOT.'/application/applicationcontext.class.inc.php');
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
require_once(APPROOT.'/application/displayblock.class.inc.php');
require_once(APPROOT.'/application/audit.category.class.inc.php');
require_once(APPROOT.'/application/audit.domain.class.inc.php');
require_once(APPROOT.'/application/audit.rule.class.inc.php');
require_once(APPROOT.'/application/query.class.inc.php');
require_once(APPROOT.'/setup/moduleinstallation.class.inc.php');

View File

@@ -376,19 +376,26 @@ class ApplicationContext
{
$oAppContext = new ApplicationContext();
if (is_null($sUrlMakerClass)) {
$sUrlMakerClass = self::GetUrlMakerClass();
}
if (is_null($sUrlMakerClass))
{
$sUrlMakerClass = self::GetUrlMakerClass();
}
$sUrl = call_user_func(array($sUrlMakerClass, 'MakeObjectUrl'), $sObjClass, $sObjKey);
if (utils::StrLen($sUrl) > 0) {
if ($bWithNavigationContext) {
return $sUrl."&".$oAppContext->GetForLink();
} else {
return $sUrl;
}
} else {
return '';
}
if (strlen($sUrl) > 0)
{
if ($bWithNavigationContext)
{
return $sUrl."&".$oAppContext->GetForLink();
}
else
{
return $sUrl;
}
}
else
{
return '';
}
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -20,7 +20,7 @@
/**
* This class manages the audit "categories". Each category defines a set of objects
* to check and is linked to a set of rules that determine the valid or invalid objects
* inside the set
* inside the set
*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
@@ -48,39 +48,13 @@ class AuditCategory extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("definition_set", array("allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSet("rules_list", array("linked_class"=>"AuditRule", "ext_key_to_me"=>"category_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(), "edit_mode" => LINKSET_EDITMODE_INPLACE, "tracking_level" => LINKSET_TRACKING_ALL)));
MetaModel::Init_AddAttribute(new AttributeInteger("ok_error_tolerance", array("allowed_values"=>null, "sql"=>"ok_error_tolerance", "default_value"=>5, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeInteger("warning_error_tolerance", array("allowed_values"=>null, "sql"=>"warning_error_tolerance", "default_value"=>25, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("domains_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "category_id", "ext_key_to_remote" => "domain_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'ok_error_tolerance', 'warning_error_tolerance', 'rules_list', 'domains_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set', 'rules_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'definition_set')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
}
/**
* @param int $iTotal
* @param int $iErrors
*
* @return string A semantic color name (eg. red, green, orange, success, failure, ... {@see css/backoffice/utils/variables/colors/_semantic-palette.scss}) to use for this category depending on its error count and tolerance
* @throws \CoreException
*
* @since 3.1.0
*/
public function GetReportColor($iTotal, $iErrors)
{
$sResult = 'red';
if ( ($iTotal == 0) || ($iErrors / $iTotal) <= ($this->Get('ok_error_tolerance') / 100) )
{
$sResult = 'green';
}
else if ( ($iErrors / $iTotal) <= ($this->Get('warning_error_tolerance') / 100) )
{
$sResult = 'orange';
}
return $sResult;
}
}
?>

View File

@@ -1,99 +0,0 @@
<?php
// Copyright (C) 2010-2021 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* This class manages the audit "categories". Each category defines a set of objects
* to check and is linked to a set of rules that determine the valid or invalid objects
* inside the set
*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* @since 3.1.0
*/
class AuditDomain extends cmdbAbstractObject
{
public static function Init()
{
$aParams = array
(
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_auditdomain",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeImage("icon", array("is_null_allowed"=>true, "depends_on"=>array(), "display_max_width"=>96, "display_max_height"=>96, "storage_max_width"=>256, "storage_max_height"=>256, "default_image"=>null, "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("categories_list", array("linked_class" => "lnkAuditCategoryToAuditDomain", "ext_key_to_me" => "domain_id", "ext_key_to_remote" => "category_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'icon', 'categories_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
}
}
/**
* @since 3.1.0
*/
class lnkAuditCategoryToAuditDomain extends cmdbAbstractObject
{
/**
* @throws \CoreException
* @throws \Exception
*/
public static function Init()
{
$aParams = array
(
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array('category_id', 'domain_id'),
"db_table" => "priv_link_audit_category_domain",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true,
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("targetclass" => "AuditCategory", "jointype" => '', "allowed_values" => null, "sql" => "category_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("allowed_values" => null, "extkey_attcode" => 'category_id', "target_attcode" => "name")));
MetaModel::Init_AddAttribute(new AttributeExternalKey("domain_id", array("targetclass" => "AuditDomain", "jointype" => '', "allowed_values" => null, "sql" => "domain_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalField("domain_name", array("allowed_values" => null, "extkey_attcode" => 'domain_id', "target_attcode" => "name")));
// Display lists
MetaModel::Init_SetZListItems('details', array('category_id', 'domain_id'));
MetaModel::Init_SetZListItems('list', array('category_id', 'domain_id'));
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('category_id', 'domain_id'));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1549,29 +1549,6 @@ JS
return $this->sDefinitionFile;
}
/**
* @param string $sDashboardFileRelative can also be an absolute path (compatibility with old URL)
*
* @return string full path to the Dashboard file
* @throws \SecurityException if path isn't under approot
* @uses utils::RealPath()
* @since 2.7.8 3.0.3 3.1.0 N°4449 remove FPD
*/
public static function GetDashboardFileFromRelativePath($sDashboardFileRelative)
{
if (utils::RealPath($sDashboardFileRelative, APPROOT)) {
// compatibility with old URL containing absolute path !
return $sDashboardFileRelative;
}
$sDashboardFile = APPROOT.$sDashboardFileRelative;
if (false === utils::RealPath($sDashboardFile, APPROOT)) {
throw new SecurityException('Invalid dashboard file !');
}
return $sDashboardFile;
}
/**
* @param string $sDefinitionFile
*/

View File

@@ -55,9 +55,9 @@
<menus>
<menu id="WelcomeMenu" xsi:type="MenuGroup" _delta="define">
<rank>10</rank>
<style>
<decoration_classes>fas fa-home</decoration_classes>
</style>
<style>
<decoration_classes>fas fa-home</decoration_classes>
</style>
</menu>
<menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="define">
<rank>10</rank>
@@ -151,9 +151,9 @@
</menu>
<menu id="ConfigurationTools" xsi:type="MenuGroup" _delta="define_if_not_exists">
<rank>90</rank>
<style>
<decoration_classes>fas fa-cog</decoration_classes>
</style>
<style>
<decoration_classes>fas fa-cog</decoration_classes>
</style>
</menu>
<menu id="DataSources" xsi:type="OQLMenuNode" _delta="define">
<rank>20</rank>
@@ -172,29 +172,29 @@
</menu>
<menu id="AdminTools" xsi:type="MenuGroup" _delta="define">
<rank>80</rank>
<style>
<decoration_classes>fas fa-tools</decoration_classes>
</style>
<style>
<decoration_classes>fas fa-tools</decoration_classes>
</style>
</menu>
<menu id="SystemTools" xsi:type="MenuGroup" _delta="define">
<rank>100</rank>
<enable_class>ResourceSystemMenu</enable_class>
<enable_action>UR_ACTION_MODIFY</enable_action>
<style>
<decoration_classes>fas fa-terminal</decoration_classes>
</style>
<style>
<decoration_classes>fas fa-terminal</decoration_classes>
</style>
</menu>
</menus>
<events>
<event id="EVENT_DB_CHECK_TO_WRITE" _delta="define">
<description>Check an object before it is written into the database (no change possible). Call DBObject::AddCheckIssue() to signal an issue</description>
<event id="EVENT_SERVICE_DB_INSERT_REQUESTED" _delta="define">
<description>An object insert in the database has been requested. All changes to the object will be persisted automatically.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>cmdbAbstractObject::DoCheckToWrite</replaces>
<replaces>DBObject::OnInsert</replaces>
<event_data>
<event_datum id="object">
<description>The object to check</description>
<description>The object inserted</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
@@ -203,8 +203,25 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_CREATE_DONE" _delta="define">
<description>An object has been created into the database. The modifications can be propagated to other objects.</description>
<event id="EVENT_SERVICE_DB_ABOUT_TO_INSERT" _delta="define">
<description>An object is about to be inserted in the database (no change possible)</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>DBObject::OnInsert</replaces>
<event_data>
<event_datum id="object">
<description>The object inserted</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_DB_INSERT_DONE" _delta="define">
<description>An object has been inserted into the database (but not reloaded). All changes to the object will be persisted automatically.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
@@ -220,8 +237,42 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_UPDATE_DONE" _delta="define">
<description>An object has been updated into the database and reloaded.</description>
<event id="EVENT_SERVICE_DB_UPDATE_REQUESTED" _delta="define">
<description>An object update has been requested. All changes to the object will be persisted automatically.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>DBObject::OnUpdate, DBObject::DoComputeValues</replaces>
<event_data>
<event_datum id="object">
<description>The object updated</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_DB_ABOUT_TO_UPDATE" _delta="define">
<description>An object is about to be updated in the database (no change possible)</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>DBObject::OnUpdate</replaces>
<event_data>
<event_datum id="object">
<description>The object updated</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_DB_UPDATE_DONE" _delta="define">
<description>An object has been updated into the database and reloaded. All changes to the object will be persisted automatically.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
@@ -237,15 +288,15 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_CHECK_TO_DELETE" _delta="define">
<description>Check an object before it is deleted from the database. Call DBObject::AddDeleteIssue() to signal an issue</description>
<event id="EVENT_SERVICE_DB_ABOUT_TO_DELETE" _delta="define">
<description>An object is about to be deleted in the database</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
<replaces>DBObject::OnDelete</replaces>
<event_data>
<event_datum id="object">
<description>The object to check</description>
<description>The object deleted</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
@@ -254,7 +305,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_DELETE_DONE" _delta="define">
<event id="EVENT_SERVICE_DB_DELETE_DONE" _delta="define">
<description>An object has been deleted into the database</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -271,7 +322,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_BEFORE_APPLY_STIMULUS" _delta="define">
<event id="EVENT_SERVICE_DB_BEFORE_APPLY_STIMULUS" _delta="define">
<description>A stimulus is about to be applied to an object</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -303,7 +354,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_AFTER_APPLY_STIMULUS" _delta="define">
<event id="EVENT_SERVICE_DB_AFTER_APPLY_STIMULUS" _delta="define">
<description>A stimulus has been applied to an object</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -335,7 +386,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_APPLY_STIMULUS_FAILED" _delta="define">
<event id="EVENT_SERVICE_DB_APPLY_STIMULUS_FAILED" _delta="define">
<description>A stimulus has failed</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -371,23 +422,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_LINKS_CHANGED" _delta="define">
<description>At least one link class was changed</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="object">
<description>The object where the link is or was pointing to</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_OBJECT_RELOAD" _delta="define">
<event id="EVENT_SERVICE_DB_OBJECT_RELOAD" _delta="define">
<description>An object has been re-loaded from the database</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -403,7 +438,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_COMPUTE_VALUES" _delta="define">
<event id="EVENT_SERVICE_DB_COMPUTE_VALUES" _delta="define">
<description>An object needs to be recomputed after changes</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -420,7 +455,49 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_ARCHIVE" _delta="define">
<event id="EVENT_SERVICE_DB_CHECK_TO_WRITE" _delta="define">
<description>Check an object before it is written into the database (no change possible)</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>cmdbAbstractObject::DoCheckToWrite</replaces>
<event_data>
<event_datum id="object">
<description>The object to check</description>
<type>DBObject</type>
</event_datum>
<event_datum id="error_messages">
<description>Array of strings where all the errors found during the object checking are added</description>
<type>array</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_DB_CHECK_TO_DELETE" _delta="define">
<description>Check an object before it is deleted from the database (no change possible)</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
<event_data>
<event_datum id="object">
<description>The object to check</description>
<type>DBObject</type>
</event_datum>
<event_datum id="error_messages">
<description>Array of strings where all the errors found during the object checking are added</description>
<type>array</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_SERVICE_DB_ARCHIVE" _delta="define">
<description>An object has been archived</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -436,7 +513,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_UNARCHIVE" _delta="define">
<event id="EVENT_SERVICE_DB_UNARCHIVE" _delta="define">
<description>An object has been unarchived</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
@@ -452,43 +529,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_SET_ATTRIBUTES_FLAGS" _delta="define">
<description>Set object attributes flags. Call cmdbAbstractObject::AddAttributeFlags() for all the attributes to be set for this target state.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="object">
<description>The current object</description>
<type>DBObject</type>
</event_datum>
<event_datum id="target_state">
<description>The target state in which to evaluate the flags</description>
<type>array</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS" _delta="define">
<description>Set object initial attributes flags. Call cmdbAbstractObject::AddInitialAttributeFlags() for all the initial attributes to be set initially.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="object">
<description>The current object</description>
<type>DBObject</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DOWNLOAD_DOCUMENT" _delta="define">
<event id="EVENT_SERVICE_DOWNLOAD_DOCUMENT" _delta="define">
<description>A document has been downloaded from the GUI</description>
<sources>
<source id="Document">Document</source>
@@ -508,7 +549,7 @@
</event_datum>
</event_data>
</event>
<event id="EVENT_LOGIN" _delta="define">
<event id="EVENT_SERVICE_LOGIN" _delta="define">
<description>Inform the listeners about the connection states</description>
<event_data>
<event_datum id="code">

View File

@@ -272,8 +272,6 @@ class DisplayBlock
'panel_title',
/** string true if panel title should be displayed as html */
'panel_title_is_html',
/** string Description of the panel content, displayed as a hint on the title */
'panel_title_tooltip',
/** string class for panel block style */
'panel_class',
/** string class for panel block style */
@@ -1047,18 +1045,9 @@ JS
$aCount = $aCounts[$sStateValue];
$sHyperlink = $aCount['link'];
$sCountLabel = $aCount['label'];
$oPill = PillFactory::MakeForState($sClass, $sStateValue);
// N°5849 - Unencode label for ExternalKey attribute because friendlyname is already html encoded thanks to DBObject::GetName() in AttributeExternalKey::GetAllowedValues(). (A fix in this function may have too much impact).
if ($oAttDef instanceof AttributeExternalKey) {
$sPillTooltip = htmlspecialchars_decode($sStateLabel, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5);
$sPillLabel = $sStateLabel;
} else {
$sPillTooltip = $sStateLabel;
$sPillLabel = utils::HtmlEntities($sStateLabel);
}
$oPill->SetTooltip($sPillTooltip)
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">".$sPillLabel."</span>");
$oPill = PillFactory::MakeForState($sClass, $sStateValue)
->SetTooltip($sStateLabel)
->AddHtml("<span class=\"ibo-dashlet-header-dynamic--count\">$sCountLabel</span><span class=\"ibo-dashlet-header-dynamic--label ibo-text-truncated-with-ellipsis\">".utils::HtmlEntities($sStateLabel)."</span>");
if ($sHyperlink != '-') {
$oPill->SetUrl($sHyperlink);
}
@@ -1315,12 +1304,17 @@ JS
}
}
if (count($aAuthorizedClasses) > 0) {
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
if ($this->m_oSet->CountWithLimit(1) > 0) {
if (empty($aExtraParams['currentId'])) {
$iListId = utils::GetUniqueId(); // Works only if not in an Ajax page !!
} else {
$iListId = $aExtraParams['currentId'];
}
$oBlock->AddSubBlock(DataTableUIBlockFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams));
} else {
$iListId = $aExtraParams['currentId'];
// Empty set
$oBlock->bEmptySet = true;
}
$oBlock->AddSubBlock(DataTableUIBlockFactory::MakeForObject($oPage, $iListId, $this->m_oSet, $aExtraParams));
} else {
// Not authorized
$oBlock->bNotAuthorized = true;
@@ -1343,13 +1337,43 @@ JS
}
// The list is made of only 1 class of objects, actions on the list are possible
if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) {
if (($this->m_oSet->CountWithLimit(1) > 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)) {
$oBlock->AddSubBlock(cmdbAbstractObject::GetDisplaySetBlock($oPage, $this->m_oSet, $aExtraParams));
} else {
$oBlock->bEmptySet = true;
$oBlock->sClass = $this->m_oFilter->GetClass();
$oBlock->sClassLabel = MetaModel::GetName($oBlock->sClass);
$bDisplayMenu = isset($aExtraParams['menu']) ? ($aExtraParams['menu'] == true) : true;
if ($bDisplayMenu) {
if ((UserRights::IsActionAllowed($oBlock->sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) {
$oBlock->sLinkTarget = '';
$oAppContext = new ApplicationContext();
$oBlock->sParams = $oAppContext->GetForLink();
// 1:n links, populate the target object as a default value when creating a new linked object
if (isset($aExtraParams['target_attr'])) {
$oBlock->sLinkTarget = ' target="_blank" ';
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
}
if (!empty($aExtraParams['default'])) {
foreach ($aExtraParams['default'] as $sKey => $sValue) {
$oBlock->sDefault .= "&default[$sKey]=$sValue";
}
}
$oBlock->bCreateNew = true;
}
}
if (isset($aExtraParams["surround_with_panel"]) && $aExtraParams["surround_with_panel"]) {
$oPanel = PanelUIBlockFactory::MakeForClass($aExtraParams["panel_class"], $aExtraParams["panel_title"]);
if(isset($aExtraParams["panel_icon"]) && strlen($aExtraParams["panel_icon"]) > 0){
$oPanel->SetIcon($aExtraParams["panel_icon"]);
}
$oPanel->AddSubBlock($oBlock);
return $oPanel;
}
}
}
return $oBlock;
@@ -1447,7 +1471,6 @@ JS
} else {
$iListId = $aExtraParams['currentId'];
}
$oBlock = DataTableUIBlockFactory::MakeForRendering($iListId, $oSet, $aExtraParams);
$oHtml->AddHtml("<tr><td>");
$oContentBlock->AddSubBlock($oBlock);
@@ -1729,14 +1752,9 @@ class MenuBlock extends DisplayBlock
$this->m_sStyle = 'list';
}
$sClass = $this->GetFilter()->GetClass();
$aSelectedClasses = $this->GetFilter()->GetSelectedClasses();
$bIsForLinkset = isset($aExtraParams['target_attr']);
$oSet = new CMDBObjectSet($this->GetFilter());
$iSetCount = $oSet->Count();
/** @var string $sRefreshAction JS snippet to run when clicking on the refresh button of the menu */
$sClass = $this->m_oFilter->GetClass();
$oSet = new CMDBObjectSet($this->m_oFilter);
$sRefreshAction = $aExtraParams['refresh_action'] ?? '';
$bIsCreationInModalAllowed = isset($aExtraParams['creation_in_modal_is_allowed']) && $aExtraParams['creation_in_modal_is_allowed'] === true;
/** @var array $aRegularActions Any action other than a transition */
$aRegularActions = [];
@@ -1744,240 +1762,275 @@ class MenuBlock extends DisplayBlock
$aTransitionActions = [];
/** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */
$aToolkitActions = [];
if (!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == "")) {
if ((!isset($aExtraParams['selection_mode']) || $aExtraParams['selection_mode'] == "") && $this->m_sStyle != 'listInObject') {
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (utils::IsNotNullOrEmptyString($sContext)) {
if (!empty($sContext)) {
$sContext = '&'.$sContext;
}
$oReflectionClass = new ReflectionClass($sClass);
$sFilter = $this->GetFilter()->serialize();
$sFilter = $this->m_oFilter->serialize();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
// Common params that will be applied to actions
$aActionParams = $this->GetDefaultParamsForMenuAction();
$aActionParams = array();
if (isset($aExtraParams['menu_actions_target'])) {
$aActionParams['target'] = $aExtraParams['menu_actions_target'];
}
// 1:n links, populate the target object as a default value when creating a new linked object
if ($bIsForLinkset) {
if (isset($aExtraParams['target_attr'])) {
$aExtraParams['default'][$aExtraParams['target_attr']] = $aExtraParams['object_id'];
}
/** @var string $sDefaultValuesAsUrlParams Default values for the object to create, already formatted as URL params (eg. "&default[org_id]=3&default[title]=Foo") */
$sDefaultValuesAsUrlParams = '';
$sDefault = '';
if (!empty($aExtraParams['default'])) {
foreach ($aExtraParams['default'] as $sKey => $sValue) {
$sDefaultValuesAsUrlParams .= "&default[$sKey]=$sValue";
$sDefault .= "&default[$sKey]=$sValue";
}
}
// Check rights
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsModifyAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) === UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
// Create in new tab
if ($bIsCreationAllowed && !$bIsCreationInModalAllowed) {
$this->AddNewObjectMenuAction($aRegularActions, $sClass, $sDefaultValuesAsUrlParams);
}
// Any style actions
// - Bulk actions on objects set
if ($iSetCount > 1) {
// Bulk actions for each selected classes (eg. "link" and "remote" on n:n relations)
foreach ($aSelectedClasses as $sSelectedAlias => $sSelectedClass) {
$sSelectedClassName = MetaModel::GetName($sSelectedClass);
// Check rights on class
$bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sSelectedClass)) && UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_MODIFY, $oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsBulkDeleteAllowed = (bool) UserRights::IsActionAllowed($sSelectedClass, UR_ACTION_BULK_DELETE, $sSelectedClass);
// Refine filter on selected class so bullk actions occur on the right class
$oSelectedClassFilter = $this->GetFilter()->DeepClone();
$oSelectedClassFilter->SetSelectedClasses([$sSelectedAlias]);
// Action identifier is using the alias on purpose so they can be used as "shortcut actions" easily for "Link" or "Remote" aliases on linksets.
// Action label dict code has a specific suffix for "Link" / "Remote" aliases to allow dedicated labels in linksets.
$sActionLabelCodeSuffix = in_array($sSelectedAlias, ['Link', 'Remote']) ? $sSelectedAlias : 'Class';
if ($bIsBulkModifyAllowed) {
$this->AddBulkModifyObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:ModifyAll:'.$sSelectedAlias, Dict::Format('UI:Menu:ModifyAll_'.$sActionLabelCodeSuffix, $sSelectedClassName));
$bIsCreationAllowed = (UserRights::IsActionAllowed($sClass,
UR_ACTION_CREATE) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
switch ($oSet->Count()) {
case 0:
// No object in the set, the only possible action is "new"
if ($bIsCreationAllowed) {
$aRegularActions['UI:Menu:New'] = array(
'label' => Dict::S('UI:Menu:New'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
) + $aActionParams;
}
if ($bIsBulkDeleteAllowed) {
$this->AddBulkDeleteObjectsMenuAction($aRegularActions, $sSelectedClass, $oSelectedClassFilter->serialize(), 'UI:Menu:BulkDelete:'.$sSelectedAlias, Dict::Format('UI:Menu:BulkDelete_'.$sActionLabelCodeSuffix, $sSelectedClassName));
}
}
break;
// Stimuli
$aStates = MetaModel::EnumStates($sClass);
// Do not perform time-consuming computations if there are too many objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
case 1:
$oObj = $oSet->Fetch();
if (is_null($oObj)) {
if (!isset($aExtraParams['link_attr'])) {
if ($bIsCreationAllowed) {
$aRegularActions['UI:Menu:New'] = array(
'label' => Dict::S('UI:Menu:New'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
) + $aActionParams;
}
}
} else {
$id = $oObj->GetKey();
if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') {
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$sRefreshAction = "window.location.reload();";
} else {
$sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';";
}
}
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) {
// Life cycle actions may be available... if all objects are in the same state
//
// Group by <state>
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
$aGroupBy = array('__state__' => $oGroupByExp);
$aQueryParams = array();
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
$bLocked = false;
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id);
if ($aLockInfo['locked']) {
$bLocked = true;
//$this->AddMenuSeparator($aActions);
//$aActions['concurrent_lock_unlock'] = array ('label' => Dict::S('UI:Menu:ReleaseConcurrentLock'), 'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}");
}
}
$bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed;
$bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
// Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr'])) {
if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Modify'] = array(
'label' => Dict::S('UI:Menu:Modify'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify&class=$sClass&id=$id{$sContext}#",
) + $aActionParams;
}
if ($bIsCreationAllowed) {
$aRegularActions['UI:Menu:New'] = array(
'label' => Dict::S('UI:Menu:New'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
) + $aActionParams;
}
if ($bIsDeleteAllowed) {
$aRegularActions['UI:Menu:Delete'] = array(
'label' => Dict::S('UI:Menu:Delete'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
if (count($aRes) == 1) {
// All objects are in the same state...
$sState = $aRes[0]['__state__'];
$aTransitions = Metamodel::EnumTransitions($sClass, $sState);
if (count($aTransitions)) {
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$oSet->Rewind();
// As soon as the user rights implementation will browse the object set,
// then we might consider using OptimizeColumnLoad() here
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet);
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
// Transitions / Stimuli
if (!$bLocked) {
$aTransitions = $oObj->EnumTransitions();
if (count($aTransitions)) {
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSet) : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
$aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
break;
default:
// Do nothing
}
}
}
}
// Relations...
$aRelations = MetaModel::EnumRelationsEx($sClass);
if (count($aRelations)) {
$this->AddMenuSeparator($aRegularActions);
foreach ($aRelations as $sRelationCode => $aRelationInfo) {
if (array_key_exists('down', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_down'] = array(
'label' => $aRelationInfo['down'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
break;
}
if (array_key_exists('up', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_up'] = array(
'label' => $aRelationInfo['up'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
}
}
default:
// Do nothing
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
if ($bLocked && $bRawModifiedAllowed) {
/** @var array $aAllowedProfiles */
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
$bCanKill = false;
$oUser = UserRights::GetUserObject();
$aUserProfiles = array();
if (!is_null($oUser)) {
$oProfileSet = $oUser->Get('profile_list');
while ($oProfile = $oProfileSet->Fetch()) {
$aUserProfiles[$oProfile->Get('profile')] = true;
}
}
foreach ($aAllowedProfiles as $sProfile) {
if (array_key_exists($sProfile, $aUserProfiles)) {
$bCanKill = true;
break;
}
}
if ($bCanKill) {
$this->AddMenuSeparator($aRegularActions);
$aRegularActions['concurrent_lock_unlock'] = array(
'label' => Dict::S('UI:Menu:KillConcurrentLock'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
);
}
}
}
$this->AddMenuSeparator($aRegularActions);
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
});
}
break;
default:
// Check rights
// New / Modify
$bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY,
$oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsBulkModifyAllowed = (!MetaModel::IsAbstract($sClass)) && UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY,
$oSet) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet);
if (isset($aExtraParams['link_attr'])) {
$id = $aExtraParams['object_id'];
$sTargetAttr = $aExtraParams['target_attr'];
$oAttDef = MetaModel::GetAttributeDef($sClass, $sTargetAttr);
$sTargetClass = $oAttDef->GetTargetClass();
if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Add'] = array(
'label' => Dict::S('UI:Menu:Add'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id&addObjects=true{$sContext}",
) + $aActionParams;
}
if ($bIsBulkModifyAllowed) {
$aRegularActions['UI:Menu:Manage'] = array(
'label' => Dict::S('UI:Menu:Manage'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=modify_links&class=$sClass&link_attr=".$aExtraParams['link_attr']."&target_class=$sTargetClass&id=$id{$sContext}",
) + $aActionParams;
}
//if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All...', 'url' => "#") + $aActionParams; }
} else {
// many objects in the set, possible actions are: new / modify all / delete all
if ($bIsCreationAllowed) {
$aRegularActions['UI:Menu:New'] = array(
'label' => Dict::S('UI:Menu:New'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=new&class=$sClass{$sContext}{$sDefault}",
) + $aActionParams;
}
if ($bIsBulkModifyAllowed) {
$aRegularActions['UI:Menu:ModifyAll'] = array(
'label' => Dict::S('UI:Menu:ModifyAll'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
) + $aActionParams;
}
if ($bIsBulkDeleteAllowed) {
$aRegularActions['UI:Menu:BulkDelete'] = array(
'label' => Dict::S('UI:Menu:BulkDelete'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=select_for_deletion&filter=".urlencode($sFilter)."{$sContext}",
) + $aActionParams;
}
// Stimuli
$aStates = MetaModel::EnumStates($sClass);
// Do not perform time consuming computations if there are too may objects in the list
$iLimit = MetaModel::GetConfig()->Get('complex_actions_limit');
if ((count($aStates) > 0) && (($iLimit == 0) || ($oSet->CountWithLimit($iLimit + 1) < $iLimit))) {
// Life cycle actions may be available... if all objects are in the same state
//
// Group by <state>
$oGroupByExp = new FieldExpression(MetaModel::GetStateAttributeCode($sClass), $this->m_oFilter->GetClassAlias());
$aGroupBy = array('__state__' => $oGroupByExp);
$aQueryParams = array();
if (isset($aExtraParams['query_params'])) {
$aQueryParams = $aExtraParams['query_params'];
}
$sSql = $this->m_oFilter->MakeGroupByQuery($aQueryParams, $aGroupBy);
$aRes = CMDBSource::QueryToArray($sSql);
if (count($aRes) == 1) {
// All objects are in the same state...
$sState = $aRes[0]['__state__'];
$aTransitions = Metamodel::EnumTransitions($sClass, $sState);
if (count($aTransitions)) {
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$oSet->Rewind();
// As soon as the user rights implementation will browse the object set,
// then we might consider using OptimizeColumnLoad() here
$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet);
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? $iActionAllowed : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
case UR_ALLOWED_DEPENDS:
$aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?operation=select_bulk_stimulus&stimulus=$sStimulusCode&state=$sState&class=$sClass&filter=".urlencode($sFilter)."{$sContext}",
) + $aActionParams;
break;
default:
// Do nothing
}
}
}
}
}
}
}
}
// NOT "listInObject" style actions
if ($this->m_sStyle !== 'listInObject') {
switch ($iSetCount) {
case 1:
$oObj = $oSet->Fetch();
if (false === is_null($oObj)) {
$id = $oObj->GetKey();
if (empty($sRefreshAction) && utils::ReadParam('operation') == 'details') {
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
$sRefreshAction = "window.location.reload();";
} else {
$sRefreshAction = "window.location.href='".ApplicationContext::MakeObjectUrl(get_class($oObj), $id)."';";
}
}
$bLocked = false;
if (MetaModel::GetConfig()->Get('concurrent_lock_enabled')) {
$aLockInfo = iTopOwnershipLock::IsLocked(get_class($oObj), $id);
if ($aLockInfo['locked']) {
$bLocked = true;
}
}
$bRawModifiedAllowed = (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet) == UR_ALLOWED_YES) && ($oReflectionClass->IsSubclassOf('cmdbAbstractObject'));
$bIsModifyAllowed = !$bLocked && $bRawModifiedAllowed;
$bIsDeleteAllowed = !$bLocked && UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
// Just one object in the set, possible actions are "new / clone / modify and delete"
if (!isset($aExtraParams['link_attr'])) {
if ($bIsModifyAllowed) {
$aRegularActions['UI:Menu:Modify'] = array(
'label' => Dict::S('UI:Menu:Modify'),
'url' => "{$sRootUrl}pages/$sUIPage?route=object.modify&class=$sClass&id=$id{$sContext}#",
) + $aActionParams;
}
if ($bIsDeleteAllowed) {
$aRegularActions['UI:Menu:Delete'] = array(
'label' => Dict::S('UI:Menu:Delete'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=delete&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
// Transitions / Stimuli
if (!$bLocked) {
$aTransitions = $oObj->EnumTransitions();
if (count($aTransitions)) {
$aStimuli = Metamodel::EnumStimuli(get_class($oObj));
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSet) : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
$aTransitionActions[$sStimulusCode] = array(
'label' => $aStimuli[$sStimulusCode]->GetLabel(),
'url' => "{$sRootUrl}pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
break;
default:
// Do nothing
}
}
}
}
// Relations...
$aRelations = MetaModel::EnumRelationsEx($sClass);
if (count($aRelations)) {
$this->AddMenuSeparator($aRegularActions);
foreach ($aRelations as $sRelationCode => $aRelationInfo) {
if (array_key_exists('down', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_down'] = array(
'label' => $aRelationInfo['down'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=down&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
if (array_key_exists('up', $aRelationInfo)) {
$aRegularActions[$sRelationCode.'_up'] = array(
'label' => $aRelationInfo['up'],
'url' => "{$sRootUrl}pages/$sUIPage?operation=view_relations&relation=$sRelationCode&direction=up&class=$sClass&id=$id{$sContext}",
) + $aActionParams;
}
}
}
// Add a special menu to kill the lock, but only to allowed users who can also modify this object
if ($bLocked && $bRawModifiedAllowed) {
/** @var array $aAllowedProfiles */
$aAllowedProfiles = MetaModel::GetConfig()->Get('concurrent_lock_override_profiles');
$bCanKill = false;
$oUser = UserRights::GetUserObject();
$aUserProfiles = array();
if (!is_null($oUser)) {
$oProfileSet = $oUser->Get('profile_list');
while ($oProfile = $oProfileSet->Fetch()) {
$aUserProfiles[$oProfile->Get('profile')] = true;
}
}
foreach ($aAllowedProfiles as $sProfile) {
if (array_key_exists($sProfile, $aUserProfiles)) {
$bCanKill = true;
break;
}
}
if ($bCanKill) {
$this->AddMenuSeparator($aRegularActions);
$aRegularActions['concurrent_lock_unlock'] = array(
'label' => Dict::S('UI:Menu:KillConcurrentLock'),
'url' => "{$sRootUrl}pages/$sUIPage?operation=kill_lock&class=$sClass&id=$id{$sContext}",
);
}
}
}
$this->AddMenuSeparator($aRegularActions);
$this->GetEnumAllowedActions($oSet, function ($sLabel, $data) use (&$aRegularActions, $aActionParams) {
$aRegularActions[$sLabel] = array('label' => $sLabel, 'url' => $data) + $aActionParams;
});
}
break;
}
}
$this->AddMenuSeparator($aRegularActions);
@@ -2002,9 +2055,8 @@ class MenuBlock extends DisplayBlock
$sRefreshAction = "window.location.reload();";
}
} else {
// It's easier just display configure this list and MENU_OBJLIST_TOOLKIT
//it's easier just display configure this list and MENU_OBJLIST_TOOLKIT
}
$param = null;
if (is_null($sId)) {
$sId = uniqid();
@@ -2017,7 +2069,14 @@ class MenuBlock extends DisplayBlock
case 'listInObject':
$oSet->Rewind();
$param = $oSet;
$bToolkitMenu = true;
if (isset($aExtraParams['toolkit_menu'])) {
$bToolkitMenu = (bool)$aExtraParams['toolkit_menu'];
}
if ($bToolkitMenu) {
$sLabel = Dict::S('UI:ConfigureThisList');
$aRegularActions['iTop::ConfigureList'] = ['label' => $sLabel, 'url' => '#', 'onclick' => "$('#datatable_dlg_datatable_{$sId}').dialog('open'); return false;"];
}
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_ACTIONS, $param, $aRegularActions, $sId);
utils::GetPopupMenuItemsBlock($oPopupMenuItemsBlock, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $param, $aToolkitActions, $sId);
break;
@@ -2029,7 +2088,6 @@ class MenuBlock extends DisplayBlock
break;
}
if ($oPopupMenuItemsBlock->HasSubBlocks()) {
$oRenderBlock->AddSubBlock($oPopupMenuItemsBlock);
} else {
@@ -2040,7 +2098,6 @@ class MenuBlock extends DisplayBlock
$oRenderBlock->AddCssFileRelPath($sCssPath);
}
}
// Extract favorite actions from their menus
$aFavoriteRegularActions = [];
$aFavoriteTransitionActions = [];
@@ -2126,16 +2183,12 @@ class MenuBlock extends DisplayBlock
break;
case 'UI:Menu:ModifyAll':
case 'UI:Menu:ModifyAll:Link': // Link class on linkset
case 'UI:Menu:ModifyAll:Remote': // Remote class on linkset
case 'UI:Menu:Modify':
$sIconClass = 'fas fa-pen';
$sLabel = '';
break;
case 'UI:Menu:BulkDelete':
case 'UI:Menu:BulkDelete:Link': // Link class on linkset
case 'UI:Menu:BulkDelete:Remote': // Remote class on linkset
case 'UI:Menu:Delete':
$sIconClass = 'fas fa-trash';
$sLabel = '';
@@ -2168,28 +2221,8 @@ class MenuBlock extends DisplayBlock
$oActionsToolbar->AddSubBlock($oActionButton);
}
// - Creation in modal
if ($bIsCreationInModalAllowed === true) {
$oAddLinkActionButton = ButtonUIBlockFactory::MakeIconAction(
'fas fa-plus',
Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
'UI:Links:New',
'',
false
);
// - If we are used in a Datatable, 'datatable_' will be prefixed to our $sId, so we do the same here
$sRealId = $sId;
if(in_array($this->m_sStyle, ['list', 'links', 'listInObject'])){
$sRealId = 'datatable_' . $sId;
}
$oAddLinkActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button'])
->SetOnClickJsCode("$('#$sRealId').trigger('open_creation_modal.object.itop');");
$oActionsToolbar->AddSubBlock($oAddLinkActionButton);
}
// - Refresh
if (utils::IsNotNullOrEmptyString($sRefreshAction)) {
if ($sRefreshAction != '') {
$oActionButton = ButtonUIBlockFactory::MakeAlternativeNeutral('', 'UI:Button:Refresh');
$oActionButton->SetIconClass('fas fa-sync-alt')
->SetOnClickJsCode($sRefreshAction)
@@ -2199,7 +2232,7 @@ class MenuBlock extends DisplayBlock
}
// - Search
if ($this->m_sStyle === 'details') {
if ($this->m_sStyle == 'details') {
$oActionButton = ButtonUIBlockFactory::MakeIconLink('fas fa-search', Dict::Format('UI:SearchFor_Class', MetaModel::GetName($sClass)), "{$sRootUrl}pages/UI.php?operation=search_form&do_search=0&class=$sClass{$sContext}", '', 'UI:SearchFor_Class');
$oActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']);
$oActionsToolbar->AddSubBlock($oActionButton);
@@ -2224,12 +2257,6 @@ class MenuBlock extends DisplayBlock
// Toolkit actions
if (!empty($aToolkitActions)) {
// Add separator if necessary
if (count($aRegularActions) > 0) {
$oRegularActionsMenu->AddItem('separator-regular-actions-toolkit-actions', PopoverMenuItemFactory::MakeSeparator());
}
// Add actions
foreach ($aToolkitActions as $sActionId => $aActionData) {
$oRegularActionsMenu->AddItem('toolkit-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItemData($sActionId, $aActionData));
}
@@ -2310,106 +2337,5 @@ class MenuBlock extends DisplayBlock
$aActions['sep_'.(count($aActions)-1)] = array('label' => $sSeparator, 'url' => '');
}
}
}
/**
* Add a create action (to $aActions) for an $sClass object
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sDefaultValuesAsUrlParams Default values for the new object form,
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddNewObjectMenuAction(array &$aActions, string $sClass, string $sDefaultValuesAsUrlParams = ''): void
{
$aActions['UI:Menu:New'] = [
'label' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($sClass)),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=new&class=$sClass{$sDefaultValuesAsUrlParams}"),
] + $this->GetDefaultParamsForMenuAction();
}
/**
* Add a bulk modify action (to $aActions) on objects defined by $sFilter
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sFilter OQL of the objects to propose for bulk modify
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:ModifyAll'
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:ModifyAll'.
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddBulkModifyObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:ModifyAll', $sActionLabel = 'UI:Menu:ModifyAll'): void
{
$aActions[$sActionIdentifier] = [
'label' => Dict::S($sActionLabel),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_modify_all&class=$sClass&filter=".urlencode($sFilter)),
] + $this->GetDefaultParamsForMenuAction();
}
/**
* Add a bulk delete action (to $aActions) on objects defined by $sFilter
*
* @param array &$aActions Pointer to the array in which the action will be added
* @param string $sClass Datamodel class concerned by the action
* @param string $sFilter OQL of the objects to propose for bulk deletion
* @param string $sActionIdentifier Unique identifier for the action. Default if 'UI:Menu:BulkDelete'
* @param string $sActionLabel Label for the action, can be either a dict code to translate or an hardcoded label. Default is 'UI:Menu:BulkDelete'.
*
* @return void
* @since 3.1.0
* @internal
*/
protected function AddBulkDeleteObjectsMenuAction(array &$aActions, string $sClass, string $sFilter, string $sActionIdentifier = 'UI:Menu:BulkDelete', $sActionLabel = 'UI:Menu:BulkDelete')
{
$aActions[$sActionIdentifier] = array(
'label' => Dict::S($sActionLabel),
'url' => $this->PrepareUrlForStandardMenuAction($sClass, "operation=select_for_deletion&filter=".urlencode($sFilter)),
) + $this->GetDefaultParamsForMenuAction();
}
/**
* @return array Default parameters of a menu action
* @since 3.1.0
* @internal
*/
private function GetDefaultParamsForMenuAction(): array
{
$aDefaultParams = [];
if (isset($aExtraParams['menu_actions_target'])) {
$aDefaultParams['target'] = $aExtraParams['menu_actions_target'];
}
return $aDefaultParams;
}
/**
* @param string $sClass Datamodel class for which the URL is prepared
* @param string $sUrlParams URL parameters to add to the URL, must already be concatenated as a string (eg. "foo=bar&some=thing&third=param")
*
* @return string An absolute URL for a menu action on the $sClass class with $sUrlParams
* @throws \Exception
* @internal
*/
private function PrepareUrlForStandardMenuAction(string $sClass, string $sUrlParams)
{
$sRootUrl = utils::GetAbsoluteUrlAppRoot();
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($sClass);
$sUrl = "{$sRootUrl}pages/{$sUIPage}?{$sUrlParams}";
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
if (utils::IsNotNullOrEmptyString($sContext)) {
$sUrl .= '&'.$sContext;
}
return $sUrl;
}
}
}

View File

@@ -6,10 +6,6 @@
class CoreException extends Exception
{
protected $m_sIssue;
protected $m_sImpact;
protected $m_aContextData;
/**
* CoreException constructor.
*

View File

@@ -1,10 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class iTopXmlException extends CoreException
{
}

View File

@@ -59,6 +59,7 @@ class LoginBasic extends AbstractLoginFSMExtension
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}

View File

@@ -42,6 +42,7 @@ class LoginExternal extends AbstractLoginFSMExtension
$sAuthUser = $this->GetAuthUser();
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
{
$_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}

View File

@@ -68,6 +68,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}

View File

@@ -57,6 +57,7 @@ class LoginURL extends AbstractLoginFSMExtension
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
{
$_SESSION['auth_user'] = $sAuthUser;
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}

View File

@@ -26,8 +26,8 @@
use Combodo\iTop\Application\Branding;
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\EventData;
use Combodo\iTop\Service\EventService;
/**
* Web page used for displaying the login form
@@ -481,13 +481,13 @@ class LoginWebPage extends NiceWebPage
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
if ($iResponse == self::LOGIN_FSM_RETURN)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
Session::WriteClose();
return $iErrorCode; // Asked to exit FSM, generally login OK
}
if ($iResponse == self::LOGIN_FSM_ERROR)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
$sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error
// An error was detected, skip the other plugins turn
break;
@@ -501,7 +501,7 @@ class LoginWebPage extends NiceWebPage
}
catch (Exception $e)
{
EventService::FireEvent(new EventData(EVENT_LOGIN, null, ['state' => $_SESSION['login_state']]));
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['state' => $_SESSION['login_state']]));
IssueLog::Error($e->getTraceAsString());
static::ResetSession();
die($e->getMessage());

View File

@@ -291,17 +291,7 @@ class ApplicationMenu
* @param string $sMenuGroupIdx
* @param array $aExtraParams
*
* @return array{
* array{
* sId: string,
* sTitle: string,
* sLabel: string,
* bHasCount: boolean,
* sUrl: string,
* bOpenInNewWindow: boolean,
* aSubMenuNodes: array
* }
* } The aSubMenuNodes key contains the same structure recursively
* @return array
* @throws \DictExceptionMissingString
* @throws \Exception
* @since 3.0.0
@@ -330,13 +320,12 @@ class ApplicationMenu
}
$aSubMenuNodes[] = [
'sId' => $oSubMenuNode->GetMenuId(),
'sTitle' => $oSubMenuNode->GetTitle(),
'sLabel' => $oSubMenuNode->GetLabel(),
'bHasCount' => $oSubMenuNode->HasCount(),
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
'sId' => $oSubMenuNode->GetMenuId(),
'sTitle' => $oSubMenuNode->GetTitle(),
'bHasCount' => $oSubMenuNode->HasCount(),
'sUrl' => $oSubMenuNode->GetHyperlink($aExtraParams),
'bOpenInNewWindow' => $oSubMenuNode->IsHyperLinkInNewWindow(),
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
'aSubMenuNodes' => static::GetSubMenuNodes($sSubMenuItemIdx, $aExtraParams),
];
}
@@ -757,7 +746,7 @@ abstract class MenuNode
}
/**
* @return string The "+" dictionary entry for this menu if exists, otherwise the Title (if we have a parent title, will output parentTitle / currentTitle)
* @return string
*/
public function GetLabel()
{
@@ -769,6 +758,7 @@ abstract class MenuNode
} else {
$sRet = $this->GetTitle();
}
//$sRet = $this->GetTitle();
}
return $sRet;
}
@@ -1111,7 +1101,6 @@ class OQLMenuNode extends MenuNode
*/
public function RenderContent(WebPage $oPage, $aExtraParams = array())
{
ContextTag::AddContext(ContextTag::TAG_OBJECT_SEARCH);
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
OQLMenuNode::RenderOQLSearch
(

View File

@@ -156,7 +156,6 @@ abstract class Query extends cmdbAbstractObject
// last export information
$this->Set('export_last_date', date(AttributeDateTime::GetSQLFormat()));
$this->Set('export_last_user_id', UserRights::GetUserObject());
$this->AllowWrite(true);
$this->DBUpdate();
// increment usage counter

View File

@@ -15,16 +15,12 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
require_once(APPROOT.'core/cmdbobject.class.inc.php');
require_once(APPROOT.'application/utils.inc.php');
require_once(APPROOT.'core/contexttag.class.inc.php');
require_once(APPROOT.'core/kpi.class.inc.php');
require_once(APPROOT.'setup/setuputils.class.inc.php');
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/core/contexttag.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
/**
@@ -103,6 +99,3 @@ else
}
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));

View File

@@ -111,6 +111,23 @@ class TwigExtension
return utils::IsDevelopmentEnvironment();
}));
// Function to get configuration parameter
// Usage in twig: {{ get_config_parameter('foo') }}
$oTwigEnv->addFunction(new TwigFunction('get_config_parameter', function ($sParamName) {
$oConfig = MetaModel::GetConfig();
return $oConfig->Get($sParamName);
}));
// Function to get a module setting
// Usage in twig: {{ get_module_setting(<MODULE_CODE>, <PROPERTY_CODE> [, <DEFAULT_VALUE>]) }}
// since 3.0.0, but see N°4034 for upcoming evolutions in the 3.1
$oTwigEnv->addFunction(new TwigFunction('get_module_setting', function (string $sModuleCode, string $sPropertyCode, $defaultValue = null) {
$oConfig = MetaModel::GetConfig();
return $oConfig->GetModuleSetting($sModuleCode, $sPropertyCode, $defaultValue);
}));
// Function to get the URL of a static page in a module
// Usage in twig: {{ get_static_page_module_url('itop-my-module', 'path-to-my-page') }}
$oTwigEnv->addFunction(new TwigFunction('get_static_page_module_url', function ($sModuleName, $sPage) {

View File

@@ -685,15 +685,15 @@ JS
}
$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, 'dtc_'.$this->iId,
$oPage->AddUiBlock($oBlock->GetDisplay($oPage, $this->iId,
array(
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'menu' => false,
'currentId' => $this->iId,
'table_id' => "dr_{$this->iId}",
'table_inner_id' => "{$this->iId}_results",
'selection_mode' => true,
'selection_type' => 'single',
'cssCount' => '#count_'.$this->iId.'_results',
'cssCount' => '#count_'.$this->iId.'_results',
)
));
$sCancel = Dict::S('UI:Button:Cancel');

View File

@@ -4,8 +4,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Application\UI\Links\Direct\BlockDirectLinksEditTable;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
/**
* Class UILinksWidgetDirect
@@ -19,12 +19,10 @@ class UILinksWidgetDirect
protected $sAttCode;
protected $sInputid;
protected $sNameSuffix;
protected $aZlist;
protected $sLinkedClass;
/**
* UILinksWidgetDirect constructor.
*
* @param string $sClass
* @param string $sAttCode
* @param string $sInputId
@@ -82,10 +80,97 @@ class UILinksWidgetDirect
*/
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj)
{
$oBlock = new BlockDirectLinksEditTable($this, $this->sInputid);
$oBlock->InitTable($oPage, $oValue, $sFormPrefix);
if (empty($aArgs)) {
$aArgs = [];
}
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
switch($oLinksetDef->GetEditMode())
{
case LINKSET_EDITMODE_NONE: // The linkset is read-only
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
break;
case LINKSET_EDITMODE_ADDONLY: // The only possible action is to open (in a new window) the form to create a new object
if ($oCurrentObj && !$oCurrentObj->IsNew())
{
$sTargetClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$sDefault = "default[$sExtKeyToMe]=".$oCurrentObj->GetKey();
$oAppContext = new ApplicationContext();
$sParams = $oAppContext->GetForLink();
$oPage->p("<a target=\"_blank\" href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=new&class=$sTargetClass&$sParams&{$sDefault}\">".Dict::Format('UI:ClickToCreateNew', Metamodel::GetName($sTargetClass))."</a>\n");
}
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, false /* bDisplayMenu*/);
break;
case LINKSET_EDITMODE_INPLACE: // The whole linkset can be edited 'in-place'
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj);
break;
case LINKSET_EDITMODE_ADDREMOVE: // The whole linkset can be edited 'in-place'
$sTargetClass = $oLinksetDef->GetLinkedClass();
$sExtKeyToMe = $oLinksetDef->GetExtKeyToMe();
$oExtKeyDef = MetaModel::GetAttributeDef($sTargetClass, $sExtKeyToMe);
$aButtons = array('add');
if ($oExtKeyDef->IsNullAllowed())
{
$aButtons = array('add', 'remove');
}
$this->DisplayEditInPlace($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons);
break;
case LINKSET_EDITMODE_ACTIONS:
default:
$this->DisplayAsBlock($oPage, $oValue, $aArgs = array(), $sFormPrefix, $oCurrentObj, true /* bDisplayMenu*/);
}
}
/**
* @param WebPage $oPage
* @param DBObjectSet $oValue
* @param array $aArgs
* @param string $sFormPrefix
* @param DBObject $oCurrentObj
* @param bool $bDisplayMenu
*
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, always called with default value)
*/
protected function DisplayAsBlock(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $bDisplayMenu)
{
$oLinksetDef = MetaModel::GetAttributeDef($this->sClass, $this->sAttCode);
$sTargetClass = $oLinksetDef->GetLinkedClass();
if ($oCurrentObj && $oCurrentObj->IsNew() && $bDisplayMenu)
{
$oPage->p(Dict::Format('UI:BeforeAdding_Class_ObjectsSaveThisObject', MetaModel::GetName($sTargetClass)));
}
else
{
$oFilter = new DBObjectSearch($sTargetClass);
$oFilter->AddCondition($oLinksetDef->GetExtKeyToMe(), $oCurrentObj->GetKey(),'=');
$aDefaults = array($oLinksetDef->GetExtKeyToMe() => $oCurrentObj->GetKey());
$oAppContext = new ApplicationContext();
foreach($oAppContext->GetNames() as $sKey)
{
// The linked object inherits the parent's value for the context
if (MetaModel::IsValidAttCode($this->sClass, $sKey) && $oCurrentObj)
{
$aDefaults[$sKey] = $oCurrentObj->Get($sKey);
}
}
$aParams = array(
'target_attr' => $oLinksetDef->GetExtKeyToMe(),
'object_id' => $oCurrentObj ? $oCurrentObj->GetKey() : null,
'menu' => $bDisplayMenu,
'menu_actions_target' => '_blank',
'default' => $aDefaults,
'table_id' => $this->sClass.'_'.$this->sAttCode,
);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oPage, $this->sInputid, $aParams);
}
}
/**
@@ -144,6 +229,55 @@ class UILinksWidgetDirect
$oPage->add('</div></div>');
}
/**
* @param WebPage $oPage
* @param DBObjectSet $oValue
* @param array $aArgs
* @param string $sFormPrefix
* @param DBObject $oCurrentObj
* @param array $aButtons
*
* @since 2.7.7 3.0.1 3.1.0 N°3129 Remove default value for $aArgs for PHP 8.0 compatibility (protected method, caller already handles it)
*/
protected function DisplayEditInPlace(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $aButtons = array('create', 'delete'))
{
$aAttribs = $this->GetTableConfig();
$oValue->Rewind();
$aData = array();
while ($oLinkObj = $oValue->Fetch()) {
$aRow = array();
$aRow['form::select'] = '<input type="checkbox" class="selectList'.$this->sInputid.'" value="'.$oLinkObj->GetKey().'"/>';
foreach ($this->aZlist as $sLinkedAttCode) {
$aRow[$sLinkedAttCode] = $oLinkObj->GetAsHTML($sLinkedAttCode);
}
$aData[] = $aRow;
}
$oDiv = UIContentBlockUIBlockFactory::MakeStandard($this->sInputid, ['listContainer']);
$oPage->AddSubBlock($oDiv);
$oDatatable = DataTableUIBlockFactory::MakeForForm($this->sInputid, $aAttribs, $aData);
$oDatatable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]);
$oDiv->AddSubBlock($oDatatable);
$sInputName = $sFormPrefix.'attr_'.$this->sAttCode;
$aLabels = array(
'delete' => Dict::S('UI:Button:Delete'),
// 'modify' => 'Modify...' ,
'creation_title' => Dict::Format('UI:CreationTitle_Class', MetaModel::GetName($this->sLinkedClass)),
'create' => Dict::Format('UI:ClickToCreateNew', MetaModel::GetName($this->sLinkedClass)),
'remove' => Dict::S('UI:Button:Remove'),
'add' => Dict::Format('UI:AddAnExisting_Class', MetaModel::GetName($this->sLinkedClass)),
'selection_title' => Dict::Format('UI:SelectionOf_Class', MetaModel::GetName($this->sLinkedClass)),
);
$oContext = new ApplicationContext();
$sSubmitUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?'.$oContext->GetForLink();
$sJSONLabels = json_encode($aLabels);
$sJSONButtons = json_encode($aButtons);
$sWizHelper = 'oWizardHelper'.$sFormPrefix;
// Don't automatically launch the search if the table is huge
$bDoSearch = !utils::IsHighCardinality($this->sLinkedClass);
$sJSDoSearch = $bDoSearch ? 'true' : 'false';
$oPage->add_ready_script("$('#{$this->sInputid}').directlinks({class_name: '$this->sClass', att_code: '$this->sAttCode', input_name:'$sInputName', labels: $sJSONLabels, submit_to: '$sSubmitUrl', buttons: $sJSONButtons, oWizardHelper: $sWizHelper, do_search: $sJSDoSearch});");
}
/**
* @param WebPage $oPage
* @param DBObject $oCurrentObj
@@ -299,21 +433,18 @@ HTML
{
}
public function GetTableConfig()
protected function GetTableConfig()
{
$aAttribs = array();
$aAttribs['form::select'] = array(
'label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);oWidget".$this->sInputid.".directlinks('instance')._onSelectChange();\" class=\"checkAll\"></input>",
'description' => Dict::S('UI:SelectAllToggle+'),
);
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->sInputid}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
foreach ($this->aZlist as $sLinkedAttCode) {
foreach($this->aZlist as $sLinkedAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($this->sLinkedClass, $sLinkedAttCode);
$aAttribs[$sLinkedAttCode] = array('label' => MetaModel::GetLabel($this->sLinkedClass, $sLinkedAttCode), 'description' => $oAttDef->GetOrderByHint());
}
return $aAttribs;
return $aAttribs;
}
/**
@@ -412,43 +543,12 @@ HTML
{
$sAttCode = call_user_func($aCallSpec, $key); // Returns null when there is no mapping for this parameter
}
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue)) {
if (MetaModel::IsValidAttCode($sDestClass, $sAttCode) && !empty($defaultValue))
{
$oSearch->AddCondition($sAttCode, $defaultValue);
}
}
}
}
public function GetClass(): string
{
return $this->sClass;
}
public function GetLinkedClass(): string
{
return $this->sLinkedClass;
}
public function GetAttCode(): string
{
return $this->sAttCode;
}
public function GetInputId(): string
{
return $this->sInputid;
}
public function GetNameSuffix(): string
{
return $this->sNameSuffix;
}
public function GetZList(): array
{
return $this->aZlist;
}
}

View File

@@ -6,18 +6,18 @@
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTableRow\FormTableRow;
use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEditTable;
use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog;
use Combodo\iTop\Application\UI\Links\Indirect\BlockIndirectLinksEdit\BlockIndirectLinksEdit;
use Combodo\iTop\Application\UI\Links\Indirect\BlockObjectPickerDialog\BlockObjectPickerDialog;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
require_once(APPROOT.'application/displayblock.class.inc.php');
class UILinksWidget
class UILinksWidget
{
protected $m_sClass;
protected $m_sAttCode;
protected $m_sNameSuffix;
protected $m_sInputId;
protected $m_iInputId;
protected $m_aAttributes;
protected $m_sExtKeyToRemote;
protected $m_sExtKeyToMe;
@@ -33,7 +33,7 @@ class UILinksWidget
*
* @param string $sClass
* @param string $sAttCode AttributeLinkedSetIndirect attcode
* @param string $sInputId
* @param int $iInputId
* @param string $sNameSuffix
* @param bool $bDuplicatesAllowed
*
@@ -41,14 +41,13 @@ class UILinksWidget
* @throws \DictExceptionMissingString
* @throws \Exception
*/
public function __construct($sClass, $sAttCode, $sInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
public function __construct($sClass, $sAttCode, $iInputId, $sNameSuffix = '', $bDuplicatesAllowed = false)
{
$this->m_sClass = $sClass;
$this->m_sAttCode = $sAttCode;
$this->m_sInputId = $sInputId;
$this->m_sNameSuffix = $sNameSuffix;
$this->m_iInputId = $iInputId;
$this->m_bDuplicatesAllowed = $bDuplicatesAllowed;
$this->m_aEditableFields = array();
/** @var AttributeLinkedSetIndirect $oAttDef */
@@ -64,7 +63,7 @@ class UILinksWidget
$this->m_aEditableFields = array();
$this->m_aTableConfig = array();
$this->m_aTableConfig['form::checkbox'] = array(
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_sInputId.".OnSelectChange();\">",
'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">",
'description' => Dict::S('UI:SelectAllToggle+'),
);
@@ -92,13 +91,225 @@ class UILinksWidget
}
}
/**
* A one-row form for editing a link record
*
* @param WebPage $oP Web page used for the ouput
* @param DBObject $oLinkedObj Remote object
* @param DBObject|int $linkObjOrId Either the lnk object or a unique number for new link records to add
* @param array $aArgs Extra context arguments
* @param DBObject $oCurrentObj The object to which all the elements of the linked set refer to
* @param int $iUniqueId A unique identifier of new links
* @param boolean $bReadOnly Display link as editable or read-only. Default is false (editable)
*
* @return array The HTML fragment of the one-row form
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false, $bModified = false)
{
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
$aRow = array();
$aFieldsMap = array();
$iKey = 0;
if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) {
$iKey = $linkObjOrId->GetKey();
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
$sPrefix .= "[$iKey][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}";
$aArgs['this'] = $linkObjOrId;
if ($bReadOnly) {
$aRow['form::checkbox'] = "";
foreach ($this->m_aEditableFields as $sFieldCode) {
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
$aRow[$sFieldCode] = $sDisplayValue;
}
} else {
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$iKey\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$iKey\">";
foreach ($this->m_aEditableFields as $sFieldCode) {
$sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode);
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId);
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
if ($bModified) {
$oP->add_ready_script(
<<<EOF
oWidget{$this->m_iInputId}.AddModified($iUniqueId, {$this->m_iInputId}, $sFieldCode, {$linkObjOrId->Get($sFieldCode)});
EOF
);
}
}
}
$sState = $linkObjOrId->GetState();
$sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);;
} else {
// form for creating a new record
if (is_object($linkObjOrId)) {
// New link existing only in memory
$oNewLinkObj = $linkObjOrId;
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
} else {
$iRemoteObjKey = $linkObjOrId;
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
}
$sPrefix .= "[-$iUniqueId][";
$sNameSuffix = "]"; // To make a tabular form
$aArgs['prefix'] = $sPrefix;
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}_".($iUniqueId < 0 ? -$iUniqueId : $iUniqueId);
$aArgs['this'] = $oNewLinkObj;
$sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId";
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"0\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$sInputValue\">";
if ($iUniqueId > 0) {
// Rows created with ajax call need OnLinkAdded call.
//
$oP->add_ready_script(
<<<EOF
PrepareWidgets();
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
EOF
);
} else {
// Rows added before loading the form don't have to call OnLinkAdded.
// Listeners are already present and DOM is not recreated
$iPositiveUniqueId = -$iUniqueId;
$oP->add_ready_script(<<<EOF
oWidget{$this->m_iInputId}.AddLink($iPositiveUniqueId, $iRemoteObjKey);
EOF
);
}
foreach($this->m_aEditableFields as $sFieldCode)
{
$sSafeFieldId = $this->GetFieldId($iUniqueId, $sFieldCode);
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $oNewLinkObj, $oP, $sNameSuffix, $sSafeFieldId);
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
$sValue = $oNewLinkObj->Get($sFieldCode);
$oP->add_ready_script(
<<<JS
oWidget{$this->m_iInputId}.OnValueChange($iKey, $iUniqueId, '$sFieldCode', '$sValue');
JS
);
}
$sState = '';
$sRemoteKeySafeFieldId = $this->GetFieldId($iUniqueId, $this->m_sExtKeyToRemote);
}
if (!$bReadOnly)
{
$sExtKeyToMeId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToMe);
$aFieldsMap[$this->m_sExtKeyToMe] = $sExtKeyToMeId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToMeId\" value=\"".$oCurrentObj->GetKey()."\">";
$sExtKeyToRemoteId = utils::GetSafeId($sPrefix.$this->m_sExtKeyToRemote);
$aFieldsMap[$this->m_sExtKeyToRemote] = $sExtKeyToRemoteId;
$aRow['form::checkbox'] .= "<input type=\"hidden\" id=\"$sExtKeyToRemoteId\" value=\"$iRemoteObjKey\">";
}
// Adding fields from remote class
// all fields are embedded in a span + added to $aFieldsMap array so that we can refresh them after extkey change
$aRemoteFieldsMap = [];
foreach (MetaModel::GetZListItems($this->m_sRemoteClass, 'list') as $sFieldCode)
{
$sSafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $sFieldCode);
$aRow['static::'.$sFieldCode] = "<span id='field_$sSafeFieldId'>".$oLinkedObj->GetAsHTML($sFieldCode).'</span>';
$aRemoteFieldsMap[$sFieldCode] = $sSafeFieldId;
}
// id field is needed so that remote object could be load server side
$aRemoteFieldsMap['id'] = $sRemoteKeySafeFieldId;
// Generate WizardHelper to update dependant fields
$this->AddWizardHelperInit($oP, $aArgs['wizHelper'], $this->m_sLinkedClass, $sState, $aFieldsMap);
//instantiate specific WizarHelper instance for remote class fields refresh
$bHasExtKeyUpdatingRemoteClassFields = (
array_key_exists('replaceDependenciesByRemoteClassFields', $aArgs)
&& ($aArgs['replaceDependenciesByRemoteClassFields'])
);
if ($bHasExtKeyUpdatingRemoteClassFields)
{
$this->AddWizardHelperInit($oP, $aArgs['wizHelperRemote'], $this->m_sRemoteClass, $sState, $aRemoteFieldsMap);
}
return $aRow;
}
private function AddRowForFieldCode(&$aRow, $sFieldCode, &$aArgs, $oLnk, $oP, $sNameSuffix, $sSafeFieldId): void
{
if (($sFieldCode === $this->m_sExtKeyToRemote))
{
// current field is the lnk extkey to the remote class
$aArgs['replaceDependenciesByRemoteClassFields'] = true;
$sRowFieldCode = 'static::key';
$aArgs['wizHelperRemote'] = $aArgs['wizHelper'].'_remote';
$aRemoteAttDefs = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($this->m_sRemoteClass);
$aRemoteCodes = array_map(
function ($value) {
return $value->GetCode();
},
$aRemoteAttDefs
);
$aArgs['remoteCodes'] = $aRemoteCodes;
}
else
{
$aArgs['replaceDependenciesByRemoteClassFields'] = false;
$sRowFieldCode = $sFieldCode;
}
$sValue = $oLnk->Get($sFieldCode);
$sDisplayValue = $oLnk->GetEditValue($sFieldCode);
$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sFieldCode);
$aRow[$sRowFieldCode] = '<div class="field_container" style="border:none;"><div class="field_data"><div class="field_value">'
.cmdbAbstractObject::GetFormElementForField(
$oP,
$this->m_sLinkedClass,
$sFieldCode,
$oAttDef,
$sValue,
$sDisplayValue,
$sSafeFieldId,
$sNameSuffix,
0,
$aArgs
)
.'</div></div></div>';
}
private function GetFieldId($iLnkId, $sFieldCode, $bSafe = true)
{
$sFieldId = $this->m_sInputId.'_'.$sFieldCode.'['.$iLnkId.']';
$sFieldId = $this->m_iInputId.'_'.$sFieldCode.'['.$iLnkId.']';
return ($bSafe) ? utils::GetSafeId($sFieldId) : $sFieldId;
}
private function AddWizardHelperInit($oP, $sWizardHelperVarName, $sWizardHelperClass, $sState, $aFieldsMap): void
{
$iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap);
$oP->add_script(
<<<JS
var $sWizardHelperVarName = new WizardHelper('$sWizardHelperClass', '', '$sState');
$sWizardHelperVarName.SetFieldsMap($sJsonFieldsMap);
$sWizardHelperVarName.SetFieldsCount($iFieldsCount);
$sWizardHelperVarName.SetReturnNotEditableFields(true);
$sWizardHelperVarName.SetWizHelperJsVarName('$sWizardHelperVarName');
JS
);
}
/**
* Display the table with the form for editing all the links at once
@@ -136,12 +347,100 @@ class UILinksWidget
*/
public function Display(WebPage $oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj): string
{
$oBlock = new BlockIndirectLinksEditTable($this);
$oBlock->InitTable($oPage, $oValue, $aArgs, $sFormPrefix, $oCurrentObj, $this->m_aTableConfig);
$sLinkedSetId = "{$this->m_sAttCode}{$this->m_sNameSuffix}";
$oBlock = new BlockIndirectLinksEdit("linkedset_{$sLinkedSetId}", ["ibo-block-indirect-links--edit"]);
$oBlock->sLinkedSetId = $sLinkedSetId;
$oBlock->sClass = $this->m_sClass;
$oBlock->sAttCode = $this->m_sAttCode;
$oBlock->iInputId = $this->m_iInputId;
$oBlock->sNameSuffix = $this->m_sNameSuffix;
$oBlock->bDuplicates = ($this->m_bDuplicatesAllowed) ? 'true' : 'false';
$oBlock->oWizHelper = 'oWizardHelper'.$sFormPrefix;
$oBlock->sExtKeyToRemote = $this->m_sExtKeyToRemote;
// Don't automatically launch the search if the table is huge
$oBlock->bJSDoSearch = utils::IsHighCardinality($this->m_sRemoteClass) ? 'false' : 'true';
$oBlock->sFormPrefix = $sFormPrefix;
$oBlock->sRemoteClass = $this->m_sRemoteClass;
$oValue->Rewind();
$aForm = array();
$iMaxAddedId = 0;
$iAddedId = -1; // Unique id for new links
$oBlock->aRemoved = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->m_sAttCode}_tbd", '[]', 'raw_data'));
$oModified = $oValue->GetModified($this->m_sExtKeyToRemote);
while ($oCurrentLink = $oValue->Fetch()) {
// We try to retrieve the remote object as usual
if (!in_array($oCurrentLink->GetKey(), $oBlock->aRemoved)) {
$bModified = false;
if (array_key_exists($oCurrentLink->GetKey(), $oModified)) {
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oModified[$oCurrentLink->GetKey()], false /* Must not be found */);
$bModified = true;
} else {
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
}
// If successful, it means that we can edit its link
if ($oLinkedObj !== null) {
$bReadOnly = false;
} // Else we retrieve it without restrictions (silos) and will display its link as readonly
else {
$bReadOnly = true;
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */, true);
}
if ($oCurrentLink->IsNew()) {
$key = $iAddedId--;
} else {
$key = $oCurrentLink->GetKey();
}
$iMaxAddedId = max($iMaxAddedId, $key);
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly, $bModified);
}
}
$oBlock->iMaxAddedId = (int)$iMaxAddedId;
$oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm);
$oDataTable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]);
$oBlock->AddSubBlock($oDataTable);
$oBlock->AddControls();
return ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock);
}
/**
* @param string $sClass
* @param string $sAttCode
*
* @return string
* @throws \Exception
*/
protected static function GetTargetClass($sClass, $sAttCode)
{
/** @var AttributeLinkedSet $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sLinkedClass = $oAttDef->GetLinkedClass();
$sTargetClass = '';
switch(get_class($oAttDef))
{
case 'AttributeLinkedSetIndirect':
/** @var AttributeExternalKey $oLinkingAttDef */
/** @var AttributeLinkedSetIndirect $oAttDef */
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
$sTargetClass = $oLinkingAttDef->GetTargetClass();
break;
case 'AttributeLinkedSet':
$sTargetClass = $sLinkedClass;
break;
}
return $sTargetClass;
}
/**
* @param WebPage $oPage
* @param DBObject $oCurrentObj
@@ -171,10 +470,15 @@ class UILinksWidget
$oCurrentObj->PrefillForm('search', $aPrefillFormParam);
}
$oBlock = new BlockObjectPickerDialog($this);
$sLinkedSetId = "{$this->m_sAttCode}{$this->m_sNameSuffix}";
$oBlock = new BlockObjectPickerDialog();
$oPage->AddUiBlock($oBlock);
$sLinkedSetId = $oBlock->oUILinksWidget->GetLinkedSetId();
$oBlock->sLinkedSetId = $sLinkedSetId;
$oBlock->iInputId = $this->m_iInputId;
$oBlock->sLinkedClassName = MetaModel::GetName($this->m_sLinkedClass);
$oBlock->sClassName = MetaModel::GetName($this->m_sClass);
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
@@ -243,8 +547,7 @@ class UILinksWidget
foreach ($aLinkedObjectIds as $iObjectId) {
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
if (is_object($oLinkedObj)) {
$oBlock = new BlockIndirectLinksEditTable($this);
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$oRow = new FormTableRow("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aRow, -$iAdditionId);
$oP->AddUiBlock($oRow);
$iAdditionId++;
@@ -271,8 +574,7 @@ class UILinksWidget
foreach ($aLinkedObjectIds as $iObjectId) {
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId, false);
if (is_object($oLinkedObj)) {
$oBlock = new BlockIndirectLinksEditTable($this);
$aRow = $oBlock->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$aRow = $this->GetFormRow($oP, $oLinkedObj, $iObjectId, array(), $oCurrentObj, $iAdditionId); // Not yet created link get negative Ids
$aData = [];
foreach ($aRow as $item) {
$aData[] = $item;
@@ -333,77 +635,24 @@ class UILinksWidget
/** @var AttributeExternalKey $oAttDef */
$sTargetClass = $oAttDef->GetTargetClass();
$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sTargetClass);
if ($sHierarchicalKeyCode !== false) {
if ($sHierarchicalKeyCode !== false)
{
$oFilter = new DBObjectSearch($sTargetClass);
$oFilter->AddCondition('id', $defaultValue);
$oHKFilter = new DBObjectSearch($sTargetClass);
$oHKFilter->AddCondition_PointingTo($oFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW);
$oSearch->AddCondition_PointingTo($oHKFilter, $sAttCode);
}
} catch (Exception $e)
{
}
catch (Exception $e) {
}
} else {
}
else
{
$oSearch->AddCondition($sAttCode, $defaultValue);
}
}
}
}
}
public function GetLinkedSetId(): string
{
return "{$this->m_sAttCode}{$this->m_sNameSuffix}";
}
public function GetClass(): string
{
return $this->m_sClass;
}
public function GetLinkedClass(): string
{
return $this->m_sLinkedClass;
}
public function GetAttCode(): string
{
return $this->m_sAttCode;
}
public function GetInputId(): string
{
return $this->m_sInputId;
}
public function GetNameSuffix(): string
{
return $this->m_sNameSuffix;
}
public function IsDuplicatesAllowed(): bool
{
return $this->m_bDuplicatesAllowed;
}
public function GetExternalKeyToRemote(): string
{
return $this->m_sExtKeyToRemote;
}
public function GetExternalKeyToMe(): string
{
return $this->m_sExtKeyToMe;
}
public function GetRemoteClass(): string
{
return $this->m_sRemoteClass;
}
public function GetEditableFields(): array
{
return $this->m_aEditableFields;
}
}

View File

@@ -69,16 +69,6 @@ class utils
* @since 3.0.0
*/
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
/**
* @var string To filter routes passed to back-end router before being redirected to corresponding controller / method
* @since 3.1.0
*/
public const ENUM_SANITIZATION_FILTER_ROUTE = 'route';
/**
* @var string To filter operation codes passed to back-end router before being redirected to corresponding controller (/ business logic in case of legacy operations)
* @since 3.1.0
*/
public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation';
/**
* @var string
* @since 3.0.0
@@ -416,8 +406,6 @@ class utils
break;
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
case static::ENUM_SANITIZATION_FILTER_ROUTE:
case static::ENUM_SANITIZATION_FILTER_OPERATION:
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
@@ -439,31 +427,27 @@ class utils
switch ($sSanitizationFilter)
{
case static::ENUM_SANITIZATION_FILTER_TRANSACTION_ID:
// Same as parameter type but keep the dot character
// transaction_id, the dot is mostly for Windows servers when using file storage as the tokens are named *.tmp
// - See N°1835
// - Note: It must be included at the regexp beginning otherwise you'll get an invalid character error
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
break;
case static::ENUM_SANITIZATION_FILTER_ROUTE:
case static::ENUM_SANITIZATION_FILTER_OPERATION:
// - Routes should be of the "controller_namespace_code.controller_method_name" form
// - Operations should be allowed to be namespaced as well even though then don't have dedicated controller yet
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[\.A-Za-z0-9_-]*$/')));
// same as parameter type but keep the dot character
// see N°1835 : when using file transaction_id on Windows you get *.tmp tokens
// it must be included at the regexp beginning otherwise you'll get an invalid character error
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[\. A-Za-z0-9_=-]*$/')));
break;
case static::ENUM_SANITIZATION_FILTER_PARAMETER:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
// Characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=-]*$/'))); // the '=', '%3D, '%2B', '%2F'
// characters are used in serialized filters (starting 2.5, only the url encoded versions are presents, but the "=" is kept for BC)
break;
case static::ENUM_SANITIZATION_FILTER_FIELD_NAME:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[A-Za-z0-9_]+(->[A-Za-z0-9_]+)*$/'))); // att_code or att_code->name or AttCode->Name or AttCode->Key2->Name
break;
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
array("options" => array("regexp" => '/^[ A-Za-z0-9_=%:+-]*$/')));
break;
}
@@ -874,50 +858,11 @@ class utils
*/
public static function DateTimeFormatToPHP($sOldDateTimeFormat)
{
$aSearch = ['%d', '%m', '%y', '%Y', '%H', '%i', '%s'];
$aReplacement = ['d', 'm', 'y', 'Y', 'H', 'i', 's'];
$aSearch = array('%d', '%m', '%y', '%Y', '%H', '%i', '%s');
$aReplacement = array('d', 'm', 'y', 'Y', 'H', 'i', 's');
return str_replace($aSearch, $aReplacement, $sOldDateTimeFormat);
}
/**
* Convert an old strtime() date/time format specification {@link https://www.php.net/manual/fr/function.strftime.php}
* to a format compatible with \DateTime::format {@link https://www.php.net/manual/fr/datetime.format.php}
*
* Example: '%Y-%m-%d %H:%M:%S' => 'Y-m-d H:i:s'
*
* Note: Not all strftime() formats can be converted, in which case they will be present in the returned string (eg. '%U' or '%W')
*
* @param string $sOldStrftimeFormat
*
* @return string
* @since 3.1.0
*/
public static function StrftimeFormatToDateTimeFormat(string $sOldStrftimeFormat): string
{
$aSearch = [
'%d', '%m', '%y', '%Y', '%H', '%M', '%S', // Most popular formats
'%a', '%A', '%e', '%j', '%u', '%w', // Day formats
'%U', '%V', '%W', // Week formats
'%b', '%B', '%h', // Month formats
'%C', '%g', '%G', // Year formats
'%k', '%I', '%l', '%p', '%P', '%r', '%R', '%T', '%X', '%z', '%Z', // Time formats
'%c', '%D', '%F', '%s', '%x', // Datetime formats
'%n', '%t', '%%', // Misc. formats
];
$aReplacement = [
'd', 'm', 'y', 'Y', 'H', 'i', 's',
'D', 'l', 'j', 'z', 'N', 'w',
'%U', 'W', '%W',
'M', 'F', 'M',
'%C', 'y', 'Y',
'G', 'h', 'g', 'A', 'a', 'h:i:s A', 'H:i', 'H:i:s', '%X', 'O', 'T',
'%c', 'm/d/y', 'Y-m-d', 'U', '%x',
'%n', '%t', '%',
];
return str_replace($aSearch, $aReplacement, $sOldStrftimeFormat);
}
/**
* Allow to set cached config. Useful when running with {@link Parameters} for example.
* @param \Config $oConfig
@@ -1449,83 +1394,71 @@ class utils
*/
public static function GetPopupMenuItemsBlock(iUIBlock &$oContainerBlock, $iMenuId, $param, &$aActions, $sDataTableId = null)
{
$aResult = [];
// 1st - add standard built-in menu items
//
switch($iMenuId)
{
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
// No native action there yet
break;
case iPopupMenuExtension::MENU_OBJLIST_TOOLKIT:
/** @var \DBObjectSet $param */
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
$sFilter = urlencode($param->GetFilter()->serialize());
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
// $param is a DBObjectSet
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink();
$sDataTableId = is_null($sDataTableId) ? '' : $sDataTableId;
$sUIPage = cmdbAbstractObject::ComputeStandardUIPage($param->GetFilter()->GetClass());
$sOQL = addslashes($param->GetFilter()->ToOQL(true));
$sFilter = urlencode($param->GetFilter()->serialize());
$sUrl = utils::GetAbsoluteUrlAppRoot()."pages/$sUIPage?operation=search&filter=".$sFilter."&{$sContext}";
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
// Configure this list on datatables
if (utils::IsNotNullOrEmptyString($sDataTableId)) {
$aResult[] = new JSPopupMenuItem(
'iTop::ConfigureList',
Dict::S('UI:ConfigureThisList'),
"$('#datatable_dlg_datatable_{$sDataTableId}').dialog('open'); return false;"
);
$aResult[] = new SeparatorPopupMenuItem();
$aResult = array();
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH)
{
$aResult[] = new SeparatorPopupMenuItem();
// Static menus: Email this page, CSV Export & Add to Dashboard
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
);
}
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO)
{
// Bulk export actions
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
if (extension_loaded('gd'))
{
// PDF export requires GD
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
}
if (strlen($sUrl) < SERVER_MAX_URL_LENGTH) {
// Static menus: Email this page, CSV Export & Add to Dashboard
$aResult[] = new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'),
"mailto:?body=".urlencode($sUrl).' ' // Add an extra space to make it work in Outlook
);
}
if (UserRights::IsActionAllowed($param->GetFilter()->GetClass(), UR_ACTION_BULK_READ, $param) != UR_ALLOWED_NO) {
// Bulk export actions
$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
if (extension_loaded('gd'))
{
// PDF export requires GD
$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
}
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
}
$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL', '$sContext')");
$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");
break;
case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
/** @var \DBObject $param */
$oObj = $param;
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
);
break;
// $param is a DBObject
$oObj = $param;
$sOQL = "SELECT ".get_class($oObj)." WHERE id=".$oObj->GetKey();
$sUrl = ApplicationContext::MakeObjectUrl(get_class($oObj), $oObj->GetKey());
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
$oContainerBlock->AddJsFileRelPath('js/tabularfieldsselector.js');
$oContainerBlock->AddJsFileRelPath('js/jquery.dragtable.js');
$oContainerBlock->AddCssFileRelPath('css/dragtable.css');
$aResult = array(
new SeparatorPopupMenuItem(),
// Static menus: Email this page & CSV Export
new URLPopupMenuItem('UI:Menu:EMail', Dict::S('UI:Menu:EMail'), "mailto:?subject=".urlencode($oObj->GetRawName())."&body=".urlencode($sUrl).' '), // Add an extra space to make it work in Outlook
new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")"),
new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")"),
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $sUrl.'&printable=1', '_blank'),
);
break;
case iPopupMenuExtension::MENU_DASHBOARD_ACTIONS:
// $param is a Dashboard
@@ -1533,19 +1466,19 @@ class utils
$oDashboard = $param;
$sDashboardId = $oDashboard->GetId();
$sDashboardFile = $oDashboard->GetDefinitionFile();
$sDashboardFileRelative = utils::LocalPath($sDashboardFile);
$sDlgTitle = addslashes(Dict::S('UI:ImportDashboardTitle'));
$sDlgText = addslashes(Dict::S('UI:ImportDashboardText'));
$sCloseBtn = addslashes(Dict::S('UI:Button:Cancel'));
$sDashboardFileJS = addslashes($sDashboardFileRelative);
$sDashboardFileURL = urlencode($sDashboardFileRelative);
$sDashboardFileJS = addslashes($sDashboardFile);
$sDashboardFileURL = urlencode($sDashboardFile);
$sUploadDashboardTransactId = utils::GetNewTransactionId();
$aResult = array(
new SeparatorPopupMenuItem(),
new URLPopupMenuItem('UI:ExportDashboard', Dict::S('UI:ExportDashBoard'), utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=export_dashboard&id='.$sDashboardId.'&file='.$sDashboardFileURL),
new JSPopupMenuItem('UI:ImportDashboard', Dict::S('UI:ImportDashBoard'), "UploadDashboard({dashboard_id: '$sDashboardId', file: '$sDashboardFileJS', title: '$sDlgTitle', text: '$sDlgText', close_btn: '$sCloseBtn', transaction: '$sUploadDashboardTransactId' })"),
);
if ($oDashboard->GetReloadURL()) {
if ($oDashboard->GetReloadURL())
{
$aResult[] = new SeparatorPopupMenuItem();
$aResult[] = new URLPopupMenuItem('UI:Menu:PrintableVersion', Dict::S('UI:Menu:PrintableVersion'), $oDashboard->GetReloadURL().'&printable=1', '_blank');
}
@@ -1554,25 +1487,34 @@ class utils
default:
// Unknown type of menu, do nothing
$aResult = array();
}
foreach ($aResult as $oMenuItem) {
foreach ($aResult as $oMenuItem)
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
}
// Invoke the plugins
//
/** @var \iPopupMenuExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance) {
if (is_object($param) && !($param instanceof DBObject)) {
foreach (MetaModel::EnumPlugins('iPopupMenuExtension') as $oExtensionInstance)
{
if (is_object($param) && !($param instanceof DBObject))
{
$tmpParam = clone $param; // In case the parameter is an DBObjectSet, clone it to prevent alterations
} else {
}
else
{
$tmpParam = $param;
}
foreach ($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem) {
if (is_object($oMenuItem)) {
foreach($oExtensionInstance->EnumItems($iMenuId, $tmpParam) as $oMenuItem)
{
if (is_object($oMenuItem))
{
$aActions[$oMenuItem->GetUID()] = $oMenuItem->GetMenuItem();
foreach ($oMenuItem->GetLinkedScripts() as $sLinkedScript) {
foreach($oMenuItem->GetLinkedScripts() as $sLinkedScript)
{
$oContainerBlock->AddJsFileRelPath($sLinkedScript);
}
}
@@ -1580,136 +1522,6 @@ class utils
}
}
/**
* We cannot use iMenuId (corresponding values in {@see \iPopupMenuExtension} constants) as value is always {@see \iPopupMenuExtension::MENU_OBJLIST_TOOLKIT}
* whenever we are in a datatable, whereas it is included in a object tab, a dashlet or a search.
*
* So a {@see \ContextTag} is set on the corresponding calls.
*
* @return bool true if we are in a search page context, either directly or by the datatable ajax call
*
* @since 3.1.0 N°3200
*
* @uses \ContextTag::TAG_OBJECT_SEARCH
*/
public static function IsCurrentPageASearch(): bool
{
if (ContextTag::Check(ContextTag::TAG_OBJECT_SEARCH)) {
return true;
}
return false;
}
/**
* @param \DBObjectSearch $oFilter object list
* @param array $aExtraParams
*
* @return string|null null if we are already in a search, otherwise the URL to open this list in a search
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @uses utils::IsCurrentPageASearch()
*
* @since 3.1.0 N°3200
*/
public static function GetDataTableSearchUrl(DBSearch $oFilter, array $aExtraParams): ?string
{
if (static::IsCurrentPageASearch()) {
// we don't want to add the link when already in a search page !
return null;
}
$bIsObjectRelation = isset($aExtraParams['object_id'], $aExtraParams['target_attr']);
if ($bIsObjectRelation) {
[$oDataTableSearchFilter, $aParams] = static::GetDataTableSearchForRelations($oFilter, $aExtraParams);
} else {
$oDataTableSearchFilter = $oFilter;
$aParams = [];
}
if (isset($aExtraParams['table_id'])) {
$aParams['table_id'] = $aExtraParams['table_id'];
}
$sParams = json_encode($aParams);
$sAppRootUrl = static::GetAbsoluteUrlAppRoot();
$oAppContext = new ApplicationContext();
$sUrl = $sAppRootUrl
.'pages/UI.php?operation=search&'
.$oAppContext->GetForLink()
.'&filter='.rawurlencode($oDataTableSearchFilter->serialize());
$sUrl .= '&aParams='.rawurlencode($sParams); // Not working... yet, cause not handled by UI.php
return $sUrl;
}
/**
* Rewrites filter for object relations, so that in the search page we will have the correct criteria and will be able to use "configure this list"
*
* @param \DBSearch $oFilter object list
* @param array{link_attr: string, target_attr: string, object_id: string} $aExtraParams
*
* @return array{\DBObjectSearch, string[]}
* @throws \CoreException
* @throws \OQLException
*
* @since 3.1.0 N°3200
*/
private static function GetDataTableSearchForRelations(DBSearch $oFilter, array $aExtraParams): array
{
$sObjectId = $aExtraParams['object_id'];
$bIsLinkedSetIndirect = isset($aExtraParams['link_attr']);
if ($bIsLinkedSetIndirect) {
//--- AttributeLinkedSetIndirect (n,n => lnk class)
$sLnkClass = $oFilter->GetClass();
$sExtKeyToObjectClass = $aExtraParams['link_attr'];
$sExtKeyToRemoteClass = $aExtraParams['target_attr'];
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
$oLnkExtKeyToRemote = MetaModel::GetAttributeDef($sLnkClass, $sExtKeyToRemoteClass);
$sRemoteClass = $oLnkExtKeyToRemote->GetTargetClass();
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
$oLnkExtKeyToObject = MetaModel::GetAttributeDef($sLnkClass, $sExtKeyToObjectClass);
$sObjectClass = $oLnkExtKeyToObject->GetTargetClass();
/** @var \AttributeExternalKey $oLnkExtKeyToRemote */
$oObjectExtKeyToLnk = $oLnkExtKeyToObject->GetMirrorLinkAttribute();
$sObjectExtKeyToLnkClass = $oObjectExtKeyToLnk->GetCode();
$sRemoteClassAliasName = ormLinkSet::REMOTE_ALIAS;
$sLnkClassAliasName = ormLinkSet::LINK_ALIAS;
$sOql = <<<SQL
SELECT {$sRemoteClassAliasName},{$sLnkClassAliasName}
FROM {$sRemoteClass} AS {$sRemoteClassAliasName}
JOIN {$sLnkClass} AS {$sLnkClassAliasName} ON {$sLnkClassAliasName}.$sExtKeyToRemoteClass = {$sRemoteClassAliasName}.id
WHERE {$sLnkClassAliasName}.$sExtKeyToObjectClass = $sObjectId
SQL;
$aAttCodesToDisplay = MetaModel::GetAttributeLinkedSetIndirectDatatableAttCodesToDisplay($sObjectClass, $sObjectExtKeyToLnkClass, $sRemoteClass, $sExtKeyToRemoteClass);
/** @noinspection PhpUnnecessaryLocalVariableInspection */
$sAttCodesToDisplay = implode(',', $aAttCodesToDisplay);
$aParams = [
'zlist' => false,
'extra_fields' => $sAttCodesToDisplay,
];
} else {
//--- AttributeLinkedSet (1,n => AttributeExternalKey)
$sClass = $oFilter->GetClass();
$sExtKeyCode = $aExtraParams['target_attr'];
$sOql = "SELECT $sClass WHERE $sExtKeyCode = $sObjectId";
$aParams = [];
}
$oDataTableSearchFilter = DBSearch::FromOQL($sOql);
return [$oDataTableSearchFilter, $aParams];
}
/**
* @param string $sEnvironment
*
@@ -1717,10 +1529,10 @@ SQL;
*/
public static function GetConfigFilePath($sEnvironment = null)
{
if (is_null($sEnvironment)) {
if (is_null($sEnvironment))
{
$sEnvironment = self::GetCurrentEnvironment();
}
return APPCONF.$sEnvironment.'/'.ITOP_CONFIG_FILE;
}
@@ -2937,18 +2749,12 @@ HTML;
$bSkipped = true;
}
else {
$sPHPFile = self::LocalPath($sPHPFile);
if ($sPHPFile !== false) {
$sPHPFile = '/'.$sPHPFile; // for regex
foreach ($aExcludedPath as $sExcludedPath) {
// Note: We use '#' as delimiters as usual '/' is often used in paths.
if ($sExcludedPath !== '' && preg_match('#'.$sExcludedPath.'#', $sPHPFile) === 1) {
$bSkipped = true;
break;
}
foreach ($aExcludedPath as $sExcludedPath) {
// Note: We use '#' as delimiters as usual '/' is often used in paths.
if ($sExcludedPath !== '' && preg_match('#'.$sExcludedPath.'#', $sPHPFile) === 1) {
$bSkipped = true;
break;
}
} else {
$bSkipped = true; // file not found
}
}
@@ -2986,7 +2792,7 @@ HTML;
$aResultPref = [];
$aShortcutPrefs = appUserPreferences::GetPref('keyboard_shortcuts', []);
// Note: Mind the 4 blackslashes, see utils::GetClassesForInterface()
$aShortcutClasses = utils::GetClassesForInterface('iKeyboardShortcut', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
$aShortcutClasses = utils::GetClassesForInterface('iKeyboardShortcut', '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]'));
foreach ($aShortcutClasses as $cShortcutPlugin) {
$sTriggeredElement = $cShortcutPlugin::GetShortcutTriggeredElementSelector();
@@ -3213,35 +3019,18 @@ HTML;
}
/**
* Transform a snake_case $sInput into a CamelCase string
*
* @param string $sInput
*
* @return string Camel case representation of $sInput (eg. "something_new" becomes "SomethingNew")
* @return string
* @since 2.7.0
*/
public static function ToCamelCase($sInput): string
public static function ToCamelCase($sInput)
{
return str_replace(' ', '', ucwords(strtr($sInput, '_-', ' ')));
}
/**
* @param string $sInput
*
* @return string Snake case representation of $sInput (eg. "SomethingNew" becomes "something_new")
* @since 3.1.0
* @link https://stackoverflow.com/a/19533226/2710325
*/
public static function ToSnakeCase(string $sInput): string
{
// Remove special chars to join words
$sOutput = preg_replace('/(\W)/', '_', $sInput);
// Transform camel case words to snake case
$sOutput = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', '_$0', $sOutput);
// Lowercase everything
$sOutput = mb_strtolower($sOutput);
// Trim outer underscores
return trim($sOutput, '_');
}
/**
* @param string $sInput
*
@@ -3400,4 +3189,9 @@ HTML;
{
return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
}
public static function GetUniqId()
{
return hash('sha256', uniqid(sprintf('%x', rand()), true).sprintf('%x', rand()));
}
}

View File

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

View File

@@ -12,7 +12,7 @@
"ext-json": "*",
"ext-mysqli": "*",
"ext-soap": "*",
"apereo/phpcas" : "~1.6.0",
"apereo/phpcas" : "~1.3",
"combodo/tcpdf": "~6.4.4",
"guzzlehttp/guzzle": "^7.4.5",
"laminas/laminas-mail": "^2.11",

26
composer.lock generated
View File

@@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1fee7c7fd7649286a09641ae53e216de",
"content-hash": "276d2024de344c0d4105b15850560696",
"packages": [
{
"name": "apereo/phpcas",
"version": "1.6.0",
"version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/apereo/phpCAS.git",
"reference": "f817c72a961484afef95ac64a9257c8e31f063b9"
"reference": "d6f5797fb568726f34c8e48741776d81e4a2646b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/f817c72a961484afef95ac64a9257c8e31f063b9",
"reference": "f817c72a961484afef95ac64a9257c8e31f063b9",
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/d6f5797fb568726f34c8e48741776d81e4a2646b",
"reference": "d6f5797fb568726f34c8e48741776d81e4a2646b",
"shasum": ""
},
"require": {
@@ -70,9 +70,9 @@
],
"support": {
"issues": "https://github.com/apereo/phpCAS/issues",
"source": "https://github.com/apereo/phpCAS/tree/1.6.0"
"source": "https://github.com/apereo/phpCAS/tree/1.5.0"
},
"time": "2022-10-31T20:39:27+00:00"
"time": "2022-05-03T21:12:54+00:00"
},
{
"name": "combodo/tcpdf",
@@ -4974,16 +4974,16 @@
},
{
"name": "twig/twig",
"version": "v3.4.3",
"version": "v3.4.2",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58"
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"reference": "e07cdd3d430cd7e453c31b36eb5ad6c0c5e43077",
"shasum": ""
},
"require": {
@@ -5034,7 +5034,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.4.3"
"source": "https://github.com/twigphp/Twig/tree/v3.4.2"
},
"funding": [
{
@@ -5046,7 +5046,7 @@
"type": "tidelift"
}
],
"time": "2022-09-28T08:42:51+00:00"
"time": "2022-08-12T06:47:24+00:00"
},
{
"name": "webmozart/assert",

View File

@@ -43,24 +43,22 @@ abstract class Action extends cmdbAbstractObject
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "name",
"complementary_name_attcode" => array('finalclass', 'description'),
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-in-transit.svg'),
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_action",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-in-transit.svg'),
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values" => new ValueSetEnum(array('test' => 'Being tested', 'enabled' => 'In production', 'disabled' => 'Inactive')), "sql" => "status", "default_value" => "test", "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list",
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "action_id", "ext_key_to_remote" => "trigger_id", "allowed_values" => null, "count_min" => 0, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list", array("linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
// Display lists
// - Attributes to be displayed for the complete details

View File

@@ -181,7 +181,7 @@ abstract class AsyncTask extends DBObject
if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
{
$aConfig = $aRetries[get_class($this)];
$bExponential = (bool) ($aConfig['exponential_delay'] ?? $bExponential);
$bExponential = (bool)$aConfig['exponential_delay'] ?? $bExponential;
}
return $bExponential;
}

View File

@@ -5,15 +5,12 @@
*/
use Combodo\iTop\Application\UI\Base\Component\FieldBadge\FieldBadgeUIBlockFactory;
use Combodo\iTop\Application\UI\Links\Indirect\BlockLinksSetDisplayAsProperty;
use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Field\TextAreaField;
use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
use Combodo\iTop\Form\Validator\Validator;
use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Service\Links\LinkSetModel;
require_once('MyHelpers.class.inc.php');
require_once('ormdocument.class.inc.php');
@@ -92,15 +89,8 @@ 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_RELATIONTYPE_PROPERTY', 'property');
define('LINKSET_RELATIONTYPE_LINK', 'link');
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
/**
* Attributes implementing this interface won't be accepted as `group by` field
*
* @since 2.7.4 N°3473
*/
interface iAttributeNoGroupBy
@@ -146,15 +136,6 @@ abstract class AttributeDefinition
abstract public function GetEditClass();
/**
* @return array Css classes
* @since 3.1.0 N°3190
*/
public function GetCssClasses(): array
{
return $this->aCSSClasses;
}
/**
* Return the search widget type corresponding to this attribute
*
@@ -376,18 +357,6 @@ abstract class AttributeDefinition
return false;
}
/**
* Returns true if the attribute can be used in bulk modify.
*
* @return bool
* @since 3.1.0 N°3190
*
*/
public static function IsBulkModifyCompatible(): bool
{
return static::IsScalar();
}
/**
* Returns true if the attribute value is a set of related objects (1-N or N-N)
*
@@ -651,16 +620,6 @@ abstract class AttributeDefinition
return $sLabel;
}
/**
* @return bool True if the attribute has a description {@see \AttributeDefinition::GetDescription()}
* @throws \Exception
* @since 3.1.0
*/
public function HasDescription(): bool
{
return utils::IsNotNullOrEmptyString($this->GetDescription());
}
/**
* @param string|null $sDefault
*
@@ -1454,7 +1413,6 @@ class AttributeLinkedSet extends AttributeDefinition
public function __construct($sCode, $aParams)
{
parent::__construct($sCode, $aParams);
$this->aCSSClasses[] = 'attribute-set';
}
public static function ListExpectedParams()
@@ -1468,12 +1426,6 @@ class AttributeLinkedSet extends AttributeDefinition
return "LinkedSet";
}
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return false;
}
public function IsWritable()
{
return true;
@@ -1491,26 +1443,7 @@ class AttributeLinkedSet extends AttributeDefinition
public function GetValuesDef()
{
$oValSetDef = $this->Get("allowed_values");
if (!$oValSetDef) {
// Let's propose every existing value
$oValSetDef = new ValueSetObjects('SELECT '.LinkSetModel::GetTargetClass($this));
}
return $oValSetDef;
}
public function GetEditValue($value, $oHostObj = null)
{
/** @var ormLinkSet $value * */
if ($value->Count() === 0) {
return '';
}
/** Return linked objects key as string **/
$aValues = $value->GetValues();
return implode(' ', $aValues);
return $this->Get("allowed_values");
}
public function GetPrerequisiteAttributes($sClass = null)
@@ -1577,42 +1510,11 @@ class AttributeLinkedSet extends AttributeDefinition
return $this->GetOptional('tracking_level', MetaModel::GetConfig()->Get('tracking_level_linked_set_default'));
}
/**
* @return string see LINKSET_EDITMODE_* constants
* @since 3.1.0 N°5563 relations are edited using new attributes in details mode, but as nothing changed in edit mode we are still using edit_mode attribute
*/
public function GetEditMode()
{
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
}
/**
* @return string see LINKSET_RELATIONTYPE_* constants
* @since 3.1.0 N°5563
*/
public function GetRelationType()
{
return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK);
}
/**
* @return string see LINKSET_DISPLAY_STYLE_* constants
* @since 3.1.0 N°3190
*/
public function GetDisplayStyle()
{
return $this->GetOptional('display_style', LINKSET_DISPLAY_STYLE_TAB);
}
/**
* @return boolean
* @since 3.1.0 N°5563
*/
public function GetReadOnly()
{
return $this->GetOptional('read_only', false);
}
public function GetLinkedClass()
{
return $this->Get('linked_class');
@@ -1638,30 +1540,49 @@ class AttributeLinkedSet extends AttributeDefinition
return '';
}
/** @inheritDoc * */
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true): string
/**
* @param string $sValue
* @param \DBObject $oHostObject
* @param bool $bLocalize
*
* @return string|null
*
* @throws \CoreException
*/
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{
try {
/** @var ormLinkSet $sValue */
if ($sValue->Count() === 0) {
return '';
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
{
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch())
{
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
{
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
$sAttValue = $oObj->GetAsHTML($sAttCode);
if (strlen($sAttValue) > 0)
{
$aAttributes[] = $sAttValue;
}
}
$sAttributes = implode(', ', $aAttributes);
$aItems[] = $sAttributes;
}
$oLinkSetBlock = new BlockLinksSetDisplayAsProperty($this->GetCode(), $this, $sValue);
return ConsoleBlockRenderer::RenderBlockTemplates($oLinkSetBlock);
return implode('<br/>', $aItems);
}
catch (Exception $e) {
$sMessage = "Error while displaying attribute {$this->GetCode()}";
IssueLog::Error($sMessage, IssueLog::CHANNEL_DEFAULT, [
'host_object_class' => $this->GetHostClass(),
'host_object_key' => $oHostObject->GetKey(),
'attribute' => $this->GetCode(),
]);
return $sMessage;
}
return null;
}
/**
@@ -2368,15 +2289,6 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
return $this->GetOptional("duplicates", false);
} // The same object may be linked several times... or not...
/**
* @return boolean
* @since 3.1.0 N°5563
*/
public function GetReadOnly()
{
return $this->GetOptional('read_only', false);
}
public function GetTrackingLevel()
{
return $this->GetOptional('tracking_level',
@@ -2414,13 +2326,6 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
return $oRet;
}
/** @inheritDoc */
public static function IsBulkModifyCompatible(): bool
{
return true;
}
}
/**

View File

@@ -21,7 +21,6 @@ MetaModel::IncludeModule('application/menunode.class.inc.php');
MetaModel::IncludeModule('application/user.preferences.class.inc.php');
MetaModel::IncludeModule('application/user.dashboard.class.inc.php');
MetaModel::IncludeModule('application/audit.rule.class.inc.php');
MetaModel::IncludeModule('application/audit.domain.class.inc.php');
MetaModel::IncludeModule('application/query.class.inc.php');
MetaModel::IncludeModule('setup/moduleinstallation.class.inc.php');

View File

@@ -1160,112 +1160,136 @@ class BulkChange
}
$iPreviousTimeLimit = ini_get('max_execution_time');
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
// Avoid too many events
cmdbAbstractObject::SetEventDBLinksChangedBlocked(true);
try {
foreach ($this->m_aData as $iRow => $aRowData) {
set_time_limit(intval($iLoopTimeLimit));
if (isset($aResult[$iRow]["__STATUS__"])) {
// An issue at the earlier steps - skip the rest
continue;
}
try {
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
$bSkipQuery = false;
foreach ($this->m_aReconcilKeys as $sAttCode) {
$valuecondition = null;
if (array_key_exists($sAttCode, $this->m_aExtKeys)) {
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode)) {
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oExtKey->IsNullAllowed()) {
$valuecondition = $oExtKey->GetNullValue();
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oExtKey->GetNullValue());
} else {
$aResult[$iRow][$sAttCode] = new CellStatus_NullIssue();
}
} else {
// The value has to be found or verified
/** var DBObjectSearch $oReconFilter */
list($oReconFilter, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
if (count($aMatches) == 1) {
$oRemoteObj = reset($aMatches); // first item
$valuecondition = $oRemoteObj->GetKey();
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
} elseif (count($aMatches) == 0) {
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
} else {
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
}
}
} else {
// The value is given in the data row
$iCol = $this->m_aAttList[$sAttCode];
if ($sAttCode == 'id') {
$valuecondition = $aRowData[$iCol];
} else {
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
$valuecondition = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
}
if (is_null($valuecondition)) {
$bSkipQuery = true;
} else {
$oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '=');
}
}
if ($bSkipQuery) {
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Reconciliation'));
} else {
$oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
switch ($oReconciliationSet->Count()) {
case 0:
$oTargetObj = $this->CreateObject($aResult, $iRow, $aRowData, $oChange);
// $aResult[$iRow]["__STATUS__"]=> set in CreateObject
$aVisited[] = $oTargetObj->GetKey();
break;
case 1:
$oTargetObj = $oReconciliationSet->Fetch();
$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
if (!is_null($this->m_sSynchroScope)) {
$aVisited[] = $oTargetObj->GetKey();
}
break;
default:
// Found several matches, ambiguous
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
$aResult[$iRow]["id"] = new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->serialize());
$aResult[$iRow]["finalclass"] = 'n/a';
}
}
} catch (Exception $e) {
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-Internal', get_class($e), $e->getMessage()));
}
foreach($this->m_aData as $iRow => $aRowData)
{
set_time_limit(intval($iLoopTimeLimit));
if (isset($aResult[$iRow]["__STATUS__"]))
{
// An issue at the earlier steps - skip the rest
continue;
}
try
{
$oReconciliationFilter = new DBObjectSearch($this->m_sClass);
$bSkipQuery = false;
foreach($this->m_aReconcilKeys as $sAttCode)
{
$valuecondition = null;
if (array_key_exists($sAttCode, $this->m_aExtKeys))
{
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
{
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oExtKey->IsNullAllowed())
{
$valuecondition = $oExtKey->GetNullValue();
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oExtKey->GetNullValue());
}
else
{
$aResult[$iRow][$sAttCode] = new CellStatus_NullIssue();
}
}
else
{
// The value has to be found or verified
if (!is_null($this->m_sSynchroScope)) {
// Compute the delta between the scope and visited objects
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
$oScopeSet = new DBObjectSet($oScopeSearch);
while ($oObj = $oScopeSet->Fetch()) {
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited)) {
set_time_limit(intval($iLoopTimeLimit));
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
/** var DBObjectSearch $oReconFilter */
list($oReconFilter, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
if (count($aMatches) == 1)
{
$oRemoteObj = reset($aMatches); // first item
$valuecondition = $oRemoteObj->GetKey();
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
}
elseif (count($aMatches) == 0)
{
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
}
else
{
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
}
}
}
else
{
// The value is given in the data row
$iCol = $this->m_aAttList[$sAttCode];
if ($sAttCode == 'id')
{
$valuecondition = $aRowData[$iCol];
}
else
{
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
$valuecondition = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
}
if (is_null($valuecondition))
{
$bSkipQuery = true;
}
else
{
$oReconciliationFilter->AddCondition($sAttCode, $valuecondition, '=');
}
}
if ($bSkipQuery)
{
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Reconciliation'));
}
else
{
$oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
switch($oReconciliationSet->Count())
{
case 0:
$oTargetObj = $this->CreateObject($aResult, $iRow, $aRowData, $oChange);
// $aResult[$iRow]["__STATUS__"]=> set in CreateObject
$aVisited[] = $oTargetObj->GetKey();
break;
case 1:
$oTargetObj = $oReconciliationSet->Fetch();
$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
if (!is_null($this->m_sSynchroScope))
{
$aVisited[] = $oTargetObj->GetKey();
}
break;
default:
// Found several matches, ambiguous
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->serialize());
$aResult[$iRow]["finalclass"]= 'n/a';
}
}
}
} finally {
// Send all the retained events for further computations
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
cmdbAbstractObject::FireEventDbLinksChangedForAllObjects();
catch (Exception $e)
{
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::Format('UI:CSVReport-Row-Issue-Internal', get_class($e), $e->getMessage()));
}
}
if (!is_null($this->m_sSynchroScope))
{
// Compute the delta between the scope and visited objects
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
$oScopeSet = new DBObjectSet($oScopeSearch);
while ($oObj = $oScopeSet->Fetch())
{
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
set_time_limit(intval($iLoopTimeLimit));
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
set_time_limit(intval($iPreviousTimeLimit));
// Fill in the blanks - the result matrix is expected to be 100% complete

View File

@@ -472,7 +472,7 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
$sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_');
$sDisplayUrl = $oPrevDoc->GetDisplayURL(get_class($this), $this->GetKey(), 'prevdata');
$sDownloadLabel = Dict::S('UI:DownloadDocument_');
$sDownloadLabel = Dict::Format('UI:DownloadDocument_');
$sDownloadUrl = $oPrevDoc->GetDownloadURL(get_class($this), $this->GetKey(), 'prevdata');
$sDocView = <<<HTML

View File

@@ -1611,22 +1611,4 @@ class CMDBSource
return 'ALTER DATABASE'.CMDBSource::GetSqlStringColumnDefinition().';';
}
/**
* Check which mysql client option (--ssl or --ssl-mode) to be used for encrypted connection
*
* @return bool true if --ssl-mode should be used, false otherwise
* @throws \MySQLException
*
* @link https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#encrypted-connection-options "Command Options for Encrypted Connections"
*/
public static function IsSslModeDBVersion()
{
if (static::GetDBVendor() === static::ENUM_DB_VENDOR_MYSQL)
{
//Mysql 5.7.0 and upper deprecated --ssl and uses --ssl-mode instead
return version_compare(static::GetDBVersion(), '5.7.11', '>=');
}
return false;
}
}

View File

@@ -131,15 +131,15 @@ class Config
],
'event_service.debug.filter_events' => [
'type' => 'array',
'description' => 'List of events name to filter Event Service debug messages',
'default' => [],
'description' => 'Filter Event Service debug by events',
'default' => '',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'event_service.debug.filter_sources' => [
'type' => 'array',
'description' => 'List of event sources to filter Event Service debug messages',
'description' => 'Filter Event Service debug by event sources',
'default' => '',
'value' => '',
'source_of_value' => '',
@@ -342,11 +342,11 @@ class Config
'show_in_conf_sample' => false,
],
'allow_menu_on_linkset' => [
'type' => 'bool',
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
'default' => true,
'value' => true,
'source_of_value' => '',
'type' => 'bool',
'description' => 'Display Action menus in view mode on any LinkedSet with edit_mode != none',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'allow_target_creation' => [
@@ -592,29 +592,13 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'email_transport_smtp.allow_self_signed' => array(
'type' => 'bool',
'description' => 'Allow self signed peer certificates',
'default' => false,
'value' => false,
'email_css' => [
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_transport_smtp.verify_peer' => array(
'type' => 'bool',
'description' => 'Verify peer certificate',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'email_css' => [
'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications',
'default' => "",
'value' => "",
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'email_default_sender_address' => [
'type' => 'string',
@@ -1270,14 +1254,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'navigation_menu.show_organization_filter' => [
'type' => 'bool',
'description' => 'Display organization filter in menu',
'default' => true,
'value' => true,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'quick_create.enabled' => [
'type' => 'bool',
'description' => 'Whether or not the quick create is enabled',
@@ -1545,11 +1521,11 @@ class Config
'show_in_conf_sample' => false,
],
'security.hide_administrators' => [
'type' => 'bool',
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
'default' => true,
'value' => true,
'source_of_value' => '',
'type' => 'bool',
'description' => 'If true, non-administrator users will not be able to see the administrator accounts, the Administrator profile and the links between the administrator accounts and their profiles.',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'behind_reverse_proxy' => [
@@ -1592,14 +1568,6 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'audit.enable_selection_landing_page' => [
'type' => 'bool',
'description' => 'If true audit categories must be selected before results are computed (use this setting in case of a lot of audit categories)',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
];
public function IsProperty($sPropCode)
@@ -1917,7 +1885,7 @@ class Config
}
if (strlen($sNoise) > 0)
{
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
throw new ConfigException('Syntax error in configuration file',
array('file' => $sConfigFile, 'error' => '<tt>'.utils::EscapeHtml($sNoise, ENT_QUOTES).'</tt>'));
}
@@ -2302,7 +2270,7 @@ class Config
$oHandle = null;
$sConfig = null;
if ($this->m_sFile !== null && is_file($this->m_sFile))
if (is_file($this->m_sFile))
{
$oHandle = fopen($this->m_sFile, 'r');
$index = 0;
@@ -2477,15 +2445,10 @@ class Config
/**
* Helper function to initialize a configuration from the page arguments
*
* @see \Parameters::GetParamForConfigArray() to get aParamValues from {@see Parameters} object hierarchy in setup
* @see \WizardController::GetParamForConfigArray() to get aParamValues from {@see \WizardController} object hierarchy in setup
*
* @param array $aParamValues
* @param ?string $sModulesDir
* @param string|null $sModulesDir
* @param bool $bPreserveModuleSettings
*
* @return void The current object is modified directly
*
* @throws \Exception
* @throws \CoreException
*/
@@ -2732,7 +2695,7 @@ class ConfigPlaceholdersResolver
}
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
{
return $rawValue;

View File

@@ -52,23 +52,17 @@
*/
class ContextTag
{
public const TAG_PORTAL = 'GUI:Portal';
public const TAG_CRON = 'CRON';
public const TAG_CONSOLE = 'GUI:Console';
public const TAG_SETUP = 'Setup';
public const TAG_SYNCHRO = 'Synchro';
public const TAG_REST = 'REST/JSON';
/**
* @var string
* @since 3.1.0 N°3200
*/
public const TAG_OBJECT_SEARCH = 'ObjectSearch';
const TAG_PORTAL = 'GUI:Portal';
const TAG_CRON = 'CRON';
const TAG_CONSOLE = 'GUI:Console';
const TAG_SETUP = 'Setup';
const TAG_SYNCHRO = 'Synchro';
const TAG_REST = 'REST/JSON';
protected static $aStack = array();
/**
* Store a context tag on the stack
*
* @param string $sTag
*/
public function __construct($sTag)

File diff suppressed because it is too large Load Diff

View File

@@ -53,18 +53,6 @@ class DBObjectSet implements iDBObjectSetIterator
* @var array
*/
protected $m_aAttToLoad;
/**
* @var null|array
*/
protected $m_aExtendedDataSpec;
/**
* @var int Maximum number of elements to retrieve
*/
protected $m_iLimitCount;
/**
* @var int Offset from which elements should be retrieved
*/
protected $m_iLimitStart;
/**
* @var array
*/
@@ -153,7 +141,7 @@ class DBObjectSet implements iDBObjectSetIterator
{
$sRet = '';
$this->Rewind();
$sRet .= "Set (".$this->m_oFilter->ToOQL(true).")<br/>\n";
$sRet .= "Set (".$this->m_oFilter->ToOQL().")<br/>\n";
$sRet .= "Query: <pre style=\"font-size: smaller; display:inline;\">".$this->m_oFilter->MakeSelectQuery().")</pre>\n";
$sRet .= $this->Count()." records<br/>\n";
@@ -166,7 +154,6 @@ class DBObjectSet implements iDBObjectSetIterator
}
$sRet .= "</ul>\n";
}
$this->Rewind();
return $sRet;
}

View File

@@ -773,14 +773,14 @@ abstract class DBSearch
* @see DBSearch::ToOQL()
*
* @param string $sQuery The OQL to convert to a DBSearch
* @param array $aParams array of <mixed> params index by <string> name
* @param mixed[string] $aParams array of <mixed> params index by <string> name
* @param ModelReflection|null $oMetaModel The MetaModel to use when checking the consistency of the OQL
*
* @return DBObjectSearch|DBUnionSearch
*
* @throws OQLException
*/
public static function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
static public function FromOQL($sQuery, $aParams = null, ModelReflection $oMetaModel=null)
{
if (empty($sQuery))
{
@@ -1717,6 +1717,6 @@ abstract class DBSearch
*/
public function __toString()
{
return $this->ToOQL(true);
return $this->ToOQL();
}
}

View File

@@ -30,7 +30,6 @@ use DOMDocument;
use DOMFormatException;
use IssueLog;
use LogAPI;
use utils;
/**
* Class \Combodo\iTop\DesignDocument

View File

@@ -110,7 +110,7 @@ class Dict
*
* @param string $sStringCode The code identifying the dictionary entry
* @param string $sDefault Default value if there is no match in the dictionary
* @param bool $bUserLanguageOnly False to allow the use of the default language as a fallback, true otherwise
* @param bool $bUserLanguageOnly True to allow the use of the default language as a fallback, false otherwise
*
* @return string
*/
@@ -174,7 +174,7 @@ class Dict
*
* @return string
*/
public static function Format($sFormatCode /*, ... arguments ... */)
public static function Format($sFormatCode /*, ... arguments ....*/)
{
$sLocalizedFormat = self::S($sFormatCode);
$aArguments = func_get_args();

View File

@@ -134,7 +134,7 @@ abstract class DOMSanitizer extends HTMLSanitizer
public function DoSanitize($sHTML)
{
$this->oDoc = new DOMDocument();
$this->oDoc->preserveWhiteSpace = true;
$this->oDoc->preserveWhitespace = true;
// MS outlook implements empty lines by the mean of <p><o:p></o:p></p>
// We have to transform that into <p><br></p> (which is how Thunderbird implements empty lines)
@@ -286,8 +286,8 @@ class HTMLDOMSanitizer extends DOMSanitizer
'strong' => array(),
'img' => array('src', 'style', 'alt', 'title'),
'ul' => array('style'),
'ol' => array('reversed', 'start', 'style', 'type'),
'li' => array('style', 'value'),
'ol' => array('style'),
'li' => array('style'),
'h1' => array('style'),
'h2' => array('style'),
'h3' => array('style'),
@@ -402,7 +402,7 @@ class HTMLDOMSanitizer extends DOMSanitizer
public function LoadDoc($sHTML)
{
@$this->oDoc->loadHTML('<?xml encoding="UTF-8"?>'.$sHTML); // For loading HTML chunks where the character set is not specified
$this->oDoc->preserveWhiteSpace = true;
$this->oDoc->preserveWhitespace = true;
}
public function PrintDoc()

View File

@@ -551,15 +551,6 @@ class LogChannels
*/
public const NOTIFICATIONS = 'notifications';
/**
* @var string Everything related to the backup / restore
* @since 3.1.0
*/
public const BACKUP = 'backup';
/**
* @since 3.0.0
*/
public const CLI = 'CLI';
/**
@@ -569,35 +560,22 @@ class LogChannels
*/
public const CMDB_SOURCE = 'cmdbsource';
/**
* @since 3.0.0
*/
public const CONSOLE = 'console';
public const CONSOLE = 'console';
public const CORE = 'core';
public const CORE = 'core';
/**
* @var string Everything related to the datatable component
* @since 3.1.0
*/
public const DATATABLE = 'Datatable';
public const DEADLOCK = 'DeadLock';
public const DEADLOCK = 'DeadLock';
public const INLINE_IMAGE = 'InlineImage';
public const PORTAL = 'portal';
/**
* @var string Everything related to the event service
* @since 3.1.0
* @var string
* @since 3.1.0 specific channel for event service
*/
public const EVENT_SERVICE = 'EventService';
/**
* @var string Everything related to the datamodel CRUD
* @since 3.1.0
*/
public const DM_CRUD = 'DMCRUD';
}
@@ -1051,7 +1029,6 @@ class DeprecatedCallsLog extends LogAPI
public const ENUM_CHANNEL_FILE = 'deprecated-file';
public const CHANNEL_DEFAULT = self::ENUM_CHANNEL_PHP_METHOD;
/** @var string Warning this constant won't be used directly ! To see the real default level check {@see GetLevelDefault()} */
public const LEVEL_DEFAULT = self::LEVEL_ERROR;
/** @var \FileLog we want our own instance ! */
@@ -1126,12 +1103,7 @@ class DeprecatedCallsLog extends LogAPI
}
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4);
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call (can be either 'trigger_deprecation' or the faulty method), level 3 = In some cases, method containing the 'trigger_deprecation' call
// In case current level is actually a 'trigger_deprecation' call, try to go one level further to get the real deprecated method
if (array_key_exists($iStackDeprecatedMethodLevel, $aStack) && ($aStack[$iStackDeprecatedMethodLevel]['function'] === 'trigger_deprecation') && array_key_exists($iStackDeprecatedMethodLevel + 1, $aStack)) {
$iStackDeprecatedMethodLevel++;
}
$iStackDeprecatedMethodLevel = 2; // level 0 = current method, level 1 = @trigger_error, level 2 = method containing the `trigger_error` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
if (($sDeprecatedObject === __CLASS__) && ($sDeprecatedMethod === 'Log')) {
@@ -1176,6 +1148,7 @@ class DeprecatedCallsLog extends LogAPI
* - else call parent method
*
* In other words, when in dev mode all deprecated calls will be logged to file
*
*/
protected static function GetLevelDefault(string $sConfigKey)
{

View File

@@ -128,17 +128,9 @@ abstract class MetaModel
/** @var string */
protected static $m_sEnvironment = 'production';
/**
* Objects currently created/updated.
*
* if an object is already being updated, then this method will return this object instead of recreating a new one.
* At this point the method DBUpdate of a new object with the same class and id won't do anything due to reentrance protection,
* so to ensure that the potential modifications are correctly saved, the object currently being updated is returned.
* DBUpdate() method then will take care that all the modifications will be saved.
*
* [class][key] -> object
*/
protected static array $m_aReentranceProtection = [];
public const REENTRANCE_TYPE_UPDATE = 'update';
protected static $m_aReentranceProtection = [];
/**
* MetaModel constructor.
@@ -350,7 +342,7 @@ abstract class MetaModel
{
self::_check_subclass($sClass);
return call_user_func([$sClass, 'GetClassName'], $sClass);
return $sClass::GetClassName($sClass);
}
/**
@@ -434,7 +426,7 @@ abstract class MetaModel
{
self::_check_subclass($sClass);
return call_user_func([$sClass, 'GetClassDescription'], $sClass);
return $sClass::GetClassDescription($sClass);
}
/**
@@ -2072,43 +2064,6 @@ abstract class MetaModel
return $aAttCodesToPrint;
}
/**
* @param string $sObjectClass class of the object containing the AttributeLinkedSetIndirect (eg: Team)
* @param string $sObjectLinkedSetIndirectAttCode code of the AttributeLinkedSetIndirect in the sObjectClass (eg: persons_list in the Team class, pointing to lnkPersonToTeam lnk class)
* @param string $sRemoteClass remote class pointed by the lnk class (eg: Person pointed by lnkPersonToTeam)
* @param string $sLnkExternalKeyToRemoteClassAttCode in the lnk class, external key to the remote class (eg: person_id in lnkPersonToTeam, pointing to a Person instance)
*
* @return string[] attcodes to display, containing aliases
* @throws \CoreException
*
* @since 3.0.0 N°2334 added code for n-n relations in {@see BlockIndirectLinksViewTable::GetAttCodesToDisplay}
* @since 3.1.0 N°3200 method creation so that it can be used elsewhere
*/
public static function GetAttributeLinkedSetIndirectDatatableAttCodesToDisplay(string $sObjectClass, string $sObjectLinkedSetIndirectAttCode, string $sRemoteClass, string $sLnkExternalKeyToRemoteClassAttCode):array
{
$aLnkAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($sObjectClass, $sObjectLinkedSetIndirectAttCode);
$aRemoteAttDefsToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectRemoteClass($sRemoteClass);
$aLnkAttCodesToDisplay = array_map(
function ($oLnkAttDef) {
return ormLinkSet::LINK_ALIAS.'.'.$oLnkAttDef->GetCode();
},
$aLnkAttDefsToDisplay
);
if (!in_array(ormLinkSet::LINK_ALIAS.'.'.$sLnkExternalKeyToRemoteClassAttCode, $aLnkAttCodesToDisplay)) {
// we need to display a link to the remote class instance !
$aLnkAttCodesToDisplay[] = ormLinkSet::LINK_ALIAS.'.'.$sLnkExternalKeyToRemoteClassAttCode;
}
$aRemoteAttCodesToDisplay = array_map(
function ($oRemoteAttDef) {
return ormLinkSet::REMOTE_ALIAS.'.'.$oRemoteAttDef->GetCode();
},
$aRemoteAttDefsToDisplay
);
$aAttCodesToDisplay = array_merge($aLnkAttCodesToDisplay, $aRemoteAttCodesToDisplay);
return $aAttCodesToDisplay;
}
/**
* @param string $sClass
* @param string $sListCode
@@ -5126,9 +5081,9 @@ abstract class MetaModel
if (!empty(self::$m_sTablePrefix))
{
foreach(self::DBEnumTables() as $sTable)
foreach(CMDBSource::EnumTables() as $sTable)
{
// perform a case-insensitive test because on Windows the table names become lowercase :-(
// perform a case insensitive test because on Windows the table names become lowercase :-(
if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix))
{
CMDBSource::DropTable($sTable);
@@ -6841,7 +6796,7 @@ abstract class MetaModel
// DBUpdate() method then will take care that all the modifications will be saved.
if (array_key_exists($sClassAlias.'id', $aRow)) {
$iKey = $aRow[$sClassAlias."id"];
$oObject = self::GetReentranceObject($sClass, $iKey);
$oObject = self::GetReentranceObject(Metamodel::REENTRANCE_TYPE_UPDATE, $sClass, $iKey);
if ($oObject !== false) {
return $oObject;
}
@@ -7606,32 +7561,33 @@ abstract class MetaModel
return $oAttDef->GetStyle($sValue);
}
protected static function GetReentranceObject($sClass, $sKey)
protected static function GetReentranceObject($sType, $sClass, $sKey)
{
if (isset(self::$m_aReentranceProtection[$sClass][$sKey])) {
return self::$m_aReentranceProtection[$sClass][$sKey];
if (isset(self::$m_aReentranceProtection[$sType][$sClass][$sKey])) {
return self::$m_aReentranceProtection[$sType][$sClass][$sKey];
}
return false;
}
/**
* @param $sType
* @param \DBObject $oObject
*
* @return bool true if reentry possible
*/
public static function StartReentranceProtection(DBObject $oObject)
public static function StartReentranceProtection($sType, DBObject $oObject)
{
if (isset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()])) {
if (isset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()])) {
return false;
}
self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()] = $oObject;
self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()] = $oObject;
return true;
}
public static function StopReentranceProtection(DBObject $oObject)
public static function StopReentranceProtection($sType, DBObject $oObject)
{
if (isset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()])) {
unset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()]);
if (isset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()])) {
unset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()]);
}
}
}

View File

@@ -2860,26 +2860,9 @@ class FunctionExpression extends Expression
{
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
}
// N°5985 - Since PHP 8.1+, a bug fix on \DateTimeInterval for a date anterior to "1937-05-23" now returns a different number of days. The workaround below aim at making the code work with PHP 7.4 => 8.2+
//
// $oDate = new DateTimeImmutable('2020-01-02');
// $oZero = new DateTimeImmutable('1937-05-22');
// $iRet = (int) $oDate->diff($oZero)->format('%a');
// echo $iRet."\n"; // 30174 (PHP 8.0) vs 30175 (PHP 8.1+)
//
// $oDate = new DateTimeImmutable('2020-01-02');
// $oZero = new DateTimeImmutable('1937-05-23');
// $iRet = (int) $oDate->diff($oZero)->format('%a');
// echo $iRet."\n"; // 30174 (PHP 8.0) vs 30174 (PHP 8.1+)
//
// To work around that we take 1970-01-01 as "zero date" and we offset it with the number of days between 1582-01-01 and 1970-01-01.
// Note that as the "target" date could be between 1582-01-01 and 1970-01-01 we have to format the interval with the "-"/"+" sign in order to correct the number of days.
$oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs));
$oZero = new DateTime('1970-01-01');
$iDaysBetween19700101And15800101 = 141713;
$iRet = (int) $oZero->diff($oDate)->format('%R%a') + 577815 + $iDaysBetween19700101And15800101;
$oZero = new DateTime('1582-01-01');
$iRet = (int) $oDate->diff($oZero)->format('%a') + 577815;
return $iRet;
case 'FROM_DAYS':

View File

@@ -26,37 +26,6 @@
class OQLException extends CoreException
{
/**
* @var string
* @since 3.1.0
*/
protected $m_MyIssue;
/**
* @var string
* @since 3.1.0
*/
protected $m_sInput;
/**
* @var int
* @since 3.1.0
*/
protected $m_iLine;
/**
* @var int
* @since 3.1.0
*/
protected $m_iCol;
/**
* @var string
* @since 3.1.0
*/
protected $m_sUnexpected;
/**
* @var array|null string
* @since 3.1.0
*/
protected $m_aExpecting;
public function __construct($sIssue, $sInput, $iLine, $iCol, $sUnexpected, $aExpecting = null)
{
$this->m_MyIssue = $sIssue;

View File

@@ -25,8 +25,8 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\EventData;
use Combodo\iTop\Service\EventService;
/**
@@ -219,7 +219,7 @@ class ormDocument
'object' => $oObj,
'document' => $oDocument,
);
EventService::FireEvent(new EventData(EVENT_DOWNLOAD_DOCUMENT, $sClass, $aEventData));
EventService::FireEvent(new EventData(EVENT_SERVICE_DOWNLOAD_DOCUMENT, $sClass, $aEventData));
$oPage->TrashUnexpectedOutput();
$oPage->SetContentType($oDocument->GetMimeType());
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());

View File

@@ -608,16 +608,32 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
$aAdded = $this->aAdded;
$aModified = $this->aModified;
$aRemoved = array();
if (count($this->aRemoved) > 0)
{
if (count($this->aRemoved) > 0) {
$oSearch = new DBObjectSearch($this->sClass);
$oSearch->AddCondition('id', $this->aRemoved, 'IN');
$oSet = new DBObjectSet($oSearch);
$aRemoved = $oSet->ToArray();
}
return array_merge($aAdded, $aModified, $aRemoved);
}
/**
* Get the list of all modified (added, modified and removed) links
*
* @return array of link objects
* @throws \Exception
*/
public function GetModified($sExtKeyToMe)
{
$aModified = [];
foreach ($this->aModified as $oObj) {
$aModified[$oObj->GetKey()] = $oObj->Get($sExtKeyToMe);
}
return $aModified;
}
/**
* @param DBObject $oHostObject
*
@@ -661,8 +677,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
{
$aCheckLinks[] = $iLinkId;
}
foreach ($this->aModified as $iLinkId => $oLink)
{
foreach ($this->aModified as $iLinkId => $oLink) {
$aCheckLinks[] = $oLink->GetKey();
}
@@ -698,8 +713,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
// Write the links according to the existing links
//
foreach ($this->aAdded as $oLink)
{
foreach ($this->aAdded as $oLink) {
// Make sure that the objects in the set point to "this"
$oLink->Set($sExtKeyToMe, $oHostObject->GetKey());
@@ -847,30 +861,11 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
}
$oLinkSet = new DBObjectSet($oLinkSearch);
$oLinkSet->SetShowObsoleteData($bShowObsolete);
if ($this->HasDelta()) {
if ($this->HasDelta())
{
$oLinkSet->AddObjectArray($this->aAdded);
}
return $oLinkSet;
}
/**
* GetValues.
*
* @return array of tag codes
*/
public function GetValues()
{
$aValues = array();
foreach ($this->aPreserved as $sTagCode => $oTag) {
$aValues[] = $sTagCode;
}
foreach ($this->aAdded as $sTagCode => $oTag) {
$aValues[] = $sTagCode;
}
sort($aValues);
return $aValues;
}
}

View File

@@ -499,9 +499,9 @@ final class ormTagSet extends ormSet
}
/**
* @param $sTagLabel
* @param $sTagCode
*
* @return string Tag code
* @return DBObject tag
* @throws \CoreUnexpectedValue
* @throws \CoreException
*/

View File

@@ -21,19 +21,6 @@ use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactor
*/
class PDFBulkExport extends HTMLBulkExport
{
/**
* @var string For sample purposes
* @internal
* @since 2.7.8
*/
const ENUM_OUTPUT_TYPE_SAMPLE = 'sample';
/**
* @var string For the real export
* @internal
* @since 2.7.8
*/
const ENUM_OUTPUT_TYPE_REAL = 'real';
public function DisplayUsage(Page $oP)
{
$oP->p(" * pdf format options:");
@@ -210,25 +197,6 @@ EOF
return $sPDF;
}
/**
* @inheritDoc
* @since 2.7.8
*/
protected function GetSampleData($oObj, $sAttCode)
{
if ($sAttCode !== 'id')
{
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
// As sample data will be displayed in the web browser, AttributeImage needs to be rendered with a regular HTML format, meaning its "src" looking like "data:image/png;base64,iVBORw0KGgoAAAANSUh..."
// Whereas for the PDF generation it needs to be rendered with a TCPPDF-compatible format, meaning its "src" looking like "@iVBORw0KGgoAAAANSUh..."
if ($oAttDef instanceof AttributeImage) {
return $this->GetAttributeImageValue($oAttDef, $oObj->Get($sAttCode), static::ENUM_OUTPUT_TYPE_SAMPLE);
}
}
return parent::GetSampleData($oObj, $sAttCode);
}
protected function GetValue($oObj, $sAttCode)
{
switch($sAttCode)
@@ -244,7 +212,31 @@ EOF
$oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode);
if ($oAttDef instanceof AttributeImage)
{
$sRet = $this->GetAttributeImageValue($oAttDef, $value, static::ENUM_OUTPUT_TYPE_REAL);
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
//
$iDefaultMaxWidthPx = 48;
$iDefaultMaxHeightPx = 48;
if ($value->IsEmpty())
{
$iNewWidth = $iDefaultMaxWidthPx;
$iNewHeight = $iDefaultMaxHeightPx;
$sUrl = $oAttDef->Get('default_image');
}
else
{
list($iWidth, $iHeight) = utils::GetImageSize($value->GetData());
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$sUrl = 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
}
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px">' : '';
$sRet = '<div class="ibo-input-image--image-view">'.$sRet.'</div>';
}
else
{
@@ -259,55 +251,6 @@ EOF
return $sRet;
}
/**
* @param \AttributeImage $oAttDef Instance of image attribute
* @param \ormDocument $oValue Value of image attribute
* @param string $sOutputType {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_SAMPLE}, {@see \PDFBulkExport::ENUM_OUTPUT_TYPE_REAL}
*
* @return string Rendered value of $oAttDef / $oValue according to the desired $sOutputType
* @since 2.7.8
*/
protected function GetAttributeImageValue(AttributeImage $oAttDef, ormDocument $oValue, string $sOutputType)
{
// To limit the image size in the PDF output, we have to enforce the size as height/width because max-width/max-height have no effect
//
$iDefaultMaxWidthPx = 48;
$iDefaultMaxHeightPx = 48;
if ($oValue->IsEmpty()) {
$iNewWidth = $iDefaultMaxWidthPx;
$iNewHeight = $iDefaultMaxHeightPx;
$sUrl = $oAttDef->Get('default_image');
} else {
list($iWidth, $iHeight) = utils::GetImageSize($oValue->GetData());
$iMaxWidthPx = min($iDefaultMaxWidthPx, $oAttDef->Get('display_max_width'));
$iMaxHeightPx = min($iDefaultMaxHeightPx, $oAttDef->Get('display_max_height'));
$fScale = min($iMaxWidthPx / $iWidth, $iMaxHeightPx / $iHeight);
$iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale;
$sValueAsBase64 = base64_encode($oValue->GetData());
switch ($sOutputType) {
case static::ENUM_OUTPUT_TYPE_SAMPLE:
$sUrl = 'data:'.$oValue->GetMimeType().';base64,'.$sValueAsBase64;
break;
case static::ENUM_OUTPUT_TYPE_REAL:
default:
// TCPDF requires base64-encoded images to be rendered without the usual "data:<MIMETYPE>;base64" header but with an "@"
// @link https://tcpdf.org/examples/example_009/
$sUrl = '@'.$sValueAsBase64;
break;
}
}
$sRet = ($sUrl !== null) ? '<img src="'.$sUrl.'" style="width: '.$iNewWidth.'px; height: '.$iNewHeight.'px;">' : '';
$sRet = '<div class="ibo-input-image--image-view">'.$sRet.'</div>';
return $sRet;
}
public function GetSupportedFormats()
{
return array('pdf' => Dict::S('Core:BulkExport:PDFFormat'));

View File

@@ -30,40 +30,18 @@
/**
* Element of the response formed by RestResultWithObjects
*
* @package RESTAPI
* @api
* @package REST Services
*/
class ObjectResult
{
/**
* @var int
* @api
*/
public $code;
/**
* @var string
* @api
*/
public $message;
/**
* @var mixed|null
* @api
*/
public $class;
/**
* @var mixed|null
* @api
*/
public $key;
/**
* @var array
* @api
*/
public $fields;
/**
* Default constructor
* @api
*/
public function __construct($sClass = null, $iId = null)
{
@@ -76,17 +54,11 @@ class ObjectResult
/**
* Helper to make an output value for a given attribute
*
* @api
*
* @param DBObject $oObject The object being reported
* @param string $sAttCode The attribute code (must be valid)
* @param boolean $bExtendedOutput Output all of the link set attributes ?
*
* @return string A scalar representation of the value
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
protected function MakeResultValue(DBObject $oObject, $sAttCode, $bExtendedOutput = false)
{
@@ -140,17 +112,11 @@ class ObjectResult
/**
* Report the value for the given object attribute
*
* @api
*
* @param DBObject $oObject The object being reported
* @param string $sAttCode The attribute code (must be valid)
* @param boolean $bExtendedOutput Output all of the link set attributes ?
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function AddField(DBObject $oObject, $sAttCode, $bExtendedOutput = false)
{
@@ -163,7 +129,8 @@ class ObjectResult
/**
* REST response for services managing objects. Derive this structure to add information and/or constants
*
* @package RESTAPI
* @package Extensibility
* @package REST Services
* @api
*/
class RestResultWithObjects extends RestResult
@@ -173,19 +140,13 @@ class RestResultWithObjects extends RestResult
/**
* Report the given object
*
* @api
* @param int $iCode An error code (RestResult::OK is no issue has been found)
*
* @param int An error code (RestResult::OK is no issue has been found)
* @param string $sMessage Description of the error if any, an empty string otherwise
* @param DBObject $oObject The object being reported
* @param array|null $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
* @param array $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
* @param boolean $bExtendedOutput Output all of the link set attributes ?
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function AddObject($iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false)
{
@@ -223,30 +184,16 @@ class RestResultWithObjects extends RestResult
}
}
/**
* @package RESTAPI
* @api
*/
class RestResultWithRelations extends RestResultWithObjects
{
public $relations;
/**
* @api
*/
public function __construct()
{
parent::__construct();
$this->relations = array();
}
/**
* @param $sSrcKey
* @param $sDestKey
*
* @return void
* @api
*/
public function AddRelation($sSrcKey, $sDestKey)
{
if (!array_key_exists($sSrcKey, $this->relations))
@@ -260,7 +207,7 @@ class RestResultWithRelations extends RestResultWithObjects
/**
* Deletion result codes for a target object (either deleted or updated)
*
* @package RESTAPI
* @package Extensibility
* @api
* @since 2.0.1
*/
@@ -268,37 +215,30 @@ class RestDelete
{
/**
* Result: Object deleted as per the initial request
* @api
*/
const OK = 0;
/**
* Result: general issue (user rights or ... ?)
* @api
* Result: general issue (user rights or ... ?)
*/
const ISSUE = 1;
/**
* Result: Must be deleted to preserve database integrity
* @api
* Result: Must be deleted to preserve database integrity
*/
const AUTO_DELETE = 2;
/**
* Result: Must be deleted to preserve database integrity, but that is NOT possible
* @api
* Result: Must be deleted to preserve database integrity, but that is NOT possible
*/
const AUTO_DELETE_ISSUE = 3;
/**
* Result: Must be deleted to preserve database integrity, but this must be requested explicitly
* @api
* Result: Must be deleted to preserve database integrity, but this must be requested explicitely
*/
const REQUEST_EXPLICITELY = 4;
/**
* Result: Must be updated to preserve database integrity
* @api
*/
const AUTO_UPDATE = 5;
/**
* Result: Must be updated to preserve database integrity, but that is NOT possible
* @api
*/
const AUTO_UPDATE_ISSUE = 6;
}
@@ -635,7 +575,7 @@ class CoreServices implements iRestServiceProvider
$oObject = $oElement->GetProperty('object');
if ($oObject)
{
if ($bEnableRedundancy && $sDirection == 'down')
if ($bEnableRedundancy)
{
// Add only the "reached" objects
if ($oElement->GetProperty('is_reached'))

View File

@@ -42,7 +42,7 @@ class TemplateStringPlaceholder
public function __construct($sToken)
{
$this->sToken = $sToken;
$this->sAttCode = '';
$this->sAttcode = '';
$this->sFunction = '';
$this->sParamName = '';
$this->bIsValid = false; // Validity may be false in general, but it can work anyway (thanks to specialization) when rendering
@@ -90,9 +90,9 @@ class TemplateString
$oPlaceholder->sFunction = '';
$oPlaceholder->sParamName = $sParamName;
$sAttCode = substr($sPlaceholder, strlen($sParamPrefix));
$oPlaceholder->sAttCode = $sAttCode;
$oPlaceholder->bIsValid = MetaModel::IsValidAttCode($sClass, $sAttCode, true /* extended */);
$sAttcode = substr($sPlaceholder, strlen($sParamPrefix));
$oPlaceholder->sAttcode = $sAttcode;
$oPlaceholder->bIsValid = MetaModel::IsValidAttCode($sClass, $sAttcode, true /* extended */);
}
}
@@ -164,7 +164,7 @@ class TemplateString
$oRef = $aParamValues[$oPlaceholder->sParamName];
try
{
$value = $oRef->Get($oPlaceholder->sAttCode);
$value = $oRef->Get($oPlaceholder->sAttcode);
$aSearch[] = '$'.$oPlaceholder->sToken.'$';
$aReplace[] = $value;
$oPlaceholder->bIsValid = true;

View File

@@ -33,22 +33,20 @@ abstract class Trigger extends cmdbAbstractObject
{
$aParams = array
(
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "description",
"complementary_name_attcode" => array('finalclass'),
"state_attcode" => "",
"reconc_keys" => array('description'),
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-conflict.svg'),
"category" => "grant_by_profile,core/cmdb",
"key_type" => "autoincrement",
"name_attcode" => "description",
"state_attcode" => "",
"reconc_keys" => array('description'),
"db_table" => "priv_trigger",
"db_key_field" => "id",
"db_finalclass_field" => "realclass",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-conflict.svg'),
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values" => null, "sql" => "description", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list",
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "trigger_id", "ext_key_to_remote" => "action_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list", array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "trigger_id", "ext_key_to_remote" => "action_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array())));
$aTags = ContextTag::GetTags();
MetaModel::Init_AddAttribute( new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));

View File

@@ -348,7 +348,7 @@ abstract class User extends cmdbAbstractObject
$aChanges = $this->ListChanges();
if (array_key_exists('login', $aChanges)) {
// Check login uniqueness
if ( $this->GetOriginal('login') === null || strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0) {
if (strcasecmp($this->Get('login'), $this->GetOriginal('login')) !== 0) {
$sNewLogin = $aChanges['login'];
$oSearch = DBObjectSearch::FromOQL_AllData("SELECT User WHERE login = :newlogin");
if (!$this->IsNew()) {
@@ -1936,7 +1936,7 @@ class UserRights
// The bug has been fixed in PHP 7.2, but in case session_regenerate_id()
// fails we just silently ignore the error and keep the same session id...
$old_error_handler = set_error_handler(array(__CLASS__, 'VoidErrorHandler'));
Session::RegenerateId(true);
session_regenerate_id(true);
if ($old_error_handler !== null) {
set_error_handler($old_error_handler);
}

View File

@@ -43,9 +43,3 @@
.ibo-svg-illustration--container > svg *[fill="#6c63ff"]{
fill: $ibo-svg-illustration--fill;
}
// Workaround for a Safari (unsupported browser) issue: https://sourceforge.net/p/itop/discussion/itop-hub/thread/fc97ec4a10/?limit=25#1cc0/9930
// Fix found here: https://stackoverflow.com/questions/40895387/z-index-not-working-on-safari-fine-on-firefox-and-chrome
.ibo-navigation-menu.ibo-is-active .ibo-navigation-menu--drawer{
transform: translate3d(0,0,0);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,3 +12,17 @@ select + label, label + select, label > select,
input + label, label + input, label > input {
margin-left: $ibo-input--spacing-left--with-label;
}
.ibo-input-with-label--label {
&.ibo-has-description {
&::after {
content: $ibo-field--label--description--content;
padding-left: $ibo-field--label--description--padding-left;
vertical-align: top;
cursor: pointer;
color: $ibo-field--label--description--color;
@extend %ibo-font-ral-bol-50;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -9,6 +9,9 @@ $ibo-alert--padding-x: 20px !default;
$ibo-alert--min-height: 30px !default;
$ibo-alert--border-radius: $ibo-border-radius-300 !default;
$ibo-alert--title--highlight--width: 4px !default;
$ibo-alert--title--highlight--height: 100% !default;
$ibo-alert--body--margin-top: $ibo-spacing-200 !default;
$ibo-alert-minimized--padding-y: 5px !default;
@@ -65,7 +68,14 @@ $ibo-alert-colors: (
@extend %ibo-font-size-150;
&::before {
@include ibo-vertical-highlight;
display: block;
position: absolute;
top: 0;
left: 0;
content: '';
width: $ibo-alert--title--highlight--width;
height: $ibo-alert--title--highlight--height;
}
.ibo-alert--title {

View File

@@ -1,5 +1,5 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -21,7 +21,7 @@
@import "prop";
@import "title";
@import "datatable/all";
@import "datatable";
@import "form";
@import "fieldset";
@import "field";

View File

@@ -1,5 +1,5 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -148,6 +148,4 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
@extend %ibo-font-ral-sembol-100;
border-bottom: $ibo-vendors-datatables--columns-header--border-bottom;
}
}
}

View File

@@ -6,6 +6,10 @@
/* SCSS variables */
$ibo-field--value--color: $ibo-color-grey-800 !default;
$ibo-field--label--description--content: "?" !default;
$ibo-field--label--description--padding-left: $ibo-spacing-200 !default;
$ibo-field--label--description--color: $ibo-color-grey-600 !default;
$ibo-field--background-color--is-fullscreen: $ibo-color-white-100 !default;
$ibo-field--label--width--is-fullscreen: 100% !default;
@@ -158,6 +162,18 @@ $ibo-field--enable-bulk--checkbox--margin-left: $ibo-spacing-300 !default;
width: 30%;
word-break: break-word; /* We want labels to wrap if it is very long if it has no spaces */
@extend %ibo-font-weight-600;
> .ibo-has-description {
&::after {
content: $ibo-field--label--description--content;
padding-left: $ibo-field--label--description--padding-left;
vertical-align: top;
cursor: pointer;
color: $ibo-field--label--description--color;
@extend %ibo-font-ral-bol-50;
}
}
}
.ibo-field--label-small .ibo-field--label{
width: 20em;

View File

@@ -1,48 +1,4 @@
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-modal-option--do-not-show-again--margin-top: $ibo-spacing-500 !default;
$ibo-modal--is-informative--min-width: $ibo-size-700 !default;
$ibo-modal--is-informative--min-height: $ibo-size-300 !default;
$ibo-modal--is-informative--is-error--highlight--background-color: $ibo-color-red-600 !default;
$ibo-modal--is-informative--is-warning--highlight--background-color: $ibo-color-orange-600 !default;
$ibo-modal--is-informative--is-information--highlight--background-color: $ibo-color-blue-600 !default;
$ibo-modal--is-informative--is-success--highlight--background-color: $ibo-color-green-600 !default;
// Modal Option - Do not show again
.ibo-modal-option--do-not-show-again{
margin-top: $ibo-modal-option--do-not-show-again--margin-top;
.ibo-modal-option--do-not-show-again--checkbox{
height: auto;
display: inline-block;
width: auto;
}
}
.ibo-modal.ibo-is-informative{
display: flex;
align-items: center;
min-width: $ibo-modal--is-informative--min-width;
min-height: $ibo-modal--is-informative--min-height !important; // !important in order to overload jQueryUI CSS rule that's put directly on the element
&::before {
@include ibo-vertical-highlight;
}
&.ibo-is-error::before {
background-color: $ibo-modal--is-informative--is-error--highlight--background-color;
}
&.ibo-is-warning::before {
background-color: $ibo-modal--is-informative--is-warning--highlight--background-color;
}
&.ibo-is-information::before {
background-color: $ibo-modal--is-informative--is-information--highlight--background-color;
}
&.ibo-is-success::before {
background-color: $ibo-modal--is-informative--is-success--highlight--background-color;
}
}
*/

View File

@@ -86,27 +86,28 @@ $ibo-panel--collapsible-toggler--font-size: $ibo-font-size-250 !default;
$ibo-panel--collapsible-toggler--color: $ibo-color-grey-700 !default;
/* Rules */
.ibo-panel {
--ibo-main-color: #{map-get($ibo-panel-colors, 'neutral')}; /* --ibo-main-color is to allow overload from custom dynamic value from the DM. The overload will be done through an additional CSS class of a particular DM class or DM attribute */
position: relative;
&.ibo-has-icon {
// Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel)
> .ibo-panel--header {
.ibo-panel--titles {
padding-left: $ibo-panel--icon--spacing;
}
.ibo-panel--titles {
padding-left: $ibo-panel--icon--spacing;
}
&.ibo-has-medallion-icon {
.ibo-panel--header-left {
position: relative;
z-index: 1;
margin-left: $ibo-panel--icon--spacing--as-medallion;
}
// Note: Direct child selector is mandatory, otherwise a panel within a panel could be affected too when it shouldn't (eg. dashboard in an object, n:n panel)
> .ibo-panel--header {
.ibo-panel--header-left {
position: relative;
z-index: 1;
margin-left: $ibo-panel--icon--spacing--as-medallion;
.ibo-panel--icon {
position: absolute;
bottom: $ibo-panel--icon--bottom--as-medallion;

View File

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

View File

@@ -1,39 +0,0 @@
/*
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-datatableconfig--attributes-panel--first-column--margin-x: $ibo-spacing-0 !default;
$ibo-datatableconfig--attributes-panel--first-column--margin-y: $ibo-spacing-300 !default;
$ibo-datatableconfig--attributes-panel--first-column--max-height: 150px !default;
$ibo-datatableconfig--attributes-panel--per-page--input--margin-x: $ibo-spacing-200 !default;
$ibo-datatableconfig--attributes-panel--per-page--input--margin-y: $ibo-spacing-0 !default;
$ibo-datatableconfig--settings-panel--option--margin-right: $ibo-spacing-200 !default;
.ibo-datatableconfig--attributes-panel .ibo-panel--body{
@extend %ibo-font-ral-nor-100;
}
.ibo-datatableconfig--attributes-panel .ibo-multi-column .ibo-column:first-child{
margin: $ibo-datatableconfig--attributes-panel--first-column--margin-y $ibo-datatableconfig--attributes-panel--first-column--margin-x;
max-height: $ibo-datatableconfig--attributes-panel--first-column--max-height;
overflow-y: scroll;
}
.ibo-datatableconfig--attributes-panel--per-page--input{
margin: $ibo-datatableconfig--attributes-panel--per-page--input--margin-y $ibo-datatableconfig--attributes-panel--per-page--input--margin-x;
}
.ibo-datatableconfig--settings-panel .ibo-panel--body{
display: flex;
flex-direction: row;
}
.ibo-datatableconfig--settings-panel--options-container{
flex-grow: 1;
}
.ibo-datatableconfig--settings-panel--option{
display: flex;
flex-direction: row;
align-items: first baseline;
margin-right: $ibo-datatableconfig--settings-panel--option--margin-right;
}

View File

@@ -4,19 +4,18 @@
*/
$ibo-input-set--padding-x: $ibo-spacing-300 !default;
$ibo-input-set--padding-y: $ibo-spacing-0 !default;
$ibo-input-set--input--height: auto !default;
$ibo-input-set--input--min-height: $ibo-input--height !default;
$ibo-input-set--padding-y: 5px !default;
$ibo-input-set--input--height: 100% !default;
$ibo-input-set--remove--padding-top: 0.15em !default;
$ibo-input-set--remove--border-left: none !default;
$ibo-input-set--has-items--after--right: 8px !default;
$ibo-input-set--has-items--after--top: 5px !default;
$ibo-input-set--item--siblings-spacing: 3px !default;
$ibo-input-set--item--margin-y: 1px !default;
$ibo-input-set--item--margin-y: $ibo-spacing-100 !default;
$ibo-input-set--item--padding-x: 6px !default;
$ibo-input-set--item--padding-y: $ibo-spacing-200 !default;
$ibo-input-set--item--max-width: 120px !default;
@@ -27,10 +26,6 @@ $ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb
.ibo-input-set {
flex-wrap: wrap;
height: $ibo-input-set--input--height;
min-height: $ibo-input-set--input--min-height;
> input {
height: $ibo-input-set--input--height;
}
@@ -41,7 +36,17 @@ $ibo-input-set--item--box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15), 0 0 1px 1px rgb
border-left: $ibo-input-set--remove--border-left;
}
&.has-items::after {
content: "\f067";
font-family: "Font Awesome 5 Free";
font-weight: 600;
font-size: 10px;
position: absolute;
right: $ibo-input-set--has-items--after--right;
top: $ibo-input-set--has-items--after--top;
}
}
/* CSS rules from the old style, retrieved almost as is. Do not change the classes names in the markup without second thought as they are used by the portal as well */

View File

@@ -477,13 +477,10 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
transition-duration: 0.1s;
transition-timing-function: linear;
> .ibo-navigation-menu--menu-group-icon{
> .ibo-navigation-menu--menu-group-icon,
> .ibo-navigation-menu--menu-group-title{
display: flex; /* To avoid end space due to display:inline-block */
}
.ibo-navigation-menu--menu-group-title{
flex-grow: 1;
/* Note that display property is not set to flex on purpose so its text has an ellipsis on overflow */
}
&:not(:last-child){
margin-bottom: $ibo-navigation-menu--middle-part--elements-spacing;
@@ -664,8 +661,8 @@ $ibo-navigation-menu--user-info--height--is-expanded: 100% !default;
.ibo-navigation-menu--menu-nodes-title{
margin-top: $ibo-navigation-menu--menu-nodes-title--margin-top;
margin-bottom: $ibo-navigation-menu--menu-nodes-title--margin-bottom;
word-break: break-word; /* Ensure that long words wrap instead of being clipped */
@extend %ibo-font-ral-nor-350;
@extend %ibo-text-truncated-with-ellipsis;
}
/* - Menu node */
.ibo-navigation-menu--menu-node-title{

View File

@@ -35,8 +35,6 @@ $ibo-tab-container--extra-tab-toggler--text-color: $ibo-color-grey-700 !default;
$ibo-tab-container--extra-tab-toggler--text-color--on-hover: $ibo-color-blue-800 !default;
$ibo-tab-container--extra-tab-toggler--background-color--on-hover: $ibo-color-grey-200 !default;
$ibo-tab-container--extra-tab-toggler--tooltip-title--margin-bottom: $ibo-spacing-500 !default;
$ibo-tab-container--tab-container--label--text-color: $ibo-color-grey-600 !default;
$ibo-tab-container--tab-container--label--spacing: 20px !default;
$ibo-tab-container--tab-container--label--margin-bottom: 20px !default;
@@ -150,24 +148,19 @@ $ibo-tab-container--tab-container--last--min-height: 60vh !default;
}
}
.ibo-tab-container--extra-tab-toggler--tooltip-title {
@extend %ibo-font-weight-600;
margin-bottom: $ibo-tab-container--extra-tab-toggler--tooltip-title--margin-bottom;
}
.ibo-tab-container--tab-container {
padding: $ibo-tab-container--tab-container--padding-y $ibo-tab-container--tab-container--padding-x;
overflow-x: auto;
}
.ibo-tab-container--tab-container-list.ibo-is-scrollable {
.ibo-tab-container--tab-container:not(:first-child:nth-last-child(2)) .ibo-tab-container--tab-container--label {
.ibo-tab-container--tab-container--label {
display: block;
}
.ibo-tab-container--tab-container {
min-height: $ibo-tab-container--tab-container--min-height;
}
.ibo-tab-container--tab-container:last-child:not(:only-child) {
.ibo-tab-container--tab-container:last-child {
min-height: $ibo-tab-container--tab-container--last--min-height;
}
}

View File

@@ -5,14 +5,9 @@
@import "border-radius";
@import "color";
@import "class-icon";
@import "depression";
@import "elevation";
@import "font-icon";
@import "fullscreen";
@import "sticky-header";
@import "text-decoration";
@import "text-position";
@import "typography";
@import "misc";
@import "visibility";
@import "class-icon";

View File

@@ -1,36 +0,0 @@
/*!
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* Used on all ancestors when an element needs to be fullscreen (see .ibo-is-fullscreen) */
html.ibo-has-fullscreen-descendant {
position: fixed !important;
width: 0 !important;
height: 0 !important;
}
body.ibo-has-fullscreen-descendant {
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
}
.ibo-has-fullscreen-descendant {
position: static !important;
overflow: visible !important;
z-index: 1050 !important;
}
/* Used on a fullscreen element (see .ibo-has-fullscreen-descendant) */
.ibo-is-fullscreen {
position: absolute;
top: 0 !important;
left: 0 !important;
margin: 0 !important;
padding: 0 !important;
width: 100vw;
height: 100vh;
overflow: auto;
z-index: 1050;
}

View File

@@ -3,9 +3,191 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-is-code--background-color: $ibo-color-white-200 !default;
$ibo-is-code--padding: 1.25rem 1.5rem !default;
$ibo-sticky-sentinel--left: 0 !default;
$ibo-sticky-sentinel--right: 0 !default;
$ibo-sticky-sentinel--height: 0 !default;
$ibo-sticky-sentinel-top--top: 0 !default;
$ibo-sticky-sentinel-top--height: $ibo-sticky-sentinel--height !default;
$ibo-sticky-sentinel-bottom--bottom: 0 !default;
$ibo-sticky-sentinel-bottom--height: $ibo-sticky-sentinel--height !default;
/**************/
/* Visibility */
/**************/
.ibo-is-visible {
display: inherit !important; /* Note: !important is necessary as it needs to overload any standard rules */
visibility: visible !important;
}
.ibo-is-hidden {
display: none !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
.ibo-is-transparent {
opacity: 0 !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
.ibo-is-opaque {
opacity: 1 !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
/****************************/
/* Disposition / alignement */
/****************************/
.ibo-is-fullwidth {
width: 100%;
}
%ibo-fully-centered-content {
display: flex;
justify-content: center;
align-items: center;
}
%ibo-vertically-centered-content {
display: flex;
align-items: center;
}
/* Typically to align icons and text as it is a good practice to align them on the baseline and not the "middle" */
%ibo-baseline-centered-content {
display: flex;
align-items: baseline;
}
/* Note: This might not be named correctly. The intention is to make an element occupy the full height of its parent and to be centered in it */
%ibo-full-height-content {
display: flex;
align-items: stretch;
}
/**************/
/* Fullscreen */
/**************/
/* Used on all ancestors when an element needs to be fullscreen (see .ibo-is-fullscreen) */
html.ibo-has-fullscreen-descendant {
position: fixed !important;
width: 0 !important;
height: 0 !important;
}
body.ibo-has-fullscreen-descendant {
width: 0 !important;
height: 0 !important;
overflow: hidden !important;
}
.ibo-has-fullscreen-descendant {
position: static !important;
overflow: visible !important;
z-index: 1050 !important;
}
/* Used on a fullscreen element (see .ibo-has-fullscreen-descendant) */
.ibo-is-fullscreen {
position: absolute;
top: 0 !important;
left: 0 !important;
margin: 0 !important;
padding: 0 !important;
width: 100vw;
height: 100vh;
overflow: auto;
z-index: 1050;
}
/****************/
/* Text helpers */
/****************/
%ibo-text-truncated-with-ellipsis {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.ibo-text-truncated-with-ellipsis {
@extend %ibo-text-truncated-with-ellipsis;
}
/* Use this when you want the hyperlink to be of the color of its container's text instead of the global hyperlink color */
%ibo-hyperlink-inherited-colors {
color: inherit;
&:hover,
&:active {
color: inherit;
}
}
.ibo-is-broken-hyperlink {
text-decoration: line-through;
cursor: help;
}
.ibo-is-code {
background-color: $ibo-is-code--background-color;
padding: $ibo-is-code--padding;
@extend %ibo-font-code-150;
}
.ibo-add-margin-top-250{
margin-top: $ibo-spacing-400;
}
/*
* A single class to handle WYSIWYG generated content, where only HTML tags are available
* See https://bulma.io/documentation/elements/content/
*/
.ibo-is-html-content {
@extend .content;
/* Force user-generated tables to fit within the container as they often have an hard-coded width */
table {
width: unset !important;
max-width: max-content;
}
/* For table to render like in CKEditor, works with bulma lib. overload see:
* - ../../vendors/_bulma-variables-overload.scss)
* - ../../_shame.scss
*/
table {
border-collapse: separate;
border-spacing: 2px;
}
/* Preserve original text color in code blocks, except for the Highlight.js blocks which have their own colors */
& > code,
code:not(.hljs) {
color: inherit;
}
}
/***********************************************************************/
/* Sticky headers */
/* */
/* Used as a trigger to make an element stick to another during scroll */
/***********************************************************************/
.ibo-sticky-sentinel {
position: absolute;
left: $ibo-sticky-sentinel--left;
right: $ibo-sticky-sentinel--right;
visibility: hidden;
}
.ibo-sticky-sentinel-top {
top: $ibo-sticky-sentinel-top--top;
height: $ibo-sticky-sentinel-top--height; /* To be overloaded by use cases */
}
.ibo-sticky-sentinel-bottom {
bottom: $ibo-sticky-sentinel-bottom--bottom;
height: $ibo-sticky-sentinel-bottom--height; /* To be overloaded by use cases */
}
%ibo-medallion {
position: relative;

View File

@@ -1,35 +0,0 @@
/*!
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/***********************************************************************/
/* Sticky headers */
/* */
/* Used as a trigger to make an element stick to another during scroll */
/***********************************************************************/
/* SCSS variables */
$ibo-sticky-sentinel--left: 0 !default;
$ibo-sticky-sentinel--right: 0 !default;
$ibo-sticky-sentinel--height: 0 !default;
$ibo-sticky-sentinel-top--top: 0 !default;
$ibo-sticky-sentinel-top--height: $ibo-sticky-sentinel--height !default;
$ibo-sticky-sentinel-bottom--bottom: 0 !default;
$ibo-sticky-sentinel-bottom--height: $ibo-sticky-sentinel--height !default;
/* Rules */
.ibo-sticky-sentinel {
position: absolute;
left: $ibo-sticky-sentinel--left;
right: $ibo-sticky-sentinel--right;
visibility: hidden;
}
.ibo-sticky-sentinel-top {
top: $ibo-sticky-sentinel-top--top;
height: $ibo-sticky-sentinel-top--height; /* To be overloaded by use cases */
}
.ibo-sticky-sentinel-bottom {
bottom: $ibo-sticky-sentinel-bottom--bottom;
height: $ibo-sticky-sentinel-bottom--height; /* To be overloaded by use cases */
}

View File

@@ -1,90 +0,0 @@
/*!
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-has-description--content: "?" !default;
$ibo-has-description--padding-left: $ibo-spacing-200 !default;
$ibo-has-description--color: $ibo-color-grey-600 !default;
$ibo-has-description--font-size: 0.7em !default; /* Font size is em on purpose as we want it to be proportional to its context */
$ibo-is-code--background-color: $ibo-color-white-200 !default;
$ibo-is-code--padding: 1.25rem 1.5rem !default;
/* Rules */
%ibo-text-truncated-with-ellipsis {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.ibo-text-truncated-with-ellipsis {
@extend %ibo-text-truncated-with-ellipsis;
}
/* Use this when you want the hyperlink to be of the color of its container's text instead of the global hyperlink color */
%ibo-hyperlink-inherited-colors {
color: inherit;
&:hover,
&:active {
color: inherit;
}
}
.ibo-is-broken-hyperlink {
text-decoration: line-through;
cursor: help;
}
/* Class to display a hint on elements that have a tooltip for further description */
.ibo-has-description {
&::after {
content: $ibo-has-description--content;
padding-left: $ibo-has-description--padding-left;
vertical-align: top;
cursor: pointer;
color: $ibo-has-description--color;
/* We don't use a %ibo-font-ral-xxx-yyy as we need a specific size */
@extend %ibo-font-weight-700;
font-size: $ibo-has-description--font-size;
}
}
.ibo-is-code {
background-color: $ibo-is-code--background-color;
padding: $ibo-is-code--padding;
@extend %ibo-font-code-150;
}
/*
* A single class to handle WYSIWYG generated content, where only HTML tags are available
* See https://bulma.io/documentation/elements/content/
*/
.ibo-is-html-content {
@extend .content;
/* Force user-generated tables to fit within the container as they often have an hard-coded width */
table {
width: unset !important;
max-width: max-content;
}
/* For table to render like in CKEditor, works with bulma lib. overload see:
* - ../../vendors/_bulma-variables-overload.scss)
* - ../../_shame.scss
*/
table {
border-collapse: separate;
border-spacing: 2px;
}
/* Preserve original text color in code blocks, except for the Highlight.js blocks which have their own colors */
& > code,
code:not(.hljs) {
color: inherit;
}
}

View File

@@ -1,34 +0,0 @@
/*!
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/****************************/
/* Disposition / alignement */
/****************************/
.ibo-is-fullwidth {
width: 100%;
}
%ibo-fully-centered-content {
display: flex;
justify-content: center;
align-items: center;
}
%ibo-vertically-centered-content {
display: flex;
align-items: center;
}
/* Typically to align icons and text as it is a good practice to align them on the baseline and not the "middle" */
%ibo-baseline-centered-content {
display: flex;
align-items: baseline;
}
/* Note: This might not be named correctly. The intention is to make an element occupy the full height of its parent and to be centered in it */
%ibo-full-height-content {
display: flex;
align-items: stretch;
}

View File

@@ -1,21 +0,0 @@
/*!
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
.ibo-is-visible {
display: inherit !important; /* Note: !important is necessary as it needs to overload any standard rules */
visibility: visible !important;
}
.ibo-is-hidden {
display: none !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
.ibo-is-transparent {
opacity: 0 !important; /* Note: !important is necessary as it needs to overload any standard rules */
}
.ibo-is-opaque {
opacity: 1 !important; /* Note: !important is necessary as it needs to overload any standard rules */
}

View File

@@ -1,6 +1,5 @@
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@import "highlight";

View File

@@ -1,17 +0,0 @@
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
$ibo-vertical-highlight--width: $ibo-size-100;
$ibo-vertical-highlight--height: 100%;
@mixin ibo-vertical-highlight {
display: block;
position: absolute;
top: 0;
left: 0;
content: "";
width: $ibo-vertical-highlight--width;
height: $ibo-vertical-highlight--height;
}

View File

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

View File

@@ -7,16 +7,6 @@
*
* Other modification done : replaced the `Alpha(` by `alpha(` to avoid warnings generated by SCSSPHP
*/
/********************************************************************************/
/* */
/* @deprecated 3.0.0 N°3558 now we are using /css/backoffice/vendors/_jquery* */
/* */
/********************************************************************************/
.ui-draggable-handle {
-ms-touch-action: none;
touch-action: none;

View File

@@ -1,10 +0,0 @@
{
"config" : {
"classmap-authoritative" : true
},
"autoload" : {
"psr-4" : {
"Combodo\\iTop\\Cas\\" : "src"
}
}
}

View File

@@ -1,18 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d751713988987e9331980363e24189ce",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.1.0"
}

View File

@@ -6,7 +6,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'CAS:Error:UserNotAllowed' => 'Nem engedélyezett felhasználó',
'CAS:Login:SignIn' => 'Bejelentkezés CAS szerverrel',
'CAS:Login:SignInTooltip' => 'Kattintson ide az azonosításhoz a CAS szerveren',
'CAS:Error:UserNotAllowed' => 'User not allowed~~',
'CAS:Login:SignIn' => 'Sign in with CAS~~',
'CAS:Login:SignInTooltip' => 'Click here to authenticate yourself with the CAS server~~',
));

View File

@@ -0,0 +1,3 @@
<?php
require_once __DIR__.'/src/Config.php';
require_once __DIR__.'/src/CASLoginExtension.php';

View File

@@ -24,8 +24,7 @@ SetupWebPage::AddModule(
//
'datamodel' => array(
'model.authent-cas.php',
'vendor/autoload.php',
'src/CASLoginExtension.php',
'main.php'
),
'webservice' => array(
@@ -51,7 +50,6 @@ SetupWebPage::AddModule(
'cas_port' => '',
'cas_context' => '',
'cas_version' => '',
'service_base_url' => '',
),
)
);

View File

@@ -1,17 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Cas;
use LogAPI;
class CASLog extends LogAPI
{
const CHANNEL_DEFAULT = 'CASLog';
protected static $m_oFileLog = null;
}

View File

@@ -1,81 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Cas;
use IssueLog;
use LogAPI;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class CASLogger implements LoggerInterface
{
public function __construct($sDebugFile)
{
CASLog::Enable($sDebugFile);
}
const LEVEL_COMPAT = [
LogLevel::EMERGENCY => LogAPI::LEVEL_ERROR,
LogLevel::ALERT => LogAPI::LEVEL_ERROR,
LogLevel::CRITICAL => LogAPI::LEVEL_ERROR,
LogLevel::ERROR => LogAPI::LEVEL_ERROR,
LogLevel::WARNING => LogAPI::LEVEL_WARNING,
LogLevel::NOTICE => LogAPI::LEVEL_INFO,
LogLevel::INFO => LogAPI::LEVEL_INFO,
LogLevel::DEBUG => LogAPI::LEVEL_DEBUG,
];
public function emergency($message, array $context = array())
{
CASLog::Error('EMERGENCY: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('EMERGENCY: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function alert($message, array $context = array())
{
CASLog::Error('ALERT: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('ALERT: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function critical($message, array $context = array())
{
CASLog::Error('CRITICAL: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('CRITICAL: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function error($message, array $context = array())
{
CASLog::Error('ERROR: '.$message, CASLog::CHANNEL_DEFAULT, $context);
IssueLog::Error('ERROR: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function warning($message, array $context = array())
{
CASLog::Warning('WARNING: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function notice($message, array $context = array())
{
CASLog::Info('NOTICE: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function info($message, array $context = array())
{
CASLog::Info('INFO: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function debug($message, array $context = array())
{
CASLog::Debug('DEBUG: '.$message, CASLog::CHANNEL_DEFAULT, $context);
}
public function log($level, $message, array $context = array())
{
$sLevel = self::LEVEL_COMPAT[$level] ?? LogAPI::LEVEL_ERROR;
CASLog::Log($sLevel, strtoupper($level).": $message", CASLog::CHANNEL_DEFAULT, $context);
}
}

View File

@@ -153,7 +153,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
$bCASDebug = Config::Get('cas_debug');
if ($bCASDebug)
{
phpCAS::setLogger(new CASLogger(APPROOT.'log/cas.log'));
phpCAS::setDebug(APPROOT.'log/cas.log');
}
// Initialize phpCAS
@@ -161,8 +161,7 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
$sCASHost = Config::Get('cas_host');
$iCASPort = Config::Get('cas_port');
$sCASContext = Config::Get('cas_context');
$sServiceBaseURL = Config::Get('service_base_url', self::GetServiceBaseURL());
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, $sServiceBaseURL, false /* session already started */);
phpCAS::client($sCASVersion, $sCASHost, $iCASPort, $sCASContext, false /* session already started */);
$sCASCACertPath = Config::Get('cas_server_ca_cert_path');
if (empty($sCASCACertPath))
{
@@ -178,38 +177,6 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte
}
}
private static function GetServiceBaseURL()
{
$protocol = $_SERVER['REQUEST_SCHEME'];
$protocol .= '://';
if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
// explode the host list separated by comma and use the first host
$hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
// see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default
return $protocol . $hosts[0];
} else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) {
$server_url = $_SERVER['HTTP_X_FORWARDED_SERVER'];
} else {
if (empty($_SERVER['SERVER_NAME'])) {
$server_url = $_SERVER['HTTP_HOST'];
} else {
$server_url = $_SERVER['SERVER_NAME'];
}
}
if (!strpos($server_url, ':')) {
if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
$server_port = $_SERVER['SERVER_PORT'];
} else {
$ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
$server_port = $ports[0];
}
$server_url .= ':';
$server_url .= $server_port;
}
return $protocol . $server_url;
}
private function DoUserProvisioning($sLogin)
{
$bCASUserSynchro = Config::Get('cas_user_synchro');

View File

@@ -1,7 +0,0 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit1878ad96115c3aa0fa5e9fd9807f5db0::getLoader();

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