Compare commits

..

7 Commits

1303 changed files with 34593 additions and 56856 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

View File

@@ -62,12 +62,6 @@ gitGraph
commit id: "2022-12-28" tag: "2.7.8" commit id: "2022-12-28" tag: "2.7.8"
checkout support/3.0 checkout support/3.0
commit id: "2023-04-12" tag: "3.0.3" commit id: "2023-04-12" tag: "3.0.3"
checkout develop
commit id: "2023-06-19" tag: "3.1.0-beta" type: REVERSE
commit id: "2023-07-26" tag: "3.1.0-1" type: HIGHLIGHT
branch support/3.1 order: 840
checkout support/3.1
commit id: "2023-08-09" tag: "3.1.0-2"
``` ```
To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start). To learn more, check the [iTop community versions history on the official wiki](https://www.itophub.io/wiki/page?id=latest:release:start).

View File

@@ -1,83 +0,0 @@
<!--
IMPORTANT: Please follow the guidelines within this PR template before submitting it, it will greatly help us process your PR. 🙏
Any PRs not following the guidelines or with missing information will not be considered.
-->
## Base information
| Question | Answer
|---------------------------------------------------------------|--------
| Related to a SourceForge thead / Another PR / Combodo ticket? | <!-- Put the URL -->
| Type of change? | Bug fix / Enhancement / Translations
## Symptom (bug) / Objective (enhancement)
<!--
If it's a bug
- Explain the symptom in details
- If possible put error messages, logs or screenshots (you can paste image directly in this editor).
If it's an enhancement
- Describe what is blocking you, what is the objective with as much details as possible.
- Add screenshots if it's related to UI.
-->
## Reproduction procedure (bug)
<!--
Remove this section only if it's NOT a bug.
Otherwise, explain step by step how to reproduce the issue on a standard iTop Community.
If it requires a custom datamodel, provide the minimal XML delta to reproduce it on a standard iTop Community.
-->
1. On iTop x.y.z <!-- Put complete iTop version (eg. 3.1.0-2) -->
2. With PHP x.y.z <!-- Put complete PHP version (eg. 8.1.24) -->
2. First go there
2. Then do that
3. ...
4. Finally, see that...
## Cause (bug)
<!--
Remove this section only if it's NOT a bug.
Otherwise, explain what is the cause of the issue (where in the code and why)
-->
## Proposed solution (bug and enhancement)
<!--
Explain in details how you are proposing to solve this:
- What did you do in the code and why
- If you changed something in the UI, put before / after screenshots (you can paste image directly in this editor)
-->
## Checklist before requesting a review
<!--
Don't remove these lines, check them once done.
-->
- [ ] I have performed a self-review of my code
- [ ] I have tested all changes I made on an iTop instance
- [ ] I have added a unit test, otherwise I have explained why I couldn't
- [ ] Is the PR clear and detailed enough so anyone can understand digging in the code?
## Checklist of things to do before PR is ready to merge
<!--
Things that needs to be done in the PR before it can be considered as ready to be merged
Examples:
- Changes requested in the review
- Unit test to add
- Dictionary entries to translate
- ...
-->
- [ ] ...
- [ ] ...
- [ ] ...

View File

@@ -1,16 +0,0 @@
name: Add PRs to Combodo PRs Dashboard
on:
pull_request_target:
types:
- opened
jobs:
add-to-project:
name: Add PR to Combodo Project
runs-on: ubuntu-latest
steps:
- uses: actions/add-to-project@v1.0.2
with:
project-url: https://github.com/orgs/Combodo/projects/5
github-token: ${{ secrets.PR_AUTOMATICALLY_ADD_TO_PROJECT }}

6
.gitignore vendored
View File

@@ -37,9 +37,7 @@ tests/*/vendor/*
# iTop extensions # iTop extensions
/extensions/** /extensions/**
!/extensions/.htaccess
!/extensions/readme.txt !/extensions/readme.txt
!/extensions/web.config
# all logs but listing prevention # all logs but listing prevention
/log/** /log/**
@@ -47,10 +45,8 @@ tests/*/vendor/*
!/log/index.php !/log/index.php
!/log/web.config !/log/web.config
# PHPUnit: Cache file, local XML working copies # PHPUnit cache file
/tests/php-unit-tests/.phpunit.result.cache /tests/php-unit-tests/.phpunit.result.cache
/tests/php-unit-tests/phpunit.xml
/tests/php-unit-tests/postbuild_integration.xml
# Jetbrains # Jetbrains

View File

@@ -27,7 +27,7 @@ $iTopFolder = __DIR__."/../../../";
require_once("$iTopFolder/approot.inc.php"); require_once("$iTopFolder/approot.inc.php");
require_once(APPROOT."/application/utils.inc.php"); require_once(APPROOT."/application/utils.inc.php");
if (PHP_SAPI !== 'cli') if (php_sapi_name() !== 'cli')
{ {
throw new \Exception('This script can only run from CLI'); throw new \Exception('This script can only run from CLI');
} }
@@ -48,4 +48,4 @@ if (!file_exists($sCssFile))
{ {
fwrite(STDERR, "Failed to compile $sCssFile, exiting."); fwrite(STDERR, "Failed to compile $sCssFile, exiting.");
exit(1); exit(1);
} }

View File

@@ -26,7 +26,7 @@ $iTopFolder = __DIR__ . "/../../" ;
require_once ("$iTopFolder/approot.inc.php"); require_once ("$iTopFolder/approot.inc.php");
require_once (APPROOT."/setup/setuputils.class.inc.php"); require_once (APPROOT."/setup/setuputils.class.inc.php");
if (PHP_SAPI !== 'cli') if (php_sapi_name() !== 'cli')
{ {
throw new \Exception('This script can only run from CLI'); throw new \Exception('This script can only run from CLI');
} }
@@ -70,4 +70,4 @@ if (false === empty($aMissing)) {
echo "Some new tests dirs exists !\n" echo "Some new tests dirs exists !\n"
.' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n" .' They must be declared either in the allowed or denied list in '.iTopComposer::class." (see N°2651).\n"
.' List of dirs:'."\n".var_export($aMissing, true); .' List of dirs:'."\n".var_export($aMissing, true);
} }

View File

@@ -19,24 +19,17 @@
* The target license file path is in `$xmlFilePath` * The target license file path is in `$xmlFilePath`
*/ */
$iTopFolder = __DIR__."/../../"; $iTopFolder = __DIR__ . "/../../" ;
$xmlFilePath = $iTopFolder."setup/licenses/community-licenses.xml"; $xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
$jqExec = shell_exec("jq -V"); // a param is mandatory otherwise the script will freeze function get_scope($product_node)
if ((null === $jqExec) || (false === $jqExec)) { {
echo "/!\ JQ is required but cannot be launched :( \n";
echo "Check this script PHPDoc block for instructions\n";
die(-1);
}
function get_scope($product_node) {
$scope = $product_node->getAttribute("scope"); $scope = $product_node->getAttribute("scope");
if ($scope === "") { //put iTop first if ($scope === "")
{ //put iTop first
return "aaaaaaaaa"; return "aaaaaaaaa";
} }
return $scope; return $scope;
} }

View File

@@ -161,4 +161,4 @@ We have one sticker per contribution type. You might get multiple stickers with
Here is the design of each stickers for year 2022: Here is the design of each stickers for year 2022:
![iTop stickers 2023](.doc/contributing-guide/2023.contributing-stickers-side-by-side.png) ![iTop stickers 2022](.doc/contributing-guide/2022.contributing-stickers-side-by-side.png)

View File

@@ -107,8 +107,7 @@ We would like to give a special thank you 🤗 to the people from the community
- Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern)) - Rudner, Björn (a.k.a [@rudnerbjoern](https://github.com/rudnerbjoern))
- Seki, Shoji - Seki, Shoji
- Shilov, Vladimir - Shilov, Vladimir
- Stukalov, Ilya (a.k.a [@ilya](https://www.github.com/ilya-stukalov)) - Stukalov, Ilya (a.k.a [@ilya](https://www.github.com/ilya)-stukalov)
- Tarjányi, Csaba (a.k.a [@tacsaby](https://github.com/tacsaby))
- Tulio, Marco - Tulio, Marco
- Turrubiates, Miguel - Turrubiates, Miguel

View File

@@ -228,7 +228,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile", "db_table" => "priv_urp_userprofile",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
'uniqueness_rules' => array( 'uniqueness_rules' => array(
'no_duplicate' => array( 'no_duplicate' => array(
'attributes' => array( 'attributes' => array(
@@ -446,12 +445,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
UR_ACTION_BULK_DELETE => 'bd', UR_ACTION_BULK_DELETE => 'bd',
); );
/**
* @var array $aUsersProfilesList Cache of users' profiles. Hash array of user ID => [profile ID => profile friendlyname, profile ID => profile friendlyname, ...]
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6887
*/
private $aUsersProfilesList = [];
// Installation: create the very first user // Installation: create the very first user
public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US') public function CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage = 'EN US')
{ {
@@ -508,7 +501,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
} }
protected $m_aUserOrgs = array(); // userid -> array of orgid protected $m_aUserOrgs = array(); // userid -> array of orgid
protected $m_aAdministrators = null; // [user id]
// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read) // Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
protected $m_aObjectActionGrants = array(); protected $m_aObjectActionGrants = array();
@@ -565,7 +557,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Cache // Cache
$this->m_aObjectActionGrants = array(); $this->m_aObjectActionGrants = array();
$this->m_aAdministrators = null;
} }
public function LoadCache() public function LoadCache()
@@ -708,10 +699,12 @@ class UserRightsProfile extends UserRightsAddOnAPI
*/ */
private function GetAdministrators() private function GetAdministrators()
{ {
if ($this->m_aAdministrators === null) static $aAdministrators = null;
if ($aAdministrators === null)
{ {
// Find all administrators // Find all administrators
$this->m_aAdministrators = array(); $aAdministrators = array();
$oAdministratorsFilter = new DBObjectSearch('User'); $oAdministratorsFilter = new DBObjectSearch('User');
$oLnkFilter = new DBObjectSearch('URP_UserProfile'); $oLnkFilter = new DBObjectSearch('URP_UserProfile');
$oExpression = new FieldExpression('profileid', 'URP_UserProfile'); $oExpression = new FieldExpression('profileid', 'URP_UserProfile');
@@ -724,10 +717,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oSet->OptimizeColumnLoad(array('User' => array('login'))); $oSet->OptimizeColumnLoad(array('User' => array('login')));
while($oUser = $oSet->Fetch()) while($oUser = $oSet->Fetch())
{ {
$this->m_aAdministrators[] = $oUser->GetKey(); $aAdministrators[] = $oUser->GetKey();
} }
} }
return $this->m_aAdministrators; return $aAdministrators;
} }
/** /**
@@ -764,12 +757,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
$sAction = self::$m_aActionCodes[$iActionCode]; $sAction = self::$m_aActionCodes[$iActionCode];
$bStatus = null; $bStatus = null;
// Cache user's profiles
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
}
// Call the API of UserRights because it caches the list for us // Call the API of UserRights because it caches the list for us
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
{ {
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction); $bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
if (!is_null($bGrant)) if (!is_null($bGrant))
@@ -895,16 +884,11 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Note: this code is VERY close to the code of IsActionAllowed() // Note: this code is VERY close to the code of IsActionAllowed()
$iUser = $oUser->GetKey(); $iUser = $oUser->GetKey();
// Cache user's profiles
if(false === array_key_exists($iUser, $this->aUsersProfilesList)){
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
}
// Note: The object set is ignored because it was interesting to optimize for huge data sets // Note: The object set is ignored because it was interesting to optimize for huge data sets
// and acceptable to consider only the root class of the object set // and acceptable to consider only the root class of the object set
$bStatus = null; $bStatus = null;
// Call the API of UserRights because it caches the list for us // Call the API of UserRights because it caches the list for us
foreach($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) foreach(UserRights::ListProfiles($oUser) as $iProfile => $oProfile)
{ {
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode); $bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
if (!is_null($bGrant)) if (!is_null($bGrant))
@@ -933,9 +917,8 @@ class UserRightsProfile extends UserRightsAddOnAPI
} }
/** /**
* @param string $sClass * Find out which attribute is corresponding the the dimension 'owner org'
* @return string|null Find out which attribute is corresponding the dimension 'owner org' * returns null if no such attribute has been found (no filtering should occur)
* returns null if no such attribute has been found (no filtering should occur)
*/ */
public static function GetOwnerOrganizationAttCode($sClass) public static function GetOwnerOrganizationAttCode($sClass)
{ {

View File

@@ -334,7 +334,6 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile", "db_table" => "priv_urp_userprofile",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
@@ -580,10 +579,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
/** /**
* Read and cache organizations allowed to the given user * Read and cache organizations allowed to the given user
* *
* @param User $oUser * @param $oUser
* @param string $sClass (not used here but can be used in overloads) * @param $sClass (not used here but can be used in overloads)
* *
* @return array keys of the User allowed org * @return array
* @throws \CoreException * @throws \CoreException
* @throws \Exception * @throws \Exception
*/ */

View File

@@ -277,7 +277,6 @@ class URP_UserProfile extends UserRightsBaseClass
"db_table" => "priv_urp_userprofile", "db_table" => "priv_urp_userprofile",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 */
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/AjaxPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/AjaxPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -238,7 +238,7 @@ class ApplicationContext
{ {
$aContextInputBlocks = []; $aContextInputBlocks = [];
foreach ($this->aValues as $sName => $sValue) { foreach ($this->aValues as $sName => $sValue) {
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", $sValue); $aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", utils::EscapeHtml($sValue));
} }
return $aContextInputBlocks; return $aContextInputBlocks;
} }

View File

@@ -335,6 +335,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
* A recommended pattern is to cache data by the mean of static members. * A recommended pattern is to cache data by the mean of static members.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 use the new event service instead, see {@see DBObject::FireEvent()} method
* @package UIExtensibilityAPI * @package UIExtensibilityAPI
*/ */
interface iApplicationUIExtension interface iApplicationUIExtension
@@ -486,6 +487,7 @@ interface iApplicationUIExtension
* @api * @api
* @package UIExtensibilityAPI * @package UIExtensibilityAPI
* @since 2.7.0 * @since 2.7.0
* @deprecated
*/ */
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
{ {
@@ -558,7 +560,6 @@ abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
* or through the GUI. * or through the GUI.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 use the new event service instead, see {@see DBObject::FireEvent()} method. More details on each method PHPDoc.
* @package ORMExtensibilityAPI * @package ORMExtensibilityAPI
*/ */
interface iApplicationObjectExtension interface iApplicationObjectExtension
@@ -573,7 +574,6 @@ interface iApplicationObjectExtension
* Otherwise, the answer is definitively "yes, the object has changed". * Otherwise, the answer is definitively "yes, the object has changed".
* *
* @api * @api
* @deprecated 3.1.0 N°4756 No alternative available, this API was unstable and is abandoned
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* *
* @return boolean True if something has changed for the target object * @return boolean True if something has changed for the target object
@@ -587,7 +587,6 @@ interface iApplicationObjectExtension
* Anyhow, this API can be called in other contexts such as the CSV import tool. * Anyhow, this API can be called in other contexts such as the CSV import tool.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_CHECK_TO_WRITE event instead
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* *
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
@@ -602,7 +601,6 @@ interface iApplicationObjectExtension
* Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled. * Please not that it is not possible to cascade deletion by this mean: only stopper issues can be handled.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_CHECK_TO_DELETE event instead
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* *
* @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user. * @return string[] A list of errors message. An error message is made of one line and it can be displayed to the end-user.
@@ -619,7 +617,6 @@ interface iApplicationObjectExtension
* * {@see DBObject::Get()} : for a given attribute the new value that was persisted * * {@see DBObject::Get()} : for a given attribute the new value that was persisted
* *
* @api * @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_WRITE event instead
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page * once for all the changes made within the current page
@@ -636,7 +633,6 @@ interface iApplicationObjectExtension
* The method is called right <b>after</b> the object has been written to the database. * The method is called right <b>after</b> the object has been written to the database.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_WRITE event instead
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page * once for all the changes made within the current page
@@ -651,7 +647,6 @@ interface iApplicationObjectExtension
* The method is called right <b>before</b> the object will be deleted from the database. * The method is called right <b>before</b> the object will be deleted from the database.
* *
* @api * @api
* @deprecated 3.1.0 N°4756 Use EVENT_DB_AFTER_DELETE event instead
* @param \cmdbAbstractObject $oObject The target object * @param \cmdbAbstractObject $oObject The target object
* @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information * @param CMDBChange|null $oChange A change context. Since 2.0 it is fine to ignore it, as the framework does maintain this information
* once for all the changes made within the current page * once for all the changes made within the current page
@@ -665,7 +660,6 @@ interface iApplicationObjectExtension
* Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods * Extend this class instead of iApplicationObjectExtension if you don't need to overload all methods
* *
* @api * @api
* @deprecated 3.1.0 N°4756 use the new event service instead, see {@see DBObject::FireEvent()} method
* @package ORMExtensibilityAPI * @package ORMExtensibilityAPI
* @since 2.7.0 * @since 2.7.0
*/ */
@@ -2015,8 +2009,6 @@ class RestUtils
* *
* @return DBObject The object found * @return DBObject The object found
* @throws Exception If the input structure is not valid or it could not find exactly one object * @throws Exception If the input structure is not valid or it could not find exactly one object
*
* @see DBObject::CheckChangedExtKeysValues() generic method to check that we can access the linked object isn't used in that use case because values can be literal, OQL, friendlyname
*/ */
public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false) public static function FindObjectFromKey($sClass, $key, $bAllowNullValue = false)
{ {
@@ -2103,16 +2095,8 @@ class RestUtils
elseif (is_string($key)) elseif (is_string($key))
{ {
// OQL // OQL
try { $oSearch = DBObjectSearch::FromOQL($key);
$oSearch = DBObjectSearch::FromOQL($key); }
} catch (Exception $e) {
throw new CoreOqlException('Query failed to execute', [
'query' => $key,
'exception_class' => get_class($e),
'exception_message' => $e->getMessage(),
]);
}
}
else else
{ {
throw new Exception("Wrong format for key"); throw new Exception("Wrong format for key");
@@ -2262,27 +2246,3 @@ interface iModuleExtension
*/ */
public function __construct(); public function __construct();
} }
/**
* KPI logging extensibility point
*
* KPI Logger extension
*/
interface iKPILoggerExtension
{
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
*
* @return mixed
*/
public function LogOperation($oKpiLogData);
}

View File

@@ -34,15 +34,15 @@ class AuditCategory extends cmdbAbstractObject
{ {
$aParams = array $aParams = array
( (
"category" => "application,grant_by_profile", "category" => "application, grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array('name'), "reconc_keys" => array('name'),
"db_table" => "priv_auditcategory", "db_table" => "priv_auditcategory",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'), 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
); );
MetaModel::Init_Params($aParams); 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("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
@@ -74,23 +74,15 @@ class AuditCategory extends cmdbAbstractObject
public function GetReportColor($iTotal, $iErrors) public function GetReportColor($iTotal, $iErrors)
{ {
$sResult = 'red'; $sResult = 'red';
if ( ($iTotal == 0) || ($iErrors / $iTotal) <= ($this->Get('ok_error_tolerance') / 100) ) { if ( ($iTotal == 0) || ($iErrors / $iTotal) <= ($this->Get('ok_error_tolerance') / 100) )
{
$sResult = 'green'; $sResult = 'green';
} else if (($iErrors / $iTotal) <= ($this->Get('warning_error_tolerance') / 100)) { }
else if ( ($iErrors / $iTotal) <= ($this->Get('warning_error_tolerance') / 100) )
{
$sResult = 'orange'; $sResult = 'orange';
} }
return $sResult; return $sResult;
} }
public static function GetShortcutActions($sFinalClass)
{
$aShortcutActions = parent::GetShortcutActions($sFinalClass);
if (!in_array('UI:Menu:RunAudit', $aShortcutActions)) {
$aShortcutActions[] = 'UI:Menu:RunAudit';
}
return $aShortcutActions;
}
} }
?> ?>

View File

@@ -35,42 +35,30 @@ class AuditDomain extends cmdbAbstractObject
{ {
$aParams = array $aParams = array
( (
"category" => "application,grant_by_profile", "category" => "application, grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"complementary_name_attcode" => array('description'), "state_attcode" => "",
"state_attcode" => "", "reconc_keys" => array('name'),
"reconc_keys" => array('name'), "db_table" => "priv_auditdomain",
"db_table" => "priv_auditdomain", "db_key_field" => "id",
"db_key_field" => "id", "db_finalclass_field" => "",
"db_finalclass_field" => "", 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-album.svg'),
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-album.svg'),
); );
MetaModel::Init_Params($aParams); 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("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 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 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", 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())));
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 // Display lists
MetaModel::Init_SetZListItems('details', array('name', 'description', 'icon', 'categories_list')); // Attributes to be displayed for the complete details 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 MetaModel::Init_SetZListItems('list', array('description', )); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description')); // Criteria of the std search form 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 MetaModel::Init_SetZListItems('default_search', array('name', 'description')); // Criteria of the default search form
} }
public static function GetShortcutActions($sFinalClass)
{
$aShortcutActions = parent::GetShortcutActions($sFinalClass);
if (!in_array('UI:Menu:RunAudit', $aShortcutActions)) {
$aShortcutActions[] = 'UI:Menu:RunAudit';
}
return $aShortcutActions;
}
} }
/** /**
@@ -86,26 +74,15 @@ class lnkAuditCategoryToAuditDomain extends cmdbAbstractObject
{ {
$aParams = array $aParams = array
( (
"category" => "application,grant_by_profile", "category" => "application, grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "", "name_attcode" => "",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array('category_id', 'domain_id'), "reconc_keys" => array('category_id', 'domain_id'),
"db_table" => "priv_link_audit_category_domain", "db_table" => "priv_link_audit_category_domain",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
"is_link" => true, "is_link" => true,
'uniqueness_rules' => array(
'no_duplicate' => array(
'attributes' => array(
0 => 'category_id',
1 => 'domain_id',
),
'filter' => '',
'disabled' => false,
'is_blocking' => true,
),
),
); );
MetaModel::Init_Params($aParams); 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 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())));

View File

@@ -35,23 +35,23 @@ class AuditRule extends cmdbAbstractObject
{ {
$aParams = array $aParams = array
( (
"category" => "application,grant_by_profile", "category" => "application, grant_by_profile",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "name", "name_attcode" => "name",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array('name'), "reconc_keys" => array('name'),
"db_table" => "priv_auditrule", "db_table" => "priv_auditrule",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit.svg'), 'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit.svg'),
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values" => null, "sql" => "name", "default_value" => "", "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeString("name", array("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 AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOQL("query", array("allowed_values" => null, "sql" => "query", "default_value" => "", "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeOQL("query", array("allowed_values"=>null, "sql"=>"query", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("allowed_values" => new ValueSetEnum('true,false'), "sql" => "valid_flag", "default_value" => "true", "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("allowed_values"=>new ValueSetEnum('true,false'), "sql"=>"valid_flag", "default_value"=>"true", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("allowed_values" => null, "sql" => "category_id", "targetclass" => "AuditCategory", "is_null_allowed" => false, "on_target_delete" => DEL_MANUAL, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("allowed_values"=>null, "sql"=>"category_id", "targetclass"=>"AuditCategory", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "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 AttributeExternalField("category_name", array("allowed_values"=>null, "extkey_attcode"=> 'category_id', "target_attcode"=>"name")));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', array('category_id', 'name', 'description', 'query', 'valid_flag')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('category_id', 'name', 'description', 'query', 'valid_flag')); // Attributes to be displayed for the complete details
@@ -60,16 +60,5 @@ class AuditRule extends cmdbAbstractObject
MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the std search form
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'category_id')); // Criteria of the advanced search form MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'category_id')); // Criteria of the advanced search form
} }
public static function GetShortcutActions($sFinalClass)
{
$aShortcutActions = parent::GetShortcutActions($sFinalClass);
if (!in_array('UI:Menu:RunAudit', $aShortcutActions)) {
$aShortcutActions[] = 'UI:Menu:RunAudit';
}
return $aShortcutActions;
}
} }
?> ?>

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CaptureWebPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CaptureWebPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CLIPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CLIPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -47,7 +47,6 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Links\LinkSetDataTransformer; use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel; use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties'); define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
@@ -134,8 +133,6 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
/** @var string */ /** @var string */
public const ENUM_INPUT_TYPE_TAGSET = 'tagset'; public const ENUM_INPUT_TYPE_TAGSET = 'tagset';
/** @var string */ /** @var string */
public const ENUM_INPUT_TYPE_TAGSET_LINKEDSET = 'tagset_linkedset';
/** @var string */
public const ENUM_INPUT_TYPE_RADIO = 'radio'; public const ENUM_INPUT_TYPE_RADIO = 'radio';
/** @var string */ /** @var string */
public const ENUM_INPUT_TYPE_CHECKBOX = 'checkbox'; public const ENUM_INPUT_TYPE_CHECKBOX = 'checkbox';
@@ -190,6 +187,9 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
/** @var array initial attributes flags cache [attcode]['flags'] */ /** @var array initial attributes flags cache [attcode]['flags'] */
protected $aInitialAttributesFlags; protected $aInitialAttributesFlags;
protected $iUpdateLoopCount;
const MAX_UPDATE_LOOP_COUNT = 10;
/** /**
* @var array First level classname, second level id, value number of calls done * @var array First level classname, second level id, value number of calls done
@@ -227,6 +227,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$this->sDisplayMode = static::DEFAULT_DISPLAY_MODE; $this->sDisplayMode = static::DEFAULT_DISPLAY_MODE;
$this->bAllowWrite = false; $this->bAllowWrite = false;
$this->bAllowDelete = false; $this->bAllowDelete = false;
$this->iUpdateLoopCount = 0;
} }
/** /**
@@ -746,13 +747,7 @@ HTML
$oPage->SetCurrentTab($sTabCode, $oAttDef->GetLabel().$sCount, $sTabDescription); $oPage->SetCurrentTab($sTabCode, $oAttDef->GetLabel().$sCount, $sTabDescription);
$aArgs = array('this' => $this); $aArgs = array('this' => $this);
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE));
$sEditWhen = $oAttDef->GetEditWhen();
// Calculate if edit_when allows to edit based on current $bEditMode
$bIsEditableBasedOnEditWhen = ($sEditWhen === LINKSET_EDITWHEN_ALWAYS) ||
($bEditMode ? $sEditWhen === LINKSET_EDITWHEN_ON_HOST_EDITION : $sEditWhen === LINKSET_EDITWHEN_ON_HOST_DISPLAY);
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE)) || !$bIsEditableBasedOnEditWhen;
if ($bEditMode && (!$bReadOnly)) { if ($bEditMode && (!$bReadOnly)) {
$sInputId = $this->m_iFormId.'_'.$sAttCode; $sInputId = $this->m_iFormId.'_'.$sAttCode;
$sDisplayValue = ''; // not used $sDisplayValue = ''; // not used
@@ -762,9 +757,9 @@ HTML
$oPage->add($sHTMLValue); $oPage->add($sHTMLValue);
} else { } else {
if ($oAttDef->IsIndirect()) { if ($oAttDef->IsIndirect()) {
$oBlockLinkSetViewTable = new BlockIndirectLinkSetViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef, $bReadOnly); $oBlockLinkSetViewTable = new BlockIndirectLinkSetViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef);
} else { } else {
$oBlockLinkSetViewTable = new BlockDirectLinkSetViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef, $bReadOnly); $oBlockLinkSetViewTable = new BlockDirectLinkSetViewTable($oPage, $this, $sClass, $sAttCode, $oAttDef);
} }
$oPage->AddUiBlock($oBlockLinkSetViewTable); $oPage->AddUiBlock($oBlockLinkSetViewTable);
} }
@@ -1174,7 +1169,7 @@ HTML
/** /**
* @param \WebPage $oPage * @param \WebPage $oPage
* @param \CMDBObjectSet $oSet * @param \CMDBObjectSet $oSet
* @param array $aExtraParams See possible values in {@see DataTableUIBlockFactory::RenderDataTable()} * @param array $aExtraParams
* *
* @throws \ApplicationException * @throws \ApplicationException
* @throws \CoreException * @throws \CoreException
@@ -2361,7 +2356,6 @@ EOF
case 'LinkedSet': case 'LinkedSet':
if ($oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_PROPERTY) { if ($oAttDef->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_PROPERTY) {
$sInputType = self::ENUM_INPUT_TYPE_TAGSET_LINKEDSET;
if (array_key_exists('bulk_context', $aArgs)) { if (array_key_exists('bulk_context', $aArgs)) {
$oTagSetBlock = LinkSetUIBlockFactory::MakeForBulkLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName, $aArgs['bulk_context']); $oTagSetBlock = LinkSetUIBlockFactory::MakeForBulkLinkSet($iId, $oAttDef, $value, $sWizardHelperJsVarName, $aArgs['bulk_context']);
} else { } else {
@@ -2828,33 +2822,33 @@ JS
} }
} }
// Custom operation for the form ? // Custom operation for the form ?
if (isset($aExtraParams['custom_operation'])) { if (isset($aExtraParams['custom_operation'])) {
$sOperation = $aExtraParams['custom_operation']; $sOperation = $aExtraParams['custom_operation'];
} else { } else {
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) { if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$sOperation = 'apply_modify'; $sOperation = 'apply_modify';
} else { } else {
$sOperation = 'apply_new'; $sOperation = 'apply_new';
} }
} }
$oContentBlock = new UIContentBlock(); $oContentBlock = new UIContentBlock();
$oPage->AddUiBlock($oContentBlock); $oPage->AddUiBlock($oContentBlock);
$oForm = new Form("form_{$this->m_iFormId}"); $oForm = new Form("form_{$this->m_iFormId}");
$oForm->SetAction($sFormAction); $oForm->SetAction($sFormAction);
$sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');"; $sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');";
if (isset($aExtraParams['js_handlers']['form_on_submit'])) { if (isset($aExtraParams['js_handlers']['form_on_submit'])) {
$oForm->SetOnSubmitJsCode($sOnSubmitForm . $aExtraParams['js_handlers']['form_on_submit']); $oForm->SetOnSubmitJsCode($sOnSubmitForm.$aExtraParams['js_handlers']['form_on_submit']);
} else { } else {
$oForm->SetOnSubmitJsCode($sOnSubmitForm . "return bOnSubmitForm;"); $oForm->SetOnSubmitJsCode($sOnSubmitForm."return bOnSubmitForm;");
} }
$oContentBlock->AddSubBlock($oForm); $oContentBlock->AddSubBlock($oForm);
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) { if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
// The object already exists in the database, it's a modification // The object already exists in the database, it's a modification
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id")); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
} }
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', $sOperation)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', $sOperation));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
@@ -2863,11 +2857,6 @@ JS
$oPage->SetTransactionId($iTransactionId); $oPage->SetTransactionId($iTransactionId);
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
// Add temporary object watchdog (only on root form)
if (!utils::IsXmlHttpRequest()) {
$oPage->add_ready_script(TemporaryObjectHelper::GetWatchDogJS($iTransactionId));
}
// TODO 3.0.0: Is this (the if condition, not the code inside) still necessary? // TODO 3.0.0: Is this (the if condition, not the code inside) still necessary?
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) { if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) {
$sClassLabel = MetaModel::GetName($sClass); $sClassLabel = MetaModel::GetName($sClass);
@@ -2878,34 +2867,34 @@ JS
} }
} }
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null); $oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel(); $oCancelButton = ButtonUIBlockFactory::MakeForCancel();
$oCancelButton->AddCSSClasses(['action', 'cancel']); $oCancelButton->AddCSSClasses(['action', 'cancel']);
$oToolbarButtons->AddSubBlock($oCancelButton); $oToolbarButtons->AddSubBlock($oCancelButton);
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true); $oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
$oApplyButton->AddCSSClass('action'); $oApplyButton->AddCSSClass('action');
$oToolbarButtons->AddSubBlock($oApplyButton); $oToolbarButtons->AddSubBlock($oApplyButton);
$bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true; $bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true;
$aTransitions = $this->EnumTransitions(); $aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) { if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) {
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case... // Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this); $oSetToCheckRights = DBObjectSet::FromObject($this);
$oTransitionPopoverMenu = new PopoverMenu(); $oTransitionPopoverMenu = new PopoverMenu();
$sTPMSectionId = 'transitions'; $sTPMSectionId = 'transitions';
$oTransitionPopoverMenu->AddSection($sTPMSectionId); $oTransitionPopoverMenu->AddSection($sTPMSectionId);
$aStimuli = Metamodel::EnumStimuli($sClass); $aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) { foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass, $iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO; $sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
switch ($iActionAllowed) { switch ($iActionAllowed) {
case UR_ALLOWED_YES: case UR_ALLOWED_YES:
// Button to be displayed on its own on large screens // Button to be displayed on its own on large screens
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true); $oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action'); $oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL); $oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton); $oToolbarButtons->AddSubBlock($oButton);
// Button to be displayed in a grouped button on smaller screens // Button to be displayed in a grouped button on smaller screens
$oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);"); $oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);");
@@ -2960,7 +2949,8 @@ JS
} }
// Prepare blocker protection to avoid loosing data // Prepare blocker protection to avoid loosing data
$sBlockerId = $sClass.':'.$iKey; // Important: This must have the synthax format as in js/layouts/activity-panel/activity-panel.js $sBlockerId = uniqid($sClass.':'.$iKey.':', true);
$sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
$sJSToken = json_encode($sOwnershipToken); $sJSToken = json_encode($sOwnershipToken);
$oPage->add_ready_script(<<<JS $oPage->add_ready_script(<<<JS
// Try to release concurrent lock when leaving the page // Try to release concurrent lock when leaving the page
@@ -3017,7 +3007,7 @@ JS
$oToolbarButtons->AddCSSClass('ibo-toolbar-top'); $oToolbarButtons->AddCSSClass('ibo-toolbar-top');
$oObjectDetails->AddToolbarBlock($oToolbarButtons); $oObjectDetails->AddToolbarBlock($oToolbarButtons);
// Allow form title customization // Allow form title customization
if (array_key_exists('form_title', $aExtraParams) && $aExtraParams['form_title'] !== null) { if (array_key_exists('form_title', $aExtraParams)) {
$oObjectDetails->SetTitle($aExtraParams['form_title']); $oObjectDetails->SetTitle($aExtraParams['form_title']);
} }
} }
@@ -3060,21 +3050,16 @@ JS
$oPage->SetCurrentTab(''); $oPage->SetCurrentTab('');
// Static fields values for wizard helper serialization
$aWizardHelperStaticValues = [];
// Add as hidden inputs values that we want displayed if they're readonly // Add as hidden inputs values that we want displayed if they're readonly
if(isset($aExtraParams['forceFieldsSubmission'])){ if(isset($aExtraParams['forceFieldsSubmission'])){
$aExtraFlags = $aExtraParams['fieldsFlags'] ?? []; $aExtraFlags = $aExtraParams['fieldsFlags'] ?? [];
foreach ($aExtraParams['forceFieldsSubmission'] as $sAttCode) { foreach ($aExtraParams['forceFieldsSubmission'] as $sAttCode) {
if(FormHelper::GetAttributeFlagsForObject($this, $sAttCode, $aExtraFlags) & OPT_ATT_READONLY) { if(FormHelper::GetAttributeFlagsForObject($this, $sAttCode, $aExtraFlags) & OPT_ATT_READONLY) {
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('attr_'.$sPrefix.$sAttCode, $this->Get($sAttCode))); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('attr_'.$sPrefix.$sAttCode, $this->Get($sAttCode)));
$aWizardHelperStaticValues[$sAttCode] = $this->Get($sAttCode);
} }
} }
} }
$sWizardHelperStaticValues = json_encode($aWizardHelperStaticValues);
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId)); $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
foreach ($aExtraParams as $sName => $value) { foreach ($aExtraParams as $sName => $value) {
@@ -3101,10 +3086,7 @@ JS
$iFieldsCount = count($aFieldsMap); $iFieldsCount = count($aFieldsMap);
$sJsonFieldsMap = json_encode($aFieldsMap); $sJsonFieldsMap = json_encode($aFieldsMap);
$sLifecycleStateForWizardHelper = ''; $sState = $this->GetState();
if (MetaModel::HasLifecycle($sClass)) {
$sLifecycleStateForWizardHelper = $this->GetState();
}
$sSessionStorageKey = $sClass.'_'.$iKey; $sSessionStorageKey = $sClass.'_'.$iKey;
$sTempId = utils::GetUploadTempId($iTransactionId); $sTempId = utils::GetUploadTempId($iTransactionId);
$oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId)); $oPage->add_ready_script(InlineImage::EnableCKEditorImageUpload($this, $sTempId));
@@ -3114,10 +3096,9 @@ JS
sessionStorage.removeItem('$sSessionStorageKey'); sessionStorage.removeItem('$sSessionStorageKey');
// Create the object once at the beginning of the page... // Create the object once at the beginning of the page...
var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix', '$sLifecycleStateForWizardHelper'); var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix', '$sState');
oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap); oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap);
oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount); oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount);
oWizardHelper$sPrefix.SetStaticValues($sWizardHelperStaticValues);
EOF EOF
); );
$oPage->add_ready_script( $oPage->add_ready_script(
@@ -3409,15 +3390,22 @@ EOF
]; ];
// The list of candidate fields is made of the ordered list of "details" attributes + other attributes // The list of candidate fields is made of the ordered list of "details" attributes + other attributes
// First attributes from the "details" zlist as they were sorted... $aAttributes = array();
$aList = $this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')); foreach ($this->FlattenZList(MetaModel::GetZListItems($sClass, 'details')) as $sAttCode) {
$aAttributes[$sAttCode] = true;
// ... then append forgotten attributes }
foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) { foreach (MetaModel::GetAttributesList($sClass) as $sAttCode) {
if (!in_array($sAttCode, $aList)) { if (!array_key_exists($sAttCode, $aAttributes)) {
$aList[] = $sAttCode; $aAttributes[$sAttCode] = true;
} }
} }
// Order the fields based on their dependencies, set the fields for which there is only one possible value
// and perform this in the order of dependencies to avoid dead-ends
$aDeps = array();
foreach ($aAttributes as $sAttCode => $trash) {
$aDeps[$sAttCode] = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
}
$aList = $this->OrderDependentFields($aDeps);
$bExistFieldToDisplay = false; $bExistFieldToDisplay = false;
foreach ($aList as $sAttCode) { foreach ($aList as $sAttCode) {
@@ -3712,7 +3700,7 @@ HTML;
if ($oAttDef->GetEditClass() == 'Document') { if ($oAttDef->GetEditClass() == 'Document') {
/** @var \ormDocument $oDocument */ /** @var \ormDocument $oDocument */
$oDocument = $this->Get($sAttCode); $oDocument = $this->Get($sAttCode);
if (is_object($oDocument) && !$oDocument->IsEmpty()) { if (!$oDocument->IsEmpty()) {
$sFieldAsHtml = $this->GetAsHTML($sAttCode); $sFieldAsHtml = $this->GetAsHTML($sAttCode);
$sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_'); $sDisplayLabel = Dict::S('UI:OpenDocumentInNewWindow_');
@@ -4530,12 +4518,16 @@ HTML;
*/ */
public function DBInsertNoReload() public function DBInsertNoReload()
{ {
$this->LogCRUDEnter(__METHOD__);
try { try {
$res = parent::DBInsertNoReload(); $res = parent::DBInsertNoReload();
$this->SetWarningsAsSessionMessages('create'); $this->SetWarningsAsSessionMessages('create');
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
}
} finally { } finally {
if (static::IsCrudStackEmpty()) { if (static::IsCrudStackEmpty()) {
// Avoid signaling the current object that links were modified // Avoid signaling the current object that links were modified
@@ -4543,25 +4535,9 @@ HTML;
static::FireEventDbLinksChangedForAllObjects(); static::FireEventDbLinksChangedForAllObjects();
} }
} }
$this->LogCRUDExit(__METHOD__);
return $res; return $res;
} }
protected function PostInsertActions(): void
{
parent::PostInsertActions();
// Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
}
}
/** /**
* @inheritdoc * @inheritdoc
* Attaches InlineImages to the current object * Attaches InlineImages to the current object
@@ -4571,23 +4547,15 @@ HTML;
InlineImage::FinalizeInlineImages($this); InlineImage::FinalizeInlineImages($this);
} }
/**
* @deprecated 3.1.1 3.2.0 N°6966 We will have only one DBClone method in the future
*/
protected function DBCloneTracked_Internal($newKey = null) protected function DBCloneTracked_Internal($newKey = null)
{ {
/** @var cmdbAbstractObject $oNewObj */ $oNewObj = parent::DBCloneTracked_Internal($newKey);
$oNewObj = MetaModel::GetObject(get_class($this), parent::DBCloneTracked_Internal($newKey));
// Invoke extensions after insertion (the object must exist, have an id, etc.) // Invoke extensions after insertion (the object must exist, have an id, etc.)
/** @var \iApplicationObjectExtension $oExtensionInstance */ /** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{ {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange()); $oExtensionInstance->OnDBInsert($oNewObj, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBInsert');
} }
return $oNewObj; return $oNewObj;
@@ -4595,42 +4563,57 @@ HTML;
public function DBUpdate() public function DBUpdate()
{ {
$this->LogCRUDEnter(__METHOD__);
$res = 0;
try { try {
if (count($this->ListChanges()) === 0) {
$this->LogCRUDExit(__METHOD__);
return $this->GetKey();
}
$res = parent::DBUpdate(); $res = parent::DBUpdate();
$this->SetWarningsAsSessionMessages('update'); $this->SetWarningsAsSessionMessages('update');
// Protection against reentrance (e.g. cascading the update of ticket logs)
// Note: This is based on the fix made on r 3190 in DBObject::DBUpdate()
if (!MetaModel::StartReentranceProtection($this)) {
$sClass = get_class($this);
$sKey = $this->GetKey();
IssueLog::Debug("CRUD: DBUpdate $sClass::$sKey Rejected (reentrance)", LogChannels::DM_CRUD);
return $res;
}
try {
// Invoke extensions after the update (could be before)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
}
}
finally {
MetaModel::StopReentranceProtection($this);
}
$aChanges = $this->ListChanges();
if (count($aChanges) != 0) {
$this->iUpdateLoopCount++;
if ($this->iUpdateLoopCount > self::MAX_UPDATE_LOOP_COUNT) {
$sClass = get_class($this);
$sKey = $this->GetKey();
$aPlugins = [];
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
$aPlugins[] = get_class($oExtensionInstance);
}
$sPlugins = implode(', ', $aPlugins);
IssueLog::Error("CRUD: DBUpdate $sClass::$sKey Update loop detected plugins: $sPlugins", LogChannels::DM_CRUD);
} else {
return $this->DBUpdate();
}
}
} finally { } finally {
if (static::IsCrudStackEmpty()) { if (static::IsCrudStackEmpty()) {
static::FireEventDbLinksChangedForAllObjects(); static::FireEventDbLinksChangedForAllObjects();
} }
} }
$this->LogCRUDExit(__METHOD__);
return $res; return $res;
} }
protected function PostUpdateActions(array $aChanges): void
{
parent::PostUpdateActions($aChanges);
// Invoke extensions after the update (could be before)
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBUpdate');
}
}
/** /**
* @param string $sMessageIdPrefix * @param string $sMessageIdPrefix
* *
@@ -4650,56 +4633,26 @@ HTML;
public function DBDelete(&$oDeletionPlan = null) public function DBDelete(&$oDeletionPlan = null)
{ {
$this->LogCRUDEnter(__METHOD__);
try { try {
parent::DBDelete($oDeletionPlan); parent::DBDelete($oDeletionPlan);
} finally { } finally {
if (static::IsCrudStackEmpty()) { if (static::IsCrudStackEmpty()) {
// Avoid signaling the current object that links were modified // Avoid signaling the current object that links were modified
static::RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey()); static::RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey());
$this->LogCRUDDebug(__METHOD__, var_export(self::$aObjectsAwaitingEventDbLinksChanged, true));
static::FireEventDbLinksChangedForAllObjects(); static::FireEventDbLinksChangedForAllObjects();
} }
} }
$this->LogCRUDExit(__METHOD__);
return $oDeletionPlan; return $oDeletionPlan;
} }
final protected function PreDeleteActions(): void protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{
/** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
}
parent::PreDeleteActions();
}
final protected function PostDeleteActions(): void
{
parent::PostDeleteActions();
}
/**
* @deprecated 3.1.1 3.2.0 N°6967 We will have only one DBDelete method in the future
*/
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{ {
// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information // Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
/** @var \iApplicationObjectExtension $oExtensionInstance */ /** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{ {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBDelete()");
$oKPI = new ExecutionKPI();
$oExtensionInstance->OnDBDelete($this, self::GetCurrentChange()); $oExtensionInstance->OnDBDelete($this, self::GetCurrentChange());
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnDBDelete');
} }
return parent::DBDeleteTracked_Internal($oDeletionPlan); return parent::DBDeleteTracked_Internal($oDeletionPlan);
@@ -4717,16 +4670,9 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */ /** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{ {
$sExtensionClass = get_class($oExtensionInstance); if ($oExtensionInstance->OnIsModified($this))
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified()"); {
$oKPI = new ExecutionKPI();
$bIsModified = $oExtensionInstance->OnIsModified($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnIsModified');
if ($bIsModified) {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
return true; return true;
} else {
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> false");
} }
} }
@@ -4778,11 +4724,7 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */ /** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{ {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToWrite()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToWrite($this); $aNewIssues = $oExtensionInstance->OnCheckToWrite($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToWrite');
if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array if (is_array($aNewIssues) && (count($aNewIssues) > 0)) // Some extensions return null instead of an empty array
{ {
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues); $this->m_aCheckIssues = array_merge($this->m_aCheckIssues, $aNewIssues);
@@ -4830,11 +4772,7 @@ HTML;
/** @var \iApplicationObjectExtension $oExtensionInstance */ /** @var \iApplicationObjectExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
{ {
$sExtensionClass = get_class($oExtensionInstance);
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnCheckToDelete()");
$oKPI = new ExecutionKPI();
$aNewIssues = $oExtensionInstance->OnCheckToDelete($this); $aNewIssues = $oExtensionInstance->OnCheckToDelete($this);
$oKPI->ComputeStatsForExtension($oExtensionInstance, 'OnCheckToDelete');
if (is_array($aNewIssues) && count($aNewIssues) > 0) if (is_array($aNewIssues) && count($aNewIssues) > 0)
{ {
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues); $this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues, $aNewIssues);
@@ -5388,7 +5326,7 @@ EOF
$aErrors = $oObj->UpdateObjectFromPostedForm(''); $aErrors = $oObj->UpdateObjectFromPostedForm('');
$bResult = (count($aErrors) == 0); $bResult = (count($aErrors) == 0);
if ($bResult) { if ($bResult) {
[$bResult, $aErrors] = $oObj->CheckToWrite(); list($bResult, $aErrors) = $oObj->CheckToWrite();
} }
if ($bPreview) { if ($bPreview) {
$sStatus = $bResult ? Dict::S('UI:BulkModifyStatusOk') : Dict::S('UI:BulkModifyStatusError'); $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusOk') : Dict::S('UI:BulkModifyStatusError');
@@ -5405,11 +5343,6 @@ EOF
'errors' => '<p>'.($bResult ? '' : implode('</p><p>', $aErrorsToDisplay)).'</p>', 'errors' => '<p>'.($bResult ? '' : implode('</p><p>', $aErrorsToDisplay)).'</p>',
); );
if ($bResult && (!$bPreview)) { if ($bResult && (!$bPreview)) {
// doing the check will load multiple times same objects :/
// but it shouldn't cost too much on execution time
// user can mitigate by selecting less extkeys/lnk to set and/or less objects to update 🤷‍♂️
$oObj->CheckChangedExtKeysValues();
$oObj->DBUpdate(); $oObj->DBUpdate();
} }
} }
@@ -5938,28 +5871,39 @@ JS
*/ */
final protected function FireEventCheckToWrite(): void final protected function FireEventCheckToWrite(): void
{ {
$this->FireEvent(EVENT_DB_CHECK_TO_WRITE, ['is_new' => $this->IsNew()]); $this->FireEvent(EVENT_DB_CHECK_TO_WRITE);
}
final protected function FireEventBeforeWrite()
{
$this->FireEvent(EVENT_DB_BEFORE_WRITE, ['is_new' => $this->IsNew()]);
} }
/**
* @return void
* @throws \CoreException
*
* @since 3.1.0
*/
final protected function FireEventCreateDone(): void
{
$this->NotifyAttachedObjectsOnLinkClassModification();
$this->FireEventDbLinksChangedForCurrentObject();
$this->FireEvent(EVENT_DB_CREATE_DONE);
}
/////////////
/// UPDATE
///
/** /**
* @param array $aChanges * @param array $aChanges
* @param bool $bIsNew
* *
* @return void * @return void
* @throws \ArchivedObjectException * @throws \ArchivedObjectException
* @throws \CoreException * @throws \CoreException
* @since 3.1.0 * @since 3.1.0
*/ */
final protected function FireEventAfterWrite(array $aChanges, bool $bIsNew): void final protected function FireEventUpdateDone(array $aChanges): void
{ {
$this->NotifyAttachedObjectsOnLinkClassModification(); $this->NotifyAttachedObjectsOnLinkClassModification();
$this->RemoveObjectAwaitingEventDbLinksChanged(get_class($this), $this->GetKey()); $this->FireEventDbLinksChangedForCurrentObject();
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges]); $this->FireEvent(EVENT_DB_UPDATE_DONE, ['changes' => $aChanges]);
} }
////////////// //////////////
@@ -5978,82 +5922,56 @@ JS
$this->FireEvent(EVENT_DB_CHECK_TO_DELETE, ['deletion_plan' => $oDeletionPlan]); $this->FireEvent(EVENT_DB_CHECK_TO_DELETE, ['deletion_plan' => $oDeletionPlan]);
} }
/**
* @return void
* @throws \CoreException
* @since 3.1.2
*/
final protected function FireEventAboutToDelete(): void
{
$this->FireEvent(EVENT_DB_ABOUT_TO_DELETE);
}
/** /**
* @return void * @return void
* @throws \CoreException * @throws \CoreException
* *
* @since 3.1.0 * @since 3.1.0
*/ */
final protected function FireEventAfterDelete(): void final protected function FireEventDeleteDone(): void
{ {
$this->NotifyAttachedObjectsOnLinkClassModification(); $this->NotifyAttachedObjectsOnLinkClassModification();
$this->FireEvent(EVENT_DB_AFTER_DELETE); $this->FireEventDbLinksChangedForCurrentObject();
$this->FireEvent(EVENT_DB_DELETE_DONE);
} }
/** /**
* Possibility for linked classes to be notified of current class modification * If the passed object is an instance of a link class, then will register each remote object for modification using {@see static::RegisterObjectAwaitingEventDbLinksChanged()}
*
* If an external key was modified, register also the previous object that was linked previously. * If an external key was modified, register also the previous object that was linked previously.
* *
* @uses static::RegisterObjectAwaitingEventDbLinksChanged() * @throws \ArchivedObjectException
* @throws \CoreException
* @throws \Exception
* *
* @throws ArchivedObjectException * @since 3.1.0 N°5906
* @throws CoreException
* @throws Exception
*
* @since 3.1.0 N°5906 method creation
* @since 3.1.1 3.2.0 N°6228 now just notify attributes having `with_php_computation`
*/ */
final protected function NotifyAttachedObjectsOnLinkClassModification(): void final protected function NotifyAttachedObjectsOnLinkClassModification(): void
{ {
$sClass = get_class($this);
if (false === MetaModel::IsLinkClass($sClass)) {
return;
}
// previous values in case of link change // previous values in case of link change
$aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes(); $aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes();
$sClass = get_class($this);
$aClassExtKeyAttCodes = MetaModel::GetAttributesList($sClass, [AttributeExternalKey::class]);
foreach ($aClassExtKeyAttCodes as $sExternalKeyAttCode) {
/** @var AttributeExternalKey $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sClass, $sExternalKeyAttCode);
if (false === $this->DoesTargetObjectHavePhpComputation($oAttDef)) { $aLnkClassExternalKeys = MetaModel::GetAttributesList($sClass, [AttributeExternalKey::class]);
continue; foreach ($aLnkClassExternalKeys as $sExternalKeyAttCode) {
/** @var \AttributeExternalKey $oExternalKeyAttDef */
$oExternalKeyAttDef = MetaModel::GetAttributeDef($sClass, $sExternalKeyAttCode);
$sRemoteClassName = $oExternalKeyAttDef->GetTargetClass();
$sRemoteObjectId = $this->Get($sExternalKeyAttCode);
if ($sRemoteObjectId > 0) {
self::RegisterObjectAwaitingEventDbLinksChanged($sRemoteClassName, $sRemoteObjectId);
} }
$sTargetObjectId = $this->Get($sExternalKeyAttCode); $sPreviousRemoteObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0;
$sTargetClass = $oAttDef->GetTargetClass(); if ($sPreviousRemoteObjectId > 0) {
if ($sTargetObjectId > 0) { self::RegisterObjectAwaitingEventDbLinksChanged($sRemoteClassName, $sPreviousRemoteObjectId);
$this->LogCRUDDebug(__METHOD__, "Add $sTargetClass:$sTargetObjectId for DBLINKS_CHANGED");
self::RegisterObjectAwaitingEventDbLinksChanged($sTargetClass, $sTargetObjectId);
}
$sPreviousTargetObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0;
if ($sPreviousTargetObjectId > 0) {
$this->LogCRUDDebug(__METHOD__, "Add $sTargetClass:$sPreviousTargetObjectId for DBLINKS_CHANGED");
self::RegisterObjectAwaitingEventDbLinksChanged($sTargetClass, $sPreviousTargetObjectId);
} }
} }
} }
private function DoesTargetObjectHavePhpComputation(AttributeExternalKey $oAttDef): bool
{
/** @var AttributeLinkedSet $oAttDefMirrorLink */
$oAttDefMirrorLink = $oAttDef->GetMirrorLinkAttribute();
if (is_null($oAttDefMirrorLink) || false === $oAttDefMirrorLink->HasPHPComputation()){
return false;
}
return true;
}
/** /**
* Register one object for later EVENT_DB_LINKS_CHANGED event. * Register one object for later EVENT_DB_LINKS_CHANGED event.
* *
@@ -6071,6 +5989,26 @@ JS
} }
} }
/**
* Fire the EVENT_DB_LINKS_CHANGED event if current object is registered
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
*
* @since 3.1.0 N°5906
*/
final protected function FireEventDbLinksChangedForCurrentObject(): void
{
if (true === static::IsEventDBLinksChangedBlocked()) {
return;
}
$sClass = get_class($this);
$sId = $this->GetKey();
self::FireEventDbLinksChangedForClassId($sClass, $sId);
}
/** /**
* Fire the EVENT_DB_LINKS_CHANGED event if given object is registered, and unregister it * Fire the EVENT_DB_LINKS_CHANGED event if given object is registered, and unregister it
* *
@@ -6099,23 +6037,16 @@ JS
// - we have a EVENT_DB_LINKS_CHANGED listener on Ticket that will update impacted items, so it will create new lnkApplicationSolutionToFunctionalCI // - we have a EVENT_DB_LINKS_CHANGED listener on Ticket that will update impacted items, so it will create new lnkApplicationSolutionToFunctionalCI
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener // We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
// By disabling the event to be fired, we can remove the current object from the attribute ! // By disabling the event to be fired, we can remove the current object from the attribute !
$oObject = MetaModel::GetObject($sClass, $sId, false); /** @noinspection PhpRedundantOptionalArgumentInspection */
self::FireEventDbLinksChangedForObject($oObject); $oObject = MetaModel::GetObject($sClass, $sId, true);
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
}
private static function FireEventDbLinksChangedForObject(DBObject $oObject)
{
self::SetEventDBLinksChangedBlocked(true); self::SetEventDBLinksChangedBlocked(true);
// N°6408 The object can have been deleted MetaModel::StartReentranceProtection($oObject);
if (!is_null($oObject)) { $oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED); MetaModel::StopReentranceProtection($oObject);
if ($oObject->IsModified()) {
// Update the object if needed $oObject->DBUpdate();
if (count($oObject->ListChanges()) !== 0) {
$oObject->DBUpdate();
}
} }
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false); cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/CSVPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/CSVPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -918,11 +918,6 @@ class RuntimeDashboard extends Dashboard
{ {
$bCustomized = false; $bCustomized = false;
$sDashboardFileSanitized = utils::RealPath(APPROOT.$sDashboardFile, APPROOT);
if (false === $sDashboardFileSanitized) {
throw new SecurityException('Invalid dashboard file !');
}
// Search for an eventual user defined dashboard // Search for an eventual user defined dashboard
$oUDSearch = new DBObjectSearch('UserDashboard'); $oUDSearch = new DBObjectSearch('UserDashboard');
$oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '='); $oUDSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
@@ -934,7 +929,7 @@ class RuntimeDashboard extends Dashboard
$sDashboardDefinition = $oUserDashboard->Get('contents'); $sDashboardDefinition = $oUserDashboard->Get('contents');
$bCustomized = true; $bCustomized = true;
} else { } else {
$sDashboardDefinition = @file_get_contents($sDashboardFileSanitized); $sDashboardDefinition = @file_get_contents($sDashboardFile);
} }
@@ -942,7 +937,7 @@ class RuntimeDashboard extends Dashboard
$oDashboard = new RuntimeDashboard($sDashBoardId); $oDashboard = new RuntimeDashboard($sDashBoardId);
$oDashboard->FromXml($sDashboardDefinition); $oDashboard->FromXml($sDashboardDefinition);
$oDashboard->SetCustomFlag($bCustomized); $oDashboard->SetCustomFlag($bCustomized);
$oDashboard->SetDefinitionFile($sDashboardFileSanitized); $oDashboard->SetDefinitionFile($sDashboardFile);
} else { } else {
$oDashboard = null; $oDashboard = null;
} }
@@ -1141,7 +1136,7 @@ JS
$oToolbar->AddSubBlock($oActionButton); $oToolbar->AddSubBlock($oActionButton);
$aActions = array(); $aActions = array();
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile)); $sFile = addslashes($this->sDefinitionFile);
$sJSExtraParams = json_encode($aExtraParams); $sJSExtraParams = json_encode($aExtraParams);
if ($this->HasCustomDashboard()) { if ($this->HasCustomDashboard()) {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)"); $oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");
@@ -1264,12 +1259,12 @@ EOF
$sOkButtonLabel = Dict::S('UI:Button:Save'); $sOkButtonLabel = Dict::S('UI:Button:Save');
$sCancelButtonLabel = Dict::S('UI:Button:Cancel'); $sCancelButtonLabel = Dict::S('UI:Button:Cancel');
$sId = utils::HtmlEntities($this->sId); $sId = addslashes($this->sId);
$sLayoutClass = utils::HtmlEntities($this->sLayoutClass); $sLayoutClass = addslashes($this->sLayoutClass);
$sAutoReload = $this->bAutoReload ? 'true' : 'false'; $sAutoReload = $this->bAutoReload ? 'true' : 'false';
$sAutoReloadSec = (string) $this->iAutoReloadSec; $sAutoReloadSec = (string) $this->iAutoReloadSec;
$sTitle = utils::HtmlEntities($this->sTitle); $sTitle = addslashes($this->sTitle);
$sFile = utils::HtmlEntities($this->GetDefinitionFile()); $sFile = addslashes($this->GetDefinitionFile());
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'; $sUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php';
$sReloadURL = $this->GetReloadURL(); $sReloadURL = $this->GetReloadURL();

View File

@@ -667,7 +667,7 @@ class DashletUnknown extends Dashlet
*/ */
public function GetPropertiesFields(DesignerForm $oForm) public function GetPropertiesFields(DesignerForm $oForm)
{ {
$oField = new DesignerXMLField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML); $oField = new DesignerLongTextField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML);
$oForm->AddField($oField); $oForm->AddField($oField);
} }
@@ -869,7 +869,7 @@ class DashletPlainText extends Dashlet
public function Render($oPage, $bEditMode = false, $aExtraParams = array()) public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{ {
$sText = $this->aProperties['text']; $sText = $this->aProperties['text'];
$sText = utils::EscapeHtml(Dict::S($sText)); $sText = utils::EscapeHtml($sText);
$sText = str_replace(array("\r\n", "\n", "\r"), "<br/>", $sText); $sText = str_replace(array("\r\n", "\n", "\r"), "<br/>", $sText);
$sId = 'plaintext_'.($bEditMode ? 'edit_' : '').$this->sId; $sId = 'plaintext_'.($bEditMode ? 'edit_' : '').$this->sId;

View File

@@ -186,33 +186,8 @@
</menu> </menu>
</menus> </menus>
<events> <events>
<event id="EVENT_DB_BEFORE_WRITE" _delta="define">
<name>Before create or update</name>
<description><![CDATA[An object is about to be written into the database.
The object can be modified.]]></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="is_new">
<description>Creation flag</description>
<type>boolean</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_CHECK_TO_WRITE" _delta="define"> <event id="EVENT_DB_CHECK_TO_WRITE" _delta="define">
<name>Check to write</name> <description>Check an object before it is written into the database (no change possible). Call DBObject::AddCheckIssue() to signal an issue</description>
<description><![CDATA[Check an object before it is written into the database (no change possible).
Call $this->AddCheckIssue() to signal an issue.]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -222,20 +197,14 @@ Call $this->AddCheckIssue() to signal an issue.]]></description>
<description>The object to check</description> <description>The object to check</description>
<type>DBObject</type> <type>DBObject</type>
</event_datum> </event_datum>
<event_datum id="is_new">
<description>Creation flag</description>
<type>boolean</type>
</event_datum>
<event_datum id="debug_info"> <event_datum id="debug_info">
<description>Debug string</description> <description>Debug string</description>
<type>string</type> <type>string</type>
</event_datum> </event_datum>
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_AFTER_WRITE" _delta="define"> <event id="EVENT_DB_CREATE_DONE" _delta="define">
<name>After create or update</name> <description>An object has been created into the database. The modifications can be propagated to other objects.</description>
<description><![CDATA[An object has been written into the database.
The modifications can be propagated to other objects.]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -245,13 +214,22 @@ The modifications can be propagated to other objects.]]></description>
<description>The object inserted</description> <description>The object inserted</description>
<type>DBObject</type> <type>DBObject</type>
</event_datum> </event_datum>
<event_datum id="is_new"> <event_datum id="debug_info">
<description>Creation flag</description> <description>Debug string</description>
<type>boolean</type> <type>string</type>
</event_datum> </event_datum>
<event_datum id="changes"> </event_data>
<description><![CDATA[For updates, the list of changes done during this operation]]></description> </event>
<type>array</type> <event id="EVENT_DB_UPDATE_DONE" _delta="define">
<description>An object has been updated into the database and reloaded.</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>DBObject::AfterUpdate</replaces>
<event_data>
<event_datum id="object">
<description>The object updated</description>
<type>DBObject</type>
</event_datum> </event_datum>
<event_datum id="debug_info"> <event_datum id="debug_info">
<description>Debug string</description> <description>Debug string</description>
@@ -260,9 +238,7 @@ The modifications can be propagated to other objects.]]></description>
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_CHECK_TO_DELETE" _delta="define"> <event id="EVENT_DB_CHECK_TO_DELETE" _delta="define">
<name>Check to delete</name> <description>Check an object before it is deleted from the database. Call DBObject::AddDeleteIssue() to signal an issue</description>
<description><![CDATA[Check an object before it is deleted from the database.
Call $this->AddDeleteIssue() to signal an issue.]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -278,27 +254,8 @@ Call $this->AddDeleteIssue() to signal an issue.]]></description>
</event_datum> </event_datum>
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_ABOUT_TO_DELETE" _delta="define"> <event id="EVENT_DB_DELETE_DONE" _delta="define">
<name>Before delete</name> <description>An object has been deleted into the database</description>
<description><![CDATA[An object is about to be deleted from the database]]></description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<replaces>cmdbAbstractObject::OnDelete</replaces>
<event_data>
<event_datum id="object">
<description>The object about to be deleted</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_AFTER_DELETE" _delta="define">
<name>After delete</name>
<description><![CDATA[An object has been deleted into the database]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -314,10 +271,8 @@ Call $this->AddDeleteIssue() to signal an issue.]]></description>
</event_datum> </event_datum>
</event_data> </event_data>
</event> </event>
<event id="EVENT_ENUM_TRANSITIONS" _delta="define"> <event id="EVENT_DB_BEFORE_APPLY_STIMULUS" _delta="define">
<name>Enum transitions</name> <description>A stimulus is about to be applied to an object</description>
<description><![CDATA[Manage the allowed transitions in current object state.
The only action allowed is to deny transitions with $this->DenyTransition()]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -326,9 +281,89 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
<description>The object where the stimulus is targeted</description> <description>The object where the stimulus is targeted</description>
<type>DBObject</type> <type>DBObject</type>
</event_datum> </event_datum>
<event_datum id="allowed_stimuli"> <event_datum id="stimulus">
<description>The list of available stimuli in the current state</description> <description>Current stimulus applied</description>
<type>array</type> <type>string</type>
</event_datum>
<event_datum id="previous_state">
<description>Object previous state</description>
<type>string</type>
</event_datum>
<event_datum id="new_state">
<description>Object new state</description>
<type>string</type>
</event_datum>
<event_datum id="save_object">
<description>The object must be saved in the database</description>
<type>boolean</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_AFTER_APPLY_STIMULUS" _delta="define">
<description>A stimulus has been applied to an object</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="object">
<description>The object where the stimulus is targeted</description>
<type>DBObject</type>
</event_datum>
<event_datum id="stimulus">
<description>Current stimulus applied</description>
<type>string</type>
</event_datum>
<event_datum id="previous_state">
<description>Object previous state</description>
<type>string</type>
</event_datum>
<event_datum id="new_state">
<description>Object new state</description>
<type>string</type>
</event_datum>
<event_datum id="save_object">
<description>The object is asked to be saved in the database</description>
<type>boolean</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DB_APPLY_STIMULUS_FAILED" _delta="define">
<description>A stimulus has failed</description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="action">
<description>The action that failed to apply the stimulus</description>
<type>string</type>
</event_datum>
<event_datum id="object">
<description>The object where the stimulus is targeted</description>
<type>DBObject</type>
</event_datum>
<event_datum id="stimulus">
<description>Current stimulus applied</description>
<type>string</type>
</event_datum>
<event_datum id="previous_state">
<description>Object previous state</description>
<type>string</type>
</event_datum>
<event_datum id="new_state">
<description>Object new state</description>
<type>string</type>
</event_datum>
<event_datum id="save_object">
<description>The object must be saved in the database</description>
<type>boolean</type>
</event_datum> </event_datum>
<event_datum id="debug_info"> <event_datum id="debug_info">
<description>Debug string</description> <description>Debug string</description>
@@ -337,8 +372,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_LINKS_CHANGED" _delta="define"> <event id="EVENT_DB_LINKS_CHANGED" _delta="define">
<name>Links on object have changed</name> <description>At least one link class was changed</description>
<description><![CDATA[At least one link class was changed]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -354,9 +388,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_OBJECT_RELOAD" _delta="define"> <event id="EVENT_DB_OBJECT_RELOAD" _delta="define">
<name>Object reload</name> <description>An object has been re-loaded from the database</description>
<internal>true</internal>
<description><![CDATA[An object has been re-loaded from the database]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -372,8 +404,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_COMPUTE_VALUES" _delta="define"> <event id="EVENT_DB_COMPUTE_VALUES" _delta="define">
<name>Recompute object values</name> <description>An object needs to be recomputed after changes</description>
<description><![CDATA[An object needs to be recomputed after changes]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -390,8 +421,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_ARCHIVE" _delta="define"> <event id="EVENT_DB_ARCHIVE" _delta="define">
<name>Object archived</name> <description>An object has been archived</description>
<description><![CDATA[An object has been archived]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -407,8 +437,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_UNARCHIVE" _delta="define"> <event id="EVENT_DB_UNARCHIVE" _delta="define">
<name>Object unarchived</name> <description>An object has been unarchived</description>
<description><![CDATA[An object has been unarchived]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -424,9 +453,7 @@ The only action allowed is to deny transitions with $this->DenyTransition()]]></
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_SET_ATTRIBUTES_FLAGS" _delta="define"> <event id="EVENT_DB_SET_ATTRIBUTES_FLAGS" _delta="define">
<name>Set attributes flags</name> <description>Set object attributes flags. Call cmdbAbstractObject::AddAttributeFlags() for all the attributes to be set for this target state.</description>
<description><![CDATA[Set object attributes flags.
Call $this->AddAttributeFlags() for all the attributes to be set for this target state.]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -437,7 +464,7 @@ Call $this->AddAttributeFlags() for all the attributes to be set for this target
</event_datum> </event_datum>
<event_datum id="target_state"> <event_datum id="target_state">
<description>The target state in which to evaluate the flags</description> <description>The target state in which to evaluate the flags</description>
<type>string</type> <type>array</type>
</event_datum> </event_datum>
<event_datum id="debug_info"> <event_datum id="debug_info">
<description>Debug string</description> <description>Debug string</description>
@@ -446,9 +473,7 @@ Call $this->AddAttributeFlags() for all the attributes to be set for this target
</event_data> </event_data>
</event> </event>
<event id="EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS" _delta="define"> <event id="EVENT_DB_SET_INITIAL_ATTRIBUTES_FLAGS" _delta="define">
<name>Set initial attributes flags</name> <description>Set object initial attributes flags. Call cmdbAbstractObject::AddInitialAttributeFlags() for all the initial attributes to be set initially.</description>
<description><![CDATA[Set object initial attributes flags.
Call $this->AddInitialAttributeFlags() for all the initial attributes to be set initially.]]></description>
<sources> <sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source> <source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources> </sources>
@@ -464,8 +489,7 @@ Call $this->AddInitialAttributeFlags() for all the initial attributes to be set
</event_data> </event_data>
</event> </event>
<event id="EVENT_DOWNLOAD_DOCUMENT" _delta="define"> <event id="EVENT_DOWNLOAD_DOCUMENT" _delta="define">
<name>Document downloaded</name> <description>A document has been downloaded from the GUI</description>
<description><![CDATA[A document has been downloaded from the GUI]]></description>
<sources> <sources>
<source id="Document">Document</source> <source id="Document">Document</source>
</sources> </sources>
@@ -485,9 +509,7 @@ Call $this->AddInitialAttributeFlags() for all the initial attributes to be set
</event_data> </event_data>
</event> </event>
<event id="EVENT_LOGIN" _delta="define"> <event id="EVENT_LOGIN" _delta="define">
<name>Login</name> <description>Inform the listeners about the connection states</description>
<internal>true</internal>
<description><![CDATA[Inform the listeners about the connection states]]></description>
<event_data> <event_data>
<event_datum id="code"> <event_datum id="code">
<description>The login step result code (LoginWebPage::EXIT_CODE_...) </description> <description>The login step result code (LoginWebPage::EXIT_CODE_...) </description>

View File

@@ -704,7 +704,7 @@ class DisplayBlock
if ($bDoSearch) if ($bDoSearch)
{ {
// Keep the table_id identifying this table if we're performing a search // Keep the table_id identifying this table if we're performing a search
$sTableId = utils::ReadParam('_table_id_', null, false, utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER); $sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data');
if ($sTableId != null) if ($sTableId != null)
{ {
$aExtraParams['table_id'] = $sTableId; $aExtraParams['table_id'] = $sTableId;
@@ -1083,7 +1083,6 @@ JS
$this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams); $this->m_oSet = new CMDBObjectSet($this->m_oFilter, array(), $aQueryParams);
$this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData); $this->m_oSet->SetShowObsoleteData($this->m_bShowObsoleteData);
} }
// Summary details // Summary details
$aCounts = array(); $aCounts = array();
$aStateLabels = array(); $aStateLabels = array();
@@ -1130,18 +1129,6 @@ JS
} }
$oBlock = new UIContentBlockWithJSRefreshCallback(null, ["ibo-dashlet-header-dynamic--container"]); $oBlock = new UIContentBlockWithJSRefreshCallback(null, ["ibo-dashlet-header-dynamic--container"]);
// N°6394 Make sure to sort elements as defined in the datamodel
if (utils::IsNotNullOrEmptyString($sStateAttrCode)) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sStateAttrCode);
$aAllowedValues = $oAttDef->GetAllowedValues() ?? [];
$AllowedValuesKeys = array_keys($aAllowedValues);
uksort($aStateLabels, function ($sKey1, $sKey2) use ($AllowedValuesKeys) {
return array_search($sKey1, $AllowedValuesKeys) > array_search($sKey2, $AllowedValuesKeys) ? 1 : -1;
});
}
foreach ($aStateLabels as $sStateValue => $sStateLabel) { foreach ($aStateLabels as $sStateValue => $sStateLabel) {
$aCount = $aCounts[$sStateValue]; $aCount = $aCounts[$sStateValue];
$sHyperlink = $aCount['link']; $sHyperlink = $aCount['link'];
@@ -1246,10 +1233,6 @@ JS
} else { } else {
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams); $oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
} }
$sClassDescription = MetaModel::GetClassDescription($sClass);
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
$oBlock->SetClassDescription($sClassDescription);
}
return $oBlock; return $oBlock;
} }
@@ -1875,13 +1858,7 @@ class MenuBlock extends DisplayBlock
/** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */ /** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */
$aToolkitActions = []; $aToolkitActions = [];
// Display menu actions only if... if (!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == "")) {
if (
// ... NOT in a selection mode
(!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == ""))
// ... "menu" parameter is NOT EXPLICITLY disabled
&& (!isset($aExtraParams['menu']) || $aExtraParams['menu'] === "1" || $aExtraParams['menu'] === true)
) {
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForLink(); $sContext = $oAppContext->GetForLink();
if (utils::IsNotNullOrEmptyString($sContext)) { if (utils::IsNotNullOrEmptyString($sContext)) {
@@ -2042,7 +2019,7 @@ class MenuBlock extends DisplayBlock
} }
//---------------------------------------------------- //----------------------------------------------------
// Any style but NOT \DisplayBlock::ENUM_STYLE_LIST_IN_OBJECT (linksets) actions // Any style but NOT "listInObject" (linksets) actions
//---------------------------------------------------- //----------------------------------------------------
if ($this->m_sStyle !== static::ENUM_STYLE_LIST_IN_OBJECT) { if ($this->m_sStyle !== static::ENUM_STYLE_LIST_IN_OBJECT) {
switch ($iSetCount) { switch ($iSetCount) {
@@ -2344,15 +2321,10 @@ class MenuBlock extends DisplayBlock
false false
); );
// creation form title
if (array_key_exists('creation_in_modal_form_title', $aExtraParams) && $aExtraParams['creation_in_modal_form_title'] !== null) {
$oAddLinkActionButton->AddDataAttribute('modal-title', $aExtraParams['creation_in_modal_form_title']);
}
// - If we are used in a Datatable, 'datatable_' will be prefixed to our $sId, so we do the same here // - If we are used in a Datatable, 'datatable_' will be prefixed to our $sId, so we do the same here
$sRealId = $sId; $sRealId = $sId;
if (in_array($this->m_sStyle, [static::ENUM_STYLE_LIST, 'links', static::ENUM_STYLE_LIST_IN_OBJECT])) { if(in_array($this->m_sStyle, [static::ENUM_STYLE_LIST, 'links', static::ENUM_STYLE_LIST_IN_OBJECT])){
$sRealId = 'datatable_'.$sId; $sRealId = 'datatable_' . $sId;
} }
$oAddLinkActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button']) $oAddLinkActionButton->AddCSSClasses(['ibo-action-button', 'ibo-regular-action-button'])
->SetOnClickJsCode("$('#$sRealId').trigger('open_creation_modal.object.itop');"); ->SetOnClickJsCode("$('#$sRealId').trigger('open_creation_modal.object.itop');");

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/ErrorPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/ErrorPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -60,24 +60,6 @@ class CoreCannotSaveObjectException extends CoreException
return $sContent; return $sContent;
} }
public function getTextMessage()
{
$sTitle = Dict::S('UI:Error:SaveFailed');
$sContent = utils::HtmlEntities($sTitle);
if (count($this->aIssues) == 1) {
$sIssue = reset($this->aIssues);
$sContent .= utils::HtmlEntities($sIssue);
} else {
foreach ($this->aIssues as $sError) {
$sContent .= " ".utils::HtmlEntities($sError).", ";
}
}
return $sContent;
}
public function getIssues() public function getIssues()
{ {
return $this->aIssues; return $this->aIssues;

View File

@@ -1,36 +0,0 @@
<?php
/**
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6458 object creation
*/
class InvalidExternalKeyValueException extends CoreUnexpectedValue
{
private const ENUM_PARAMS_OBJECT = 'current_object';
private const ENUM_PARAMS_ATTCODE = 'attcode';
private const ENUM_PARAMS_ATTVALUE = 'attvalue';
private const ENUM_PARAMS_USER = 'current_user';
public function __construct($oObject, $sAttCode, $aContextData = null, $oPrevious = null)
{
$aContextData[self::ENUM_PARAMS_OBJECT] = get_class($oObject) . '::' . $oObject->GetKey();
$aContextData[self::ENUM_PARAMS_ATTCODE] = $sAttCode;
$aContextData[self::ENUM_PARAMS_ATTVALUE] = $oObject->Get($sAttCode);
$oCurrentUser = UserRights::GetUserObject();
if (false === is_null($oCurrentUser)) {
$aContextData[self::ENUM_PARAMS_USER] = get_class($oCurrentUser) . '::' . $oCurrentUser->GetKey();
}
parent::__construct('Attribute pointing to an object that is either non existing or not readable by the current user', $aContextData, '', $oPrevious);
}
public function GetAttCode(): string
{
return $this->getContextData()[self::ENUM_PARAMS_ATTCODE];
}
public function GetAttValue(): string
{
return $this->getContextData()[self::ENUM_PARAMS_ATTVALUE];
}
}

View File

@@ -1110,41 +1110,13 @@ $('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId',
} }
EOF EOF
); );
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".$this->PrepareValueForRendering()."</textarea>"; $sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".utils::EscapeHtml($this->defaultValue)."</textarea>";
} }
else { else {
$sValue = "<div $sCSSClasses id=\"$sId\">".$this->PrepareValueForRendering()."</div>"; $sValue = "<div $sCSSClasses id=\"$sId\">".utils::EscapeHtml($this->defaultValue)."</div>";
} }
return array('label' => $this->sLabel, 'value' => $sValue); return array('label' => $this->sLabel, 'value' => $sValue);
} }
/**
* @return string|null The value itself as expected for rendering. May it be encoded, escaped or else.
* @since 3.1.0 N°6405
*/
protected function PrepareValueForRendering(): ?string
{
return utils::EscapeHtml($this->defaultValue);
}
}
/**
* Class DesignerXMLField
*
* Field to display XML content
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
* @since 3.1.0 N°6405
*/
class DesignerXMLField extends DesignerLongTextField
{
/**
* @inheritDoc
*/
protected function PrepareValueForRendering(): ?string
{
return utils::EscapeHtml($this->defaultValue, true);
}
} }
class DesignerIntegerField extends DesignerFormField class DesignerIntegerField extends DesignerFormField

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWebPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWebPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWizardWebPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/iTopWizardWebPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -80,11 +80,6 @@ class LoginBasic extends AbstractLoginFSMExtension
{ {
if (Session::Get('login_mode') == 'basic') if (Session::Get('login_mode') == 'basic')
{ {
$iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
}
LoginWebPage::HTTP401Error(); LoginWebPage::HTTP401Error();
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -79,7 +79,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
{ {
self::ResetLoginSession(); self::ResetLoginSession();
$iOnExit = LoginWebPage::getIOnExit(); $iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN) if ($iOnExit == LoginWebPage::EXIT_RETURN)
{ {
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
} }
@@ -95,12 +95,6 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
{ {
if (!Session::IsSet('login_mode')) if (!Session::IsSet('login_mode'))
{ {
// N°6358 - if EXIT_RETURN was asked, send an error
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
return LoginWebPage::LOGIN_FSM_ERROR;
}
// If no plugin validated the user, exit // If no plugin validated the user, exit
self::ResetLoginSession(); self::ResetLoginSession();
exit(); exit();
@@ -119,11 +113,6 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
protected function OnConnected(&$iErrorCode) protected function OnConnected(&$iErrorCode)
{ {
Session::Unset('login_temp_auth_user'); Session::Unset('login_temp_auth_user');
if (is_null(UserRights::GetUserObject())){
//N°7085 avoid infinite loop
IssueLog::Error("No user logged in. exit");
exit(-1);
}
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;
} }
@@ -139,4 +128,4 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte
} }
} }
} }
} }

View File

@@ -73,11 +73,6 @@ class LoginExternal extends AbstractLoginFSMExtension
{ {
if (Session::Get('login_mode') == 'external') if (Session::Get('login_mode') == 'external')
{ {
$iOnExit = LoginWebPage::getIOnExit();
if ($iOnExit === LoginWebPage::EXIT_RETURN)
{
return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM
}
LoginWebPage::HTTP401Error(); LoginWebPage::HTTP401Error();
} }
return LoginWebPage::LOGIN_FSM_CONTINUE; return LoginWebPage::LOGIN_FSM_CONTINUE;

View File

@@ -44,10 +44,6 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
exit; exit;
} }
if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) {
return LoginWebPage::LOGIN_FSM_CONTINUE;
}
// No credentials yet, display the form // No credentials yet, display the form
$oPage = LoginWebPage::NewLoginWebPage(); $oPage = LoginWebPage::NewLoginWebPage();
$oPage->DisplayLoginForm($this->bForceFormOnError); $oPage->DisplayLoginForm($this->bForceFormOnError);

View File

@@ -90,7 +90,7 @@ class LoginWebPage extends NiceWebPage
parent::__construct($sTitle); parent::__construct($sTitle);
$this->SetStyleSheet(); $this->SetStyleSheet();
$this->no_cache(); $this->no_cache();
$this->add_http_headers(); $this->add_xframe_options();
} }
public function SetStyleSheet() public function SetStyleSheet()
@@ -248,7 +248,6 @@ class LoginWebPage extends NiceWebPage
$oEmail = new Email(); $oEmail = new Email();
$oEmail->SetRecipientTO($sTo); $oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from'); $sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
$sFrom = utils::IsNullOrEmptyString($sFrom) ? MetaModel::GetConfig()->Get('email_default_sender_address') : $sFrom;
$oEmail->SetRecipientFrom($sFrom); $oEmail->SetRecipientFrom($sFrom);
$oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject', $oUser->Get('login'))); $oEmail->SetSubject(Dict::S('UI:ResetPwd-EmailSubject', $oUser->Get('login')));
$sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken); $sResetUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?loginop=reset_pwd&auth_user='.urlencode($oUser->Get('login')).'&token='.urlencode($sToken);
@@ -392,11 +391,6 @@ class LoginWebPage extends NiceWebPage
Session::Unset('can_logoff'); Session::Unset('can_logoff');
Session::Unset('archive_mode'); Session::Unset('archive_mode');
Session::Unset('impersonate_user'); Session::Unset('impersonate_user');
Session::Unset('PluginProperties');
Session::Unset('UrlMakerClass');
Session::Unset('itop_env');
Session::Unset('obj_messages');
Session::Unset('profile_list');
UserRights::_ResetSessionCache(); UserRights::_ResetSessionCache();
// If it's desired to kill the session, also delete the session cookie. // If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data! // Note: This will destroy the session, and not just the session data!

View File

@@ -103,7 +103,7 @@ class ApplicationMenu
{ {
self::$sFavoriteSiloQuery = $sOQL; self::$sFavoriteSiloQuery = $sOQL;
} }
/** /**
* Get the query used to limit the list of displayed organizations in the drop-down menu * Get the query used to limit the list of displayed organizations in the drop-down menu
* @return string The OQL query returning a list of Organization objects * @return string The OQL query returning a list of Organization objects
@@ -536,7 +536,7 @@ EOF
return -1; return -1;
} }
/** /**
* Retrieves the currently active menu (if any, otherwise the first menu is the default) * Retrieves the currently active menu (if any, otherwise the first menu is the default)
* @return string The Id of the currently active menu * @return string The Id of the currently active menu
@@ -544,7 +544,7 @@ EOF
public static function GetActiveNodeId() public static function GetActiveNodeId()
{ {
$oAppContext = new ApplicationContext(); $oAppContext = new ApplicationContext();
$sMenuId = $oAppContext->GetCurrentValue('menu', null); $sMenuId = $oAppContext->GetCurrentValue('menu', null);
if ($sMenuId === null) if ($sMenuId === null)
{ {
$sMenuId = self::GetDefaultMenuId(); $sMenuId = self::GetDefaultMenuId();
@@ -654,7 +654,7 @@ abstract class MenuNode
/** /**
* Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu * Stimulus to check: if the user can 'apply' this stimulus, then she/he can see this menu
*/ */
protected $m_aEnableStimuli; protected $m_aEnableStimuli;
/** /**
@@ -814,7 +814,7 @@ abstract class MenuNode
{ {
return false; return false;
} }
/** /**
* Add a limiting display condition for the same menu node. The conditions will be combined with a AND * Add a limiting display condition for the same menu node. The conditions will be combined with a AND
* @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction * @param $oMenuNode MenuNode Another definition of the same menu node, with potentially different access restriction
@@ -987,7 +987,7 @@ class TemplateMenuNode extends MenuNode
* @var string * @var string
*/ */
protected $sTemplateFile; protected $sTemplateFile;
/** /**
* Create a menu item based on a custom template and inserts it into the application's main menu * Create a menu item based on a custom template and inserts it into the application's main menu
* @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary) * @param string $sMenuId Unique identifier of the menu (used to identify the menu for bookmarking, and for getting the labels from the dictionary)
@@ -1058,7 +1058,7 @@ class OQLMenuNode extends MenuNode
* @var bool|null * @var bool|null
*/ */
protected $bSearchFormOpen; protected $bSearchFormOpen;
/** /**
* Extra parameters to be passed to the display block to fine tune its appearence * Extra parameters to be passed to the display block to fine tune its appearence
*/ */
@@ -1091,7 +1091,7 @@ class OQLMenuNode extends MenuNode
// Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects // Enhancement: we could set as the "enable" condition that the user has enough rights to "read" the objects
// of the class specified by the OQL... // of the class specified by the OQL...
} }
/** /**
* Set some extra parameters to be passed to the display block to fine tune its appearence * Set some extra parameters to be passed to the display block to fine tune its appearence
* @param array $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters * @param array $aParams paramCode => value. See DisplayBlock::GetDisplay for the meaning of the parameters
@@ -1111,7 +1111,7 @@ class OQLMenuNode extends MenuNode
*/ */
public function RenderContent(WebPage $oPage, $aExtraParams = array()) public function RenderContent(WebPage $oPage, $aExtraParams = array())
{ {
$oTag = new ContextTag(ContextTag::TAG_OBJECT_SEARCH); ContextTag::AddContext(ContextTag::TAG_OBJECT_SEARCH);
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId()); ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
OQLMenuNode::RenderOQLSearch OQLMenuNode::RenderOQLSearch
( (
@@ -1120,7 +1120,7 @@ class OQLMenuNode extends MenuNode
'Menu_'.$this->GetMenuId(), 'Menu_'.$this->GetMenuId(),
$this->bSearch, // Search pane $this->bSearch, // Search pane
$this->bSearchFormOpen, // Search open $this->bSearchFormOpen, // Search open
$oPage, $oPage,
array_merge($this->m_aParams, $aExtraParams), array_merge($this->m_aParams, $aExtraParams),
true true
); );
@@ -1354,10 +1354,10 @@ class NewObjectMenuNode extends MenuNode
{ {
// Enable this menu, only if the current user has enough rights to create such an object, or an object of // Enable this menu, only if the current user has enough rights to create such an object, or an object of
// any child class // any child class
$aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself $aSubClasses = MetaModel::EnumChildClasses($this->sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself
$bActionIsAllowed = false; $bActionIsAllowed = false;
foreach($aSubClasses as $sCandidateClass) foreach($aSubClasses as $sCandidateClass)
{ {
if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES))
@@ -1366,7 +1366,7 @@ class NewObjectMenuNode extends MenuNode
break; // Enough for now break; // Enough for now
} }
} }
return $bActionIsAllowed; return $bActionIsAllowed;
} }
/** /**
@@ -1508,7 +1508,7 @@ class DashboardMenuNode extends MenuNode
throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'"); throw new Exception("Error: failed to load dashboard file: '{$this->sDashboardFile}'");
} }
} }
} }
/** /**
@@ -1549,7 +1549,7 @@ class ShortcutContainerMenuNode extends MenuNode
$sName = $this->GetMenuId().'_'.$oShortcut->GetKey(); $sName = $this->GetMenuId().'_'.$oShortcut->GetKey();
new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++); new ShortcutMenuNode($sName, $oShortcut, $this->GetIndex(), $fRank++);
} }
// Complete the tree // Complete the tree
// //
parent::PopulateChildMenus(); parent::PopulateChildMenus();

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/NiceWebPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/NiceWebPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/PDFPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/PDFPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -74,7 +74,6 @@ abstract class Query extends cmdbAbstractObject
"default_value" => 0, "default_value" => 0,
"is_null_allowed" => false, "is_null_allowed" => false,
"depends_on" => array(), "depends_on" => array(),
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
))); )));
MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", array( MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", array(
@@ -83,7 +82,6 @@ abstract class Query extends cmdbAbstractObject
"default_value" => null, "default_value" => null,
"is_null_allowed" => true, "is_null_allowed" => true,
"depends_on" => array(), "depends_on" => array(),
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
))); )));
MetaModel::Init_AddAttribute(new AttributeExternalKey("export_last_user_id", MetaModel::Init_AddAttribute(new AttributeExternalKey("export_last_user_id",
@@ -95,16 +93,14 @@ abstract class Query extends cmdbAbstractObject
"depends_on"=>array(), "depends_on"=>array(),
"display_style"=>'select', "display_style"=>'select',
"always_load_in_tables"=>false, "always_load_in_tables"=>false,
"on_target_delete"=>DEL_SILENT, "on_target_delete"=>DEL_SILENT
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
))); )));
MetaModel::Init_AddAttribute(new AttributeExternalField("export_last_user_contact", MetaModel::Init_AddAttribute(new AttributeExternalField("export_last_user_contact",
array( array(
"allowed_values"=>null, "allowed_values"=>null,
"extkey_attcode"=> "export_last_user_id", "extkey_attcode"=> "export_last_user_id",
"target_attcode"=>"contactid", "target_attcode"=>"contactid"
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
))); )));
// Display lists // Display lists
@@ -296,7 +292,7 @@ class QueryOQL extends Query
} }
catch catch
(OQLException $e) { (OQLException $e) {
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc()) $oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
->SetIsClosable(false) ->SetIsClosable(false)
->SetIsCollapsible(false); ->SetIsCollapsible(false);
$oAlert->AddCSSClass('mb-5'); $oAlert->AddCSSClass('mb-5');

View File

@@ -15,7 +15,10 @@
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // 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\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.'core/cmdbobject.class.inc.php');
require_once(APPROOT.'application/utils.inc.php'); require_once(APPROOT.'application/utils.inc.php');
@@ -99,10 +102,7 @@ else
Session::Set('itop_env', ITOP_DEFAULT_ENV); Session::Set('itop_env', ITOP_DEFAULT_ENV);
} }
$sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE; $sConfigFile = APPCONF.$sEnv.'/'.ITOP_CONFIG_FILE;
try { MetaModel::Startup($sConfigFile, false /* $bModelOnly */, $bAllowCache, false /* $bTraceSourceFiles */, $sEnv);
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();
catch (MySQLException $e) { EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
IssueLog::Debug($e->getMessage());
throw new MySQLException('Could not connect to the DB server', []);
}

View File

@@ -163,11 +163,13 @@ class UIExtKeyWidget
$oPage->add_linked_script('../js/extkeywidget.js'); $oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js'); $oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_MODIFY) && $bAllowTargetCreation); $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true; $bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm'); $sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_'; $sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL()); $sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if ($this->bSearchMode) { if ($this->bSearchMode) {
$sWizHelper = 'null'; $sWizHelper = 'null';
@@ -975,10 +977,6 @@ HTML
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context // Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
FormHelper::DisableAttributeBlobInputs($this->sTargetClass, $aFormExtraParams); FormHelper::DisableAttributeBlobInputs($this->sTargetClass, $aFormExtraParams);
if(FormHelper::HasMandatoryAttributeBlobInputs($oNewObj)){
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE));
}
cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), $aFormExtraParams); cmdbAbstractObject::DisplayCreationForm($oPage, $this->sTargetClass, $oNewObj, array(), $aFormExtraParams);
$oPage->add(<<<HTML $oPage->add(<<<HTML
</div> </div>
@@ -1072,27 +1070,18 @@ JS
{ {
$oObj = MetaModel::NewObject($this->sTargetClass); $oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId); $aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0) { if (count($aErrors) == 0)
{
// Retrieve JSON data $oObj->DBInsert();
$sJSON = utils::ReadParam('json', '{}', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$oJSON = json_decode($sJSON);
$oObj->SetContextSection('temporary_objects', [
'create' => [
'transaction_id' => utils::ReadParam('root_transaction_id', '', false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID),
'host_class' => $oJSON->m_sClass,
'host_att_code' => $this->sAttCode,
],
]);
$oObj->DBInsertNoReload();
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey()); return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
} else { }
else
{
return array('error' => implode(' ', $aErrors), 'id' => 0); return array('error' => implode(' ', $aErrors), 'id' => 0);
} }
} }
catch (Exception $e) { catch(Exception $e)
{
return array('error' => $e->getMessage(), 'id' => 0); return array('error' => $e->getMessage(), 'id' => 0);
} }
} }

View File

@@ -71,16 +71,15 @@ class UIHTMLEditorWidget
// To change the default settings of the editor, // To change the default settings of the editor,
// a) edit the file /js/ckeditor/config.js // a) edit the file /js/ckeditor/config.js
// b) or override some of the configuration settings, using the second parameter of ckeditor() // b) or override some of the configuration settings, using the second parameter of ckeditor()
$sJSDefineWidth = '';
$aConfig = utils::GetCkeditorPref(); $aConfig = utils::GetCkeditorPref();
$sWidthSpec = addslashes(trim($this->m_oAttDef->GetWidth())); $sWidthSpec = addslashes(trim($this->m_oAttDef->GetWidth()));
if ($sWidthSpec != '') { if ($sWidthSpec != '')
/*N°6543 - the function min allow to keep text inside the column when width is defined*/ {
$aConfig['width'] = "min($sWidthSpec,100%)"; $aConfig['width'] = $sWidthSpec;
$sJSDefineWidth = '$("#cke_'.$iId.' iframe").contents().find("body").css("width", "'.$sWidthSpec.'")';
} }
$sHeightSpec = addslashes(trim($this->m_oAttDef->GetHeight())); $sHeightSpec = addslashes(trim($this->m_oAttDef->GetHeight()));
if ($sHeightSpec != '') { if ($sHeightSpec != '')
{
$aConfig['height'] = $sHeightSpec; $aConfig['height'] = $sHeightSpec;
} }
$sConfigJS = json_encode($aConfig); $sConfigJS = json_encode($aConfig);
@@ -111,7 +110,6 @@ $('#$iId').on('update', function(evt){
else else
{ {
oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled')); oMe.data('ckeditorInstance').setReadOnly(oMe.prop('disabled'));
$sJSDefineWidth
} }
}; };
setTimeout(delayedSetReadOnly, 50); setTimeout(delayedSetReadOnly, 50);

View File

@@ -143,10 +143,6 @@ JS
// Remove blob edition from creation form @see N°5863 to allow blob edition in modal context // Remove blob edition from creation form @see N°5863 to allow blob edition in modal context
FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams); FormHelper::DisableAttributeBlobInputs($sRealClass, $aFormExtraParams);
if(FormHelper::HasMandatoryAttributeBlobInputs($oObj)){
$oPage->AddUiBlock(FormHelper::GetAlertForMandatoryAttributeBlobInputsInModal(FormHelper::ENUM_MANDATORY_BLOB_MODE_CREATE));
}
cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aFormExtraParams); cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObj, array(), $aFormExtraParams);
} }

View File

@@ -178,18 +178,17 @@ class UILinksWidget
$oDisplayBlock = new DisplayBlock($oFilter, 'search', false); $oDisplayBlock = new DisplayBlock($oFilter, 'search', false);
$oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}", $oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}",
[ array(
'menu' => false, 'menu' => false,
'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}", 'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}",
'table_id' => "add_{$sLinkedSetId}", 'table_id' => "add_{$sLinkedSetId}",
'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}", 'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}",
'selection_mode' => true, 'selection_mode' => true,
'json' => $sJson, 'json' => $sJson,
'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix, 'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix,
'query_params' => $oFilter->GetInternalParams(), 'query_params' => $oFilter->GetInternalParams(),
'hidden_criteria' => $sAlreadyLinkedExpression, 'hidden_criteria' => $sAlreadyLinkedExpression,
'submit_on_load' => false, )));
]));
$oBlock->AddForm(); $oBlock->AddForm();
} }

View File

@@ -20,7 +20,6 @@
use Combodo\iTop\Application\Helper\Session; use Combodo\iTop\Application\Helper\Session;
use Combodo\iTop\Application\UI\Base\iUIBlock; use Combodo\iTop\Application\UI\Base\iUIBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Service\Module\ModuleService;
use ScssPhp\ScssPhp\Compiler; use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\OutputStyle; use ScssPhp\ScssPhp\OutputStyle;
use ScssPhp\ScssPhp\ValueConverter; use ScssPhp\ScssPhp\ValueConverter;
@@ -52,31 +51,22 @@ class utils
{ {
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer'; public const ENUM_SANITIZATION_FILTER_INTEGER = 'integer';
/** /**
* Datamodel class
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 update PHPDoc
* @uses MetaModel::IsValidClass()
*/ */
public const ENUM_SANITIZATION_FILTER_CLASS = 'class'; public const ENUM_SANITIZATION_FILTER_CLASS = 'class';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.4 3.1.1 3.2.0 N°6606 * @since 3.0.0
* @uses class_exists()
*/
public const ENUM_SANITIZATION_FILTER_PHP_CLASS = 'php_class';
/**
* @var string
* @since 2.7.10 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_STRING = 'string'; public const ENUM_SANITIZATION_FILTER_STRING = 'string';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param'; public const ENUM_SANITIZATION_FILTER_CONTEXT_PARAM = 'context_param';
/** /**
@@ -91,29 +81,24 @@ class utils
public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation'; public const ENUM_SANITIZATION_FILTER_OPERATION = 'operation';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter'; public const ENUM_SANITIZATION_FILTER_PARAMETER = 'parameter';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name'; public const ENUM_SANITIZATION_FILTER_FIELD_NAME = 'field_name';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id'; public const ENUM_SANITIZATION_FILTER_TRANSACTION_ID = 'transaction_id';
/** /**
* @var string For XML / HTML node identifiers * @var string For XML / HTML node identifiers
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier'; public const ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER = 'element_identifier';
/**
* @var string For XML / HTML node id/class selector
* @since 3.1.2 3.2.1
*/
public const ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR = 'element_selector';
/** /**
* @var string For variables names * @var string For variables names
* @since 3.0.0 * @since 3.0.0
@@ -121,13 +106,12 @@ class utils
public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name'; public const ENUM_SANITIZATION_FILTER_VARIABLE_NAME = 'variable_name';
/** /**
* @var string * @var string
* @since 2.7.10 3.0.0 * @since 3.0.0
*/ */
public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data'; public const ENUM_SANITIZATION_FILTER_RAW_DATA = 'raw_data';
/** /**
* @var string * @var string
* @since 3.0.2 3.1.0 N°4899 * @since 3.0.2, 3.1.0 N°4899
* @since 2.7.10 N°6606
*/ */
public const ENUM_SANITIZATION_FILTER_URL = 'url'; public const ENUM_SANITIZATION_FILTER_URL = 'url';
@@ -170,8 +154,6 @@ class utils
private static $iNextId = 0; private static $iNextId = 0;
private static $m_sAppRootUrl = null;
protected static function LoadParamFile($sParamFile) protected static function LoadParamFile($sParamFile)
{ {
if (!file_exists($sParamFile)) { if (!file_exists($sParamFile)) {
@@ -243,8 +225,13 @@ class utils
public static function IsModeCLI() public static function IsModeCLI()
{ {
$sCleanName = strtolower(trim(PHP_SAPI)); $sSAPIName = php_sapi_name();
return ($sCleanName === 'cli'); $sCleanName = strtolower(trim($sSAPIName));
if ($sCleanName == 'cli') {
return true;
} else {
return false;
}
} }
/** /**
@@ -367,13 +354,13 @@ class utils
} }
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
} }
public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter') public static function ReadPostedParam($sName, $defaultValue = '', $sSanitizationFilter = 'parameter')
{ {
$retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue; $retValue = isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter); return self::Sanitize($retValue, $defaultValue, $sSanitizationFilter);
} }
public static function Sanitize($value, $defaultValue, $sSanitizationFilter) public static function Sanitize($value, $defaultValue, $sSanitizationFilter)
{ {
if ($value === $defaultValue) if ($value === $defaultValue)
@@ -389,7 +376,7 @@ class utils
$retValue = $defaultValue; $retValue = $defaultValue;
} }
} }
return $retValue; return $retValue;
} }
/** /**
@@ -408,10 +395,6 @@ class utils
* @since 2.7.0 new 'element_identifier' filter * @since 2.7.0 new 'element_identifier' filter
* @since 3.0.0 new utils::ENUM_SANITIZATION_* const * @since 3.0.0 new utils::ENUM_SANITIZATION_* const
* @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter * @since 2.7.7, 3.0.2, 3.1.0 N°4899 - new 'url' filter
* @since 2.7.10 N°6606 use the utils::ENUM_SANITIZATION_* const
* @since 2.7.10 N°6606 new case for ENUM_SANITIZATION_FILTER_PHP_CLASS
*
* @link https://www.php.net/manual/en/filter.filters.sanitize.php PHP sanitization filters
*/ */
protected static function Sanitize_Internal($value, $sSanitizationFilter) protected static function Sanitize_Internal($value, $sSanitizationFilter)
{ {
@@ -432,13 +415,6 @@ class utils
$retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS); $retValue = filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
break; break;
case static::ENUM_SANITIZATION_FILTER_PHP_CLASS:
$retValue = $value;
if (!class_exists($value)) {
$retValue = false;
}
break;
case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM: case static::ENUM_SANITIZATION_FILTER_CONTEXT_PARAM:
case static::ENUM_SANITIZATION_FILTER_ROUTE: case static::ENUM_SANITIZATION_FILTER_ROUTE:
case static::ENUM_SANITIZATION_FILTER_OPERATION: case static::ENUM_SANITIZATION_FILTER_OPERATION:
@@ -494,17 +470,8 @@ class utils
} }
break; break;
// For XML / HTML node identifiers
case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER: case static::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER:
$retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value); $retValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $value);
$retValue = filter_var($retValue, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break;
// For XML / HTML node id selector
case static::ENUM_SANITIZATION_FILTER_ELEMENT_SELECTOR:
$retValue = filter_var($value, FILTER_VALIDATE_REGEXP,
['options' => ['regexp' => '/^[#\.][A-Za-z0-9][A-Za-z0-9_-]*$/']]);
break; break;
case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME: case static::ENUM_SANITIZATION_FILTER_VARIABLE_NAME:
@@ -513,8 +480,7 @@ class utils
// For URL // For URL
case static::ENUM_SANITIZATION_FILTER_URL: case static::ENUM_SANITIZATION_FILTER_URL:
// N°6350 - returns only valid URLs $retValue = filter_var($value, FILTER_SANITIZE_URL);
$retValue = filter_var($value, FILTER_VALIDATE_URL);
break; break;
default: default:
@@ -554,11 +520,11 @@ class utils
$sMimeType = self::GetFileMimeType($sTmpName); $sMimeType = self::GetFileMimeType($sTmpName);
$oDocument = new ormDocument($doc_content, $sMimeType, $sName); $oDocument = new ormDocument($doc_content, $sMimeType, $sName);
break; break;
case UPLOAD_ERR_NO_FILE: case UPLOAD_ERR_NO_FILE:
// no file to load, it's a normal case, just return an empty document // no file to load, it's a normal case, just return an empty document
break; break;
case UPLOAD_ERR_FORM_SIZE: case UPLOAD_ERR_FORM_SIZE:
case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_INI_SIZE:
throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize'))); throw new FileUploadException(Dict::Format('UI:Error:UploadedFileTooBig', ini_get('upload_max_filesize')));
@@ -567,7 +533,7 @@ class utils
case UPLOAD_ERR_PARTIAL: case UPLOAD_ERR_PARTIAL:
throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.')); throw new FileUploadException(Dict::S('UI:Error:UploadedFileTruncated.'));
break; break;
case UPLOAD_ERR_NO_TMP_DIR: case UPLOAD_ERR_NO_TMP_DIR:
throw new FileUploadException(Dict::S('UI:Error:NoTmpDir')); throw new FileUploadException(Dict::S('UI:Error:NoTmpDir'));
break; break;
@@ -580,7 +546,7 @@ class utils
$sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex]; $sName = is_null($sIndex) ? $aFileInfo['name'] : $aFileInfo['name'][$sIndex];
throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName)); throw new FileUploadException(Dict::Format('UI:Error:UploadStoppedByExtension_FileName', $sName));
break; break;
default: default:
throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError)); throw new FileUploadException(Dict::Format('UI:Error:UploadFailedUnknownCause_Code', $sError));
break; break;
@@ -686,17 +652,17 @@ class utils
return $aSelectedObj; return $aSelectedObj;
} }
public static function GetNewTransactionId() public static function GetNewTransactionId()
{ {
return privUITransaction::GetNewTransactionId(); return privUITransaction::GetNewTransactionId();
} }
public static function IsTransactionValid($sId, $bRemoveTransaction = true) public static function IsTransactionValid($sId, $bRemoveTransaction = true)
{ {
return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction); return privUITransaction::IsTransactionValid($sId, $bRemoveTransaction);
} }
public static function RemoveTransaction($sId) public static function RemoveTransaction($sId)
{ {
return privUITransaction::RemoveTransaction($sId); return privUITransaction::RemoveTransaction($sId);
@@ -880,9 +846,9 @@ class utils
$aDateTokens = array_keys($aSpec); $aDateTokens = array_keys($aSpec);
$aDateRegexps = array_values($aSpec); $aDateRegexps = array_values($aSpec);
} }
$sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat); $sDateRegexp = str_replace($aDateTokens, $aDateRegexps, $sFormat);
if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches)) if (preg_match('!^(?<head>)'.$sDateRegexp.'(?<tail>)$!', $sDate, $aMatches))
{ {
$sYear = isset($aMatches['year']) ? $aMatches['year'] : 0; $sYear = isset($aMatches['year']) ? $aMatches['year'] : 0;
@@ -899,7 +865,7 @@ class utils
} }
// http://www.spaweditor.com/scripts/regex/index.php // http://www.spaweditor.com/scripts/regex/index.php
} }
/** /**
* Convert an old date/time format specification (using % placeholders) * Convert an old date/time format specification (using % placeholders)
* to a format compatible with DateTime::createFromFormat * to a format compatible with DateTime::createFromFormat
@@ -1057,7 +1023,7 @@ class utils
*/ */
public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false) public static function GetAbsoluteUrlAppRoot($bForceTrustProxy = false)
{ {
$sUrl = static::$m_sAppRootUrl; static $sUrl = null;
if ($sUrl === null || $bForceTrustProxy) if ($sUrl === null || $bForceTrustProxy)
{ {
$sUrl = self::GetConfig()->Get('app_root_url'); $sUrl = self::GetConfig()->Get('app_root_url');
@@ -1078,9 +1044,8 @@ class utils
} }
$sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl); $sUrl = str_replace(SERVER_NAME_PLACEHOLDER, $sServerName, $sUrl);
} }
static::$m_sAppRootUrl = $sUrl;
} }
return static::$m_sAppRootUrl; return $sUrl;
} }
/** /**
@@ -1431,23 +1396,13 @@ class utils
return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/'; return APPROOT . 'env-' . MetaModel::GetEnvironment() . '/';
} }
/**
* @return string A path to the folder into which data can be written
* @internal
* @since N°6097 2.7.10 3.0.4 3.1.1
*/
public static function GetDataPath(): string
{
return APPROOT.'data/';
}
/** /**
* @return string A path to a folder into which any module can store cache data * @return string A path to a folder into which any module can store cache data
* The corresponding folder is created or cleaned upon code compilation * The corresponding folder is created or cleaned upon code compilation
*/ */
public static function GetCachePath() public static function GetCachePath()
{ {
return static::GetDataPath().'cache-'.MetaModel::GetEnvironment().'/'; return APPROOT.'data/cache-'.MetaModel::GetEnvironment().'/';
} }
/** /**
@@ -1497,7 +1452,7 @@ class utils
$aResult = []; $aResult = [];
// 1st - add standard built-in menu items // 1st - add standard built-in menu items
// //
switch($iMenuId) switch($iMenuId)
{ {
case iPopupMenuExtension::MENU_OBJLIST_ACTIONS: case iPopupMenuExtension::MENU_OBJLIST_ACTIONS:
@@ -1881,7 +1836,7 @@ SQL;
return $sProposed; return $sProposed;
} }
} }
/** /**
* Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore) * Some characters cause troubles with jQuery when used inside DOM IDs, so let's replace them by the safe _ (underscore)
* @param string $sId The ID to sanitize * @param string $sId The ID to sanitize
@@ -1891,7 +1846,7 @@ SQL;
{ {
return str_replace(array(':', '[', ']', '+', '-', ' '), '_', $sId); return str_replace(array(':', '[', ']', '+', '-', ' '), '_', $sId);
} }
/** /**
* Helper to execute an HTTP POST request, uses CURL PHP extension * Helper to execute an HTTP POST request, uses CURL PHP extension
* *
@@ -1976,7 +1931,7 @@ SQL;
/** /**
* Get a standard list of character sets * Get a standard list of character sets
* *
* @param array $aAdditionalEncodings Additional values * @param array $aAdditionalEncodings Additional values
* @return array of iconv code => english label, sorted by label * @return array of iconv code => english label, sorted by label
*/ */
@@ -2015,7 +1970,6 @@ SQL;
/** /**
* @param string $sValue * @param string $sValue
* @param bool $bDoubleEncode Whether to double encode the value or not
* *
* @return string passed value with only characters having a special meaning in HTML escaped as entities * @return string passed value with only characters having a special meaning in HTML escaped as entities
* Since 3.0.0 we were using for this {@link HtmlEntities} but it was overkill and leads to double escaping ! * Since 3.0.0 we were using for this {@link HtmlEntities} but it was overkill and leads to double escaping !
@@ -2023,15 +1977,14 @@ SQL;
* @uses \htmlspecialchars() * @uses \htmlspecialchars()
* @link https://www.php.net/manual/fr/function.htmlspecialchars.php * @link https://www.php.net/manual/fr/function.htmlspecialchars.php
* @since 3.0.0 N°3623 * @since 3.0.0 N°3623
* @since 3.1.0 N°6405 Add $bDoubleEncode parameter
*/ */
public static function EscapeHtml($sValue, bool $bDoubleEncode = false) public static function EscapeHtml($sValue)
{ {
return htmlspecialchars( return htmlspecialchars(
$sValue ?? '', $sValue ?? '',
ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5,
WebPage::PAGES_CHARSET, WebPage::PAGES_CHARSET,
$bDoubleEncode false
); );
} }
@@ -2083,7 +2036,7 @@ SQL;
return $e->getMessage(); return $e->getMessage();
} }
} }
/** /**
* Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags * Convert (?) plain text to some HTML markup by replacing newlines by <br/> tags
* and escaping HTML entities * and escaping HTML entities
@@ -2092,15 +2045,12 @@ SQL;
*/ */
public static function TextToHtml($sText) public static function TextToHtml($sText)
{ {
if (static::IsNullOrEmptyString($sText)){
return '';
}
$sText = str_replace("\r\n", "\n", $sText); $sText = str_replace("\r\n", "\n", $sText);
$sText = str_replace("\r", "\n", $sText); $sText = str_replace("\r", "\n", $sText);
return str_replace("\n", '<br/>', utils::EscapeHtml($sText)); return str_replace("\n", '<br/>', utils::EscapeHtml($sText));
} }
/** /**
* Eventually compiles the SASS (.scss) file into the CSS (.css) file * Eventually compiles the SASS (.scss) file into the CSS (.css) file
* *
@@ -2214,7 +2164,7 @@ SQL;
case 'image/png': case 'image/png':
$img = @imagecreatefromstring($oImage->GetData()); $img = @imagecreatefromstring($oImage->GetData());
break; break;
default: default:
// Unsupported image type, return the image as-is // Unsupported image type, return the image as-is
//throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used."); //throw new Exception("Unsupported image type: '".$oImage->GetMimeType()."'. Cannot resize the image, original image will be used.");
@@ -2228,14 +2178,14 @@ SQL;
else else
{ {
// Let's scale the image, preserving the transparency for GIFs and PNGs // Let's scale the image, preserving the transparency for GIFs and PNGs
$fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight); $fScale = min($iMaxImageWidth / $iWidth, $iMaxImageHeight / $iHeight);
$iNewWidth = $iWidth * $fScale; $iNewWidth = $iWidth * $fScale;
$iNewHeight = $iHeight * $fScale; $iNewHeight = $iHeight * $fScale;
$new = imagecreatetruecolor($iNewWidth, $iNewHeight); $new = imagecreatetruecolor($iNewWidth, $iNewHeight);
// Preserve transparency // Preserve transparency
if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png")) if(($oImage->GetMimeType() == "image/gif") || ($oImage->GetMimeType() == "image/png"))
{ {
@@ -2243,38 +2193,38 @@ SQL;
imagealphablending($new, false); imagealphablending($new, false);
imagesavealpha($new, true); imagesavealpha($new, true);
} }
imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight); imagecopyresampled($new, $img, 0, 0, 0, 0, $iNewWidth, $iNewHeight, $iWidth, $iHeight);
ob_start(); ob_start();
switch ($oImage->GetMimeType()) switch ($oImage->GetMimeType())
{ {
case 'image/gif': case 'image/gif':
imagegif($new); // send image to output buffer imagegif($new); // send image to output buffer
break; break;
case 'image/jpeg': case 'image/jpeg':
imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality imagejpeg($new, null, 80); // null = send image to output buffer, 80 = good quality
break; break;
case 'image/png': case 'image/png':
imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression imagepng($new, null, 5); // null = send image to output buffer, 5 = medium compression
break; break;
} }
$oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName()); $oResampledImage = new ormDocument(ob_get_contents(), $oImage->GetMimeType(), $oImage->GetFileName());
@ob_end_clean(); @ob_end_clean();
imagedestroy($img); imagedestroy($img);
imagedestroy($new); imagedestroy($new);
return $oResampledImage; return $oResampledImage;
} }
} }
/** /**
* Create a 128 bit UUID in the format: {########-####-####-####-############} * Create a 128 bit UUID in the format: {########-####-####-####-############}
* *
* Note: this method can be run from the command line as well as from the web server. * Note: this method can be run from the command line as well as from the web server.
* Note2: this method is not cryptographically secure! If you need a cryptographically secure value * Note2: this method is not cryptographically secure! If you need a cryptographically secure value
* consider using open_ssl or PHP 7 methods. * consider using open_ssl or PHP 7 methods.
@@ -2310,9 +2260,26 @@ SQL;
*/ */
public static function GetCurrentModuleName($iCallDepth = 0) public static function GetCurrentModuleName($iCallDepth = 0)
{ {
return ModuleService::GetInstance()->GetCurrentModuleName($iCallDepth + 1); $sCurrentModuleName = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleName = $sModuleName;
break;
}
}
}
return $sCurrentModuleName;
} }
/** /**
* **Warning** : returned result can be invalid as we're using backtrace to find the module dir name * **Warning** : returned result can be invalid as we're using backtrace to find the module dir name
* *
@@ -2332,7 +2299,24 @@ SQL;
*/ */
public static function GetCurrentModuleDir($iCallDepth) public static function GetCurrentModuleDir($iCallDepth)
{ {
return ModuleService::GetInstance()->GetCurrentModuleDir($iCallDepth); $sCurrentModuleDir = '';
$aCallStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$sCallerFile = realpath($aCallStack[$iCallDepth]['file']);
foreach(GetModulesInfo() as $sModuleName => $aInfo)
{
if ($aInfo['root_dir'] !== '')
{
$sRootDir = realpath(APPROOT.$aInfo['root_dir']);
if(substr($sCallerFile, 0, strlen($sRootDir)) === $sRootDir)
{
$sCurrentModuleDir = basename($sRootDir);
break;
}
}
}
return $sCurrentModuleDir;
} }
/** /**
@@ -2347,9 +2331,14 @@ SQL;
*/ */
public static function GetCurrentModuleUrl() public static function GetCurrentModuleUrl()
{ {
return ModuleService::GetInstance()->GetCurrentModuleUrl(1); $sDir = static::GetCurrentModuleDir(1);
if ( $sDir !== '')
{
return static::GetAbsoluteUrlModulesRoot().'/'.$sDir;
}
return '';
} }
/** /**
* @param string $sProperty The name of the property to retrieve * @param string $sProperty The name of the property to retrieve
* @param mixed $defaultvalue * @param mixed $defaultvalue
@@ -2357,18 +2346,24 @@ SQL;
*/ */
public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null) public static function GetCurrentModuleSetting($sProperty, $defaultvalue = null)
{ {
return ModuleService::GetInstance()->GetCurrentModuleSetting($sProperty, $defaultvalue); $sModuleName = static::GetCurrentModuleName(1);
return MetaModel::GetModuleSetting($sModuleName, $sProperty, $defaultvalue);
} }
/** /**
* @param string $sModuleName * @param string $sModuleName
* @return string|NULL compiled version of a given module, as it was seen by the compiler * @return string|NULL compiled version of a given module, as it was seen by the compiler
*/ */
public static function GetCompiledModuleVersion($sModuleName) public static function GetCompiledModuleVersion($sModuleName)
{ {
return ModuleService::GetInstance()->GetCompiledModuleVersion($sModuleName); $aModulesInfo = GetModulesInfo();
if (array_key_exists($sModuleName, $aModulesInfo))
{
return $aModulesInfo[$sModuleName]['version'];
}
return null;
} }
/** /**
* Check if the given path/url is an http(s) URL * Check if the given path/url is an http(s) URL
* @param string $sPath * @param string $sPath
@@ -2383,7 +2378,7 @@ SQL;
} }
return $bRet; return $bRet;
} }
/** /**
* Check if the given URL is a link to download a document/image on the CURRENT iTop * Check if the given URL is a link to download a document/image on the CURRENT iTop
* In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument * In such a case we can read the content of the file directly in the database (if the users rights allow) and return the ormDocument
@@ -2432,7 +2427,7 @@ SQL;
} }
return $result; return $result;
} }
/** /**
* Read the content of a file (and retrieve its MIME type) from either: * Read the content of a file (and retrieve its MIME type) from either:
* - an URL pointing to a blob (image/document) on the current iTop server * - an URL pointing to a blob (image/document) on the current iTop server
@@ -2476,7 +2471,7 @@ SQL;
'html' => 'text/html', 'html' => 'text/html',
'exe' => 'application/octet-stream', 'exe' => 'application/octet-stream',
); );
$sData = null; $sData = null;
$sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters... $sMimeType = 'text/plain'; // Default MIME Type: treat the file as a bunch a characters...
$sFileName = 'uploaded-file'; // Default name for downloaded-files $sFileName = 'uploaded-file'; // Default name for downloaded-files
@@ -2508,17 +2503,15 @@ SQL;
$aHeaders = static::ParseHeaders($http_response_header); $aHeaders = static::ParseHeaders($http_response_header);
$sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream'; $sMimeType = array_key_exists('Content-Type', $aHeaders) ? strtolower($aHeaders['Content-Type']) : 'application/x-octet-stream';
// Compute the file extension from the MIME Type // Compute the file extension from the MIME Type
foreach ($aKnownExtensions as $sExtValue => $sMime) { foreach($aKnownExtensions as $sExtValue => $sMime)
if ($sMime === $sMimeType) { {
if ($sMime === $sMimeType)
{
$sExtension = '.'.$sExtValue; $sExtension = '.'.$sExtValue;
break; break;
} }
} }
} }
$sPathName = pathinfo($sPath, PATHINFO_FILENAME);
if (utils::IsNotNullOrEmptyString($sPathName)) {
$sFileName = $sPathName;
}
$sFileName .= $sExtension; $sFileName .= $sExtension;
} }
$oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName); $oUploadedDoc = new ormDocument($sData, $sMimeType, $sFileName);
@@ -2534,7 +2527,7 @@ SQL;
} }
$sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION)); $sExtension = strtolower(pathinfo($sPath, PATHINFO_EXTENSION));
$sFileName = basename($sPath); $sFileName = basename($sPath);
if (array_key_exists($sExtension, $aKnownExtensions)) if (array_key_exists($sExtension, $aKnownExtensions))
{ {
$sMimeType = $aKnownExtensions[$sExtension]; $sMimeType = $aKnownExtensions[$sExtension];
@@ -2548,7 +2541,7 @@ SQL;
} }
return $oUploadedDoc; return $oUploadedDoc;
} }
protected static function ParseHeaders($aHeaders) protected static function ParseHeaders($aHeaders)
{ {
$aCleanHeaders = array(); $aCleanHeaders = array();
@@ -2573,7 +2566,7 @@ SQL;
} }
return $aCleanHeaders; return $aCleanHeaders;
} }
/** /**
* @return string a string based on compilation time or (if not available because the datamodel has not been loaded) * @return string a string based on compilation time or (if not available because the datamodel has not been loaded)
* the version of iTop. This string is useful to prevent browser side caching of content that may vary at each * the version of iTop. This string is useful to prevent browser side caching of content that may vary at each
@@ -2693,26 +2686,24 @@ SQL;
} }
/** /**
* Returns the local path relative to the iTop installation (APPROOT or the given base path) * Returns the local path relative to the iTop installation of an existing file
* Dir separator is changed to '/' for consistency among the different OS * Dir separator is changed to '/' for consistency among the different OS
* *
* @param string $sAbsolutePath absolute path * @param string $sAbsolutePath absolute path
* @param string $sBasePath Base path for the resulting local path (default APPROOT)
* *
* @return false|string The generated local path or false if absolute path is not under the base path * @return false|string
* @since 3.1.1 Added base path defaulted to previous version APPROOT
*/ */
final public static function LocalPath($sAbsolutePath, string $sBasePath = APPROOT) final public static function LocalPath($sAbsolutePath)
{ {
$sRootPath = realpath($sBasePath); $sRootPath = realpath(APPROOT);
$sFullPath = realpath($sAbsolutePath); $sFullPath = realpath($sAbsolutePath);
if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath)) if (($sFullPath === false) || !self::StartsWith($sFullPath, $sRootPath))
{ {
return false; return false;
} }
$sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR)); $sLocalPath = substr($sFullPath, strlen($sRootPath.DIRECTORY_SEPARATOR));
$sLocalPath = str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath);
return str_replace(DIRECTORY_SEPARATOR, '/', $sLocalPath); return $sLocalPath;
} }
/** /**
@@ -2904,7 +2895,7 @@ HTML;
// Add already loaded classes // Add already loaded classes
$aCurrentClasses = array_fill_keys(get_declared_classes(), ''); $aCurrentClasses = array_fill_keys(get_declared_classes(), '');
$aClassMap = array_merge($aCurrentClasses, $aClassMap); $aClassMap = array_merge($aClassMap, $aCurrentClasses);
foreach ($aClassMap as $sPHPClass => $sPHPFile) { foreach ($aClassMap as $sPHPClass => $sPHPFile) {
$bSkipped = false; $bSkipped = false;
@@ -2913,8 +2904,7 @@ HTML;
if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) { if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) {
$bSkipped = true; $bSkipped = true;
} }
// For some PHP classes we don't have their file path as they are already in memory, so we never filter on their paths else {
elseif (utils::IsNotNullOrEmptyString($sPHPFile)) {
$sPHPFile = self::LocalPath($sPHPFile); $sPHPFile = self::LocalPath($sPHPFile);
if ($sPHPFile !== false) { if ($sPHPFile !== false) {
$sPHPFile = '/'.$sPHPFile; // for regex $sPHPFile = '/'.$sPHPFile; // for regex
@@ -2929,12 +2919,11 @@ HTML;
$bSkipped = true; // file not found $bSkipped = true; // file not found
} }
} }
if(!$bSkipped){ if(!$bSkipped){
try { try {
$oRefClass = new ReflectionClass($sPHPClass); $oRefClass = new ReflectionClass($sPHPClass);
if ($oRefClass->implementsInterface($sInterface) && if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) {
!$oRefClass->isInterface() && !$oRefClass->isAbstract() && !$oRefClass->isTrait()) {
$aMatchingClasses[] = $sPHPClass; $aMatchingClasses[] = $sPHPClass;
} }
} catch (Exception $e) { } catch (Exception $e) {
@@ -3040,7 +3029,6 @@ HTML;
* *
* @return bool if string null or empty * @return bool if string null or empty
* @since 3.0.2 N°5302 * @since 3.0.2 N°5302
* @since 2.7.10 N°6458 add method in the 2.7 branch
*/ */
public static function IsNullOrEmptyString(?string $sString): bool public static function IsNullOrEmptyString(?string $sString): bool
{ {
@@ -3056,7 +3044,6 @@ HTML;
* *
* @return bool if string is not null and not empty * @return bool if string is not null and not empty
* @since 3.0.2 N°5302 * @since 3.0.2 N°5302
* @since 2.7.10 N°6458 add method in the 2.7 branch
*/ */
public static function IsNotNullOrEmptyString(?string $sString): bool public static function IsNotNullOrEmptyString(?string $sString): bool
{ {
@@ -3381,22 +3368,5 @@ HTML;
{ {
return in_array($sTrait, self::TraitsUsedByClass($sClass, true)); return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
} }
/**
* Get stack trace as string array.
*
* @return array
* @since 3.1.0
*/
public static function GetStackTraceAsArray(): array
{
$e = new Exception();
$aTrace = explode("\n", $e->getTraceAsString());
// Remove call to this method
array_shift($aTrace);
// Remove Main
array_pop($aTrace);
return $aTrace;
}
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/WebPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/WebPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -351,7 +351,6 @@ class WizardHelper
/** /**
* @return string JS code to be executed for fields update * @return string JS code to be executed for fields update
* @since 3.0.0 N°3198 * @since 3.0.0 N°3198
* @deprecated 3.0.3-2 3.0.4 3.1.1 3.2.0 Use {@see \WizardHelper::AddJsForUpdateFields()} instead
*/ */
public function GetJsForUpdateFields() public function GetJsForUpdateFields()
{ {
@@ -364,32 +363,6 @@ class WizardHelper
JS; JS;
} }
/**
* Add necessary JS snippets (to the page) to be executed for fields update
*
* @param \WebPage $oPage
* @return void
* @since 3.0.3-2 3.0.4 3.1.1 3.2.0 N°6766
*/
public function AddJsForUpdateFields(WebPage $oPage)
{
$sWizardHelperJsVar = (!is_null($this->m_aData['m_sWizHelperJsVarName'])) ? utils::Sanitize($this->m_aData['m_sWizHelperJsVarName'], '', utils::ENUM_SANITIZATION_FILTER_PARAMETER) : 'oWizardHelper'.$this->GetFormPrefix();
$sWizardHelperJson = $this->ToJSON();
$oPage->add_script(<<<JS
{$sWizardHelperJsVar}.m_oData = {$sWizardHelperJson};
{$sWizardHelperJsVar}.UpdateFields();
JS
);
$oPage->add_ready_script(<<<JS
if ({$sWizardHelperJsVar}.m_oDependenciesUpdatedPromiseResolve !== null){
{$sWizardHelperJsVar}.m_oDependenciesUpdatedPromiseResolve();
}
JS
);
}
/* /*
* Function with an old pattern of code * Function with an old pattern of code
* @deprecated 3.1.0 * @deprecated 3.1.0
@@ -398,9 +371,11 @@ JS
{ {
$aSet = json_decode($sJsonSet, true); // true means hash array instead of object $aSet = json_decode($sJsonSet, true); // true means hash array instead of object
$oSet = CMDBObjectSet::FromScratch($sLinkClass); $oSet = CMDBObjectSet::FromScratch($sLinkClass);
foreach ($aSet as $aLinkObj) { foreach ($aSet as $aLinkObj)
{
$oLink = MetaModel::NewObject($sLinkClass); $oLink = MetaModel::NewObject($sLinkClass);
foreach ($aLinkObj as $sAttCode => $value) { foreach ($aLinkObj as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0)) if (($oAttDef->IsExternalKey()) && ($value != '') && ($value > 0))
{ {

View File

@@ -1,6 +1,6 @@
<?php <?php
/** /**
* @deprecated 3.0.0 will be removed in 3.1.0 - moved to sources/Application/WebPage/XMLPage.php, now loadable using autoloader * @deprecated will be removed in 3.1.0 - moved to sources/Application/WebPage/XMLPage.php, now loadable using autoloader
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
* @copyright Copyright (C) 2010-2023 Combodo SARL * @copyright Copyright (C) 2010-2023 Combodo SARL
*/ */

View File

@@ -23,13 +23,11 @@ define('ITOP_DESIGN_LATEST_VERSION', '3.1');
* @used-by utils::GetItopVersionWikiSyntax() * @used-by utils::GetItopVersionWikiSyntax()
* @used-by iTopModulesPhpVersionIntegrationTest * @used-by iTopModulesPhpVersionIntegrationTest
*/ */
define('ITOP_CORE_VERSION', '3.1.2'); define('ITOP_CORE_VERSION', '3.1.0');
/** /**
* @var string * @since 3.0.4 N°6274 Allow to test if PHPUnit is currently running. Starting with PHPUnit 9.5 we'll be able to replace it with $GLOBALS['phpunit_version']
* @since 3.0.4 3.1.0 3.2.0 N°6274 Allow to test if PHPUnit is currently running. Starting with PHPUnit 9.5 we'll be able to replace it with $GLOBALS['phpunit_version']
* @since 3.0.4 3.1.1 3.2.0 N°6976 Fix constant name (DeprecatedCallsLog error handler was never set)
*/ */
const ITOP_PHPUNIT_RUNNING_CONSTANT_NAME = 'ITOP_PHPUNIT_RUNNING'; define('ITOP_PHPUNIT_RUNNING_CONSTANT_NAME', 'ITOP_PHPUNIT_RUNNING');
require_once APPROOT.'bootstrap.inc.php'; require_once APPROOT.'bootstrap.inc.php';

View File

@@ -45,7 +45,6 @@ define('MAINTENANCE_MODE_FILE', APPROOT.'data/.maintenance');
define('READONLY_MODE_FILE', APPROOT.'data/.readonly'); define('READONLY_MODE_FILE', APPROOT.'data/.readonly');
$fItopStarted = microtime(true); $fItopStarted = microtime(true);
$iItopInitialMemory = memory_get_usage(true);
if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) { if (!isset($GLOBALS['bBypassAutoload']) || $GLOBALS['bBypassAutoload'] == false) {
require_once APPROOT.'/lib/autoload.php'; require_once APPROOT.'/lib/autoload.php';
@@ -68,7 +67,7 @@ if (file_exists(MAINTENANCE_MODE_FILE) && !$bBypassMaintenance)
http_response_code(503); http_response_code(503);
// Display message depending on the request // Display message depending on the request
include(APPROOT.'application/maintenancemsg.php'); include(APPROOT.'application/maintenancemsg.php');
$sSAPIName = strtoupper(trim(PHP_SAPI)); $sSAPIName = strtoupper(trim(php_sapi_name()));
switch (true) switch (true)
{ {

View File

@@ -12,10 +12,10 @@
"ext-json": "*", "ext-json": "*",
"ext-mysqli": "*", "ext-mysqli": "*",
"ext-soap": "*", "ext-soap": "*",
"apereo/phpcas": "~1.6.0", "apereo/phpcas" : "~1.6.0",
"combodo/tcpdf": "~6.4.4", "combodo/tcpdf": "~6.4.4",
"firebase/php-jwt": "~6.4.0", "firebase/php-jwt": "~6.4.0",
"guzzlehttp/guzzle": "^7.5.1", "guzzlehttp/guzzle": "^7.4.5",
"laminas/laminas-mail": "^2.11", "laminas/laminas-mail": "^2.11",
"laminas/laminas-servicemanager": "^3.5", "laminas/laminas-servicemanager": "^3.5",
"league/oauth2-google": "^3.0", "league/oauth2-google": "^3.0",

150
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cb3883f141f50e1bfbc957880ab93be1", "content-hash": "69db9dbdea61a588fa2058724d91a579",
"packages": [ "packages": [
{ {
"name": "apereo/phpcas", "name": "apereo/phpcas",
@@ -217,22 +217,22 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.7.0", "version": "7.4.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
"reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0", "guzzlehttp/promises": "^1.5",
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "guzzlehttp/psr7": "^1.9 || ^2.4",
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0" "symfony/deprecation-contracts": "^2.2 || ^3.0"
@@ -241,11 +241,10 @@
"psr/http-client-implementation": "1.0" "psr/http-client-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.4.1",
"ext-curl": "*", "ext-curl": "*",
"php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/client-integration-tests": "^3.0",
"php-http/message-factory": "^1.1", "phpunit/phpunit": "^8.5.5 || ^9.3.5",
"phpunit/phpunit": "^8.5.29 || ^9.5.23",
"psr/log": "^1.1 || ^2.0 || ^3.0" "psr/log": "^1.1 || ^2.0 || ^3.0"
}, },
"suggest": { "suggest": {
@@ -255,9 +254,8 @@
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "branch-alias": {
"bin-links": true, "dev-master": "7.4-dev"
"forward-command": false
} }
}, },
"autoload": { "autoload": {
@@ -323,7 +321,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.7.0" "source": "https://github.com/guzzle/guzzle/tree/7.4.5"
}, },
"funding": [ "funding": [
{ {
@@ -339,37 +337,38 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-21T14:04:53+00:00" "time": "2022-06-20T22:16:13+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
"version": "2.0.0", "version": "1.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/promises.git", "url": "https://github.com/guzzle/promises.git",
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2.5 || ^8.0" "php": ">=5.5"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "symfony/phpunit-bridge": "^4.4 || ^5.1"
"phpunit/phpunit": "^8.5.29 || ^9.5.23"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "branch-alias": {
"bin-links": true, "dev-master": "1.5-dev"
"forward-command": false
} }
}, },
"autoload": { "autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": { "psr-4": {
"GuzzleHttp\\Promise\\": "src/" "GuzzleHttp\\Promise\\": "src/"
} }
@@ -406,7 +405,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/promises/issues", "issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.0" "source": "https://github.com/guzzle/promises/tree/1.5.1"
}, },
"funding": [ "funding": [
{ {
@@ -422,26 +421,26 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-21T13:50:22+00:00" "time": "2021-10-22T20:56:57+00:00"
}, },
{ {
"name": "guzzlehttp/psr7", "name": "guzzlehttp/psr7",
"version": "2.5.0", "version": "2.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/psr7.git", "url": "https://github.com/guzzle/psr7.git",
"reference": "b635f279edd83fc275f822a1188157ffea568ff6" "reference": "13388f00956b1503577598873fffb5ae994b5737"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737",
"reference": "b635f279edd83fc275f822a1188157ffea568ff6", "reference": "13388f00956b1503577598873fffb5ae994b5737",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2.5 || ^8.0", "php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0", "psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0", "psr/http-message": "^1.0",
"ralouphie/getallheaders": "^3.0" "ralouphie/getallheaders": "^3.0"
}, },
"provide": { "provide": {
@@ -449,18 +448,17 @@
"psr/http-message-implementation": "1.0" "psr/http-message-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"bamarni/composer-bin-plugin": "^1.8.1", "bamarni/composer-bin-plugin": "^1.4.1",
"http-interop/http-factory-tests": "^0.9", "http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^8.5.29 || ^9.5.23" "phpunit/phpunit": "^8.5.8 || ^9.3.10"
}, },
"suggest": { "suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"bamarni-bin": { "branch-alias": {
"bin-links": true, "dev-master": "2.4-dev"
"forward-command": false
} }
}, },
"autoload": { "autoload": {
@@ -522,7 +520,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/psr7/issues", "issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.5.0" "source": "https://github.com/guzzle/psr7/tree/2.4.0"
}, },
"funding": [ "funding": [
{ {
@@ -538,7 +536,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-04-17T16:11:26+00:00" "time": "2022-06-20T21:43:11+00:00"
}, },
{ {
"name": "laminas/laminas-loader", "name": "laminas/laminas-loader",
@@ -1670,21 +1668,21 @@
}, },
{ {
"name": "psr/http-client", "name": "psr/http-client",
"version": "1.0.2", "version": "1.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-client.git", "url": "https://github.com/php-fig/http-client.git",
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.0 || ^8.0", "php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0" "psr/http-message": "^1.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@@ -1704,7 +1702,7 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "https://www.php-fig.org/" "homepage": "http://www.php-fig.org/"
} }
], ],
"description": "Common interface for HTTP clients", "description": "Common interface for HTTP clients",
@@ -1716,27 +1714,27 @@
"psr-18" "psr-18"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-client/tree/1.0.2" "source": "https://github.com/php-fig/http-client/tree/master"
}, },
"time": "2023-04-10T20:12:12+00:00" "time": "2020-06-29T06:28:15+00:00"
}, },
{ {
"name": "psr/http-factory", "name": "psr/http-factory",
"version": "1.0.2", "version": "1.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-factory.git", "url": "https://github.com/php-fig/http-factory.git",
"reference": "e616d01114759c4c489f93b099585439f795fe35" "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"reference": "e616d01114759c4c489f93b099585439f795fe35", "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.0.0",
"psr/http-message": "^1.0 || ^2.0" "psr/http-message": "^1.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
@@ -1756,7 +1754,7 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "https://www.php-fig.org/" "homepage": "http://www.php-fig.org/"
} }
], ],
"description": "Common interfaces for PSR-7 HTTP message factories", "description": "Common interfaces for PSR-7 HTTP message factories",
@@ -1771,31 +1769,31 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-factory/tree/1.0.2" "source": "https://github.com/php-fig/http-factory/tree/master"
}, },
"time": "2023-04-10T20:10:41+00:00" "time": "2019-04-30T12:38:16+00:00"
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "2.0", "version": "1.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-message.git", "url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0" "php": ">=5.3.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.0.x-dev" "dev-master": "1.0.x-dev"
} }
}, },
"autoload": { "autoload": {
@@ -1810,7 +1808,7 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "https://www.php-fig.org/" "homepage": "http://www.php-fig.org/"
} }
], ],
"description": "Common interface for HTTP messages", "description": "Common interface for HTTP messages",
@@ -1824,9 +1822,9 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-message/tree/2.0" "source": "https://github.com/php-fig/http-message/tree/master"
}, },
"time": "2023-04-04T09:54:51+00:00" "time": "2016-08-06T14:39:51+00:00"
}, },
{ {
"name": "psr/log", "name": "psr/log",
@@ -4476,16 +4474,16 @@
}, },
{ {
"name": "symfony/twig-bridge", "name": "symfony/twig-bridge",
"version": "v5.4.31", "version": "v5.4.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/twig-bridge.git", "url": "https://github.com/symfony/twig-bridge.git",
"reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942" "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/twig-bridge/zipball/fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/63b8a50d48c9fe3d04e77307d4f1771dd848baa8",
"reference": "fc6ee0a3b672ea12ca1f26592d257bfc7f4ee942", "reference": "63b8a50d48c9fe3d04e77307d4f1771dd848baa8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -4498,22 +4496,22 @@
"phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0", "phpdocumentor/type-resolver": "<1.4.0",
"symfony/console": "<5.3", "symfony/console": "<5.3",
"symfony/form": "<5.4.21|>=6,<6.2.7", "symfony/form": "<5.3",
"symfony/http-foundation": "<5.3", "symfony/http-foundation": "<5.3",
"symfony/http-kernel": "<4.4", "symfony/http-kernel": "<4.4",
"symfony/translation": "<5.2", "symfony/translation": "<5.2",
"symfony/workflow": "<5.2" "symfony/workflow": "<5.2"
}, },
"require-dev": { "require-dev": {
"doctrine/annotations": "^1.12|^2", "doctrine/annotations": "^1.12",
"egulias/email-validator": "^2.1.10|^3|^4", "egulias/email-validator": "^2.1.10|^3",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/asset": "^4.4|^5.0|^6.0", "symfony/asset": "^4.4|^5.0|^6.0",
"symfony/console": "^5.3|^6.0", "symfony/console": "^5.3|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0", "symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/finder": "^4.4|^5.0|^6.0", "symfony/finder": "^4.4|^5.0|^6.0",
"symfony/form": "^5.4.21|^6.2.7", "symfony/form": "^5.3|^6.0",
"symfony/http-foundation": "^5.3|^6.0", "symfony/http-foundation": "^5.3|^6.0",
"symfony/http-kernel": "^4.4|^5.0|^6.0", "symfony/http-kernel": "^4.4|^5.0|^6.0",
"symfony/intl": "^4.4|^5.0|^6.0", "symfony/intl": "^4.4|^5.0|^6.0",
@@ -4577,7 +4575,7 @@
"description": "Provides integration for Twig with various Symfony components", "description": "Provides integration for Twig with various Symfony components",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/twig-bridge/tree/v5.4.31" "source": "https://github.com/symfony/twig-bridge/tree/v5.4.11"
}, },
"funding": [ "funding": [
{ {
@@ -4593,7 +4591,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-11-09T21:19:08+00:00" "time": "2022-07-20T13:00:38+00:00"
}, },
{ {
"name": "symfony/twig-bundle", "name": "symfony/twig-bundle",
@@ -5276,5 +5274,5 @@
"platform-overrides": { "platform-overrides": {
"php": "7.4.0" "php": "7.4.0"
}, },
"plugin-api-version": "2.6.0" "plugin-api-version": "2.1.0"
} }

View File

@@ -59,17 +59,9 @@ class DbConnectionWrapper
* Use this to register a mock that will handle {@see mysqli::query()} * Use this to register a mock that will handle {@see mysqli::query()}
* *
* @param \mysqli|null $oMysqli * @param \mysqli|null $oMysqli
* @since 3.0.4 3.1.1 3.2.0 Param $oMysqli becomes nullable
* @since 3.1.0-4 N°6848 backport of restoring cnx on null parameter value
*/ */
public static function SetDbConnectionMockForQuery(?mysqli $oMysqli = null): void public static function SetDbConnectionMockForQuery(?mysqli $oMysqli): void
{ {
if (is_null($oMysqli)) { static::$oDbCnxMockableForQuery = $oMysqli;
// Reset to standard connection
static::$oDbCnxMockableForQuery = static::$oDbCnxStandard;
}
else {
static::$oDbCnxMockableForQuery = $oMysqli;
}
} }
} }

View File

@@ -419,7 +419,6 @@ class MyHelpers
//} //}
return $sOutput; return $sOutput;
} }
} }
/** /**
@@ -524,3 +523,5 @@ class Str
return (strtolower($sString) == $sString); return (strtolower($sString) == $sString);
} }
} }
?>

View File

@@ -1,101 +0,0 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Class TemporaryObjectDescriptor
*
* Descriptor to track a temporary object.
*
* @experimental do not use, this feature will be part of a future version
*
* @since 3.1
*/
class TemporaryObjectDescriptor extends DBObject
{
public static function Init()
{
$aParams = array(
'category' => 'core',
'key_type' => 'autoincrement',
'name_attcode' => array('item_class', 'temp_id'),
'image_attcode' => '',
'state_attcode' => '',
'reconc_keys' => array(''),
'db_table' => 'priv_temporary_object_descriptor',
'db_key_field' => 'id',
'db_finalclass_field' => '',
'style' => new ormStyle(null, null, null, null, null, null),
'indexes' => array(
1 =>
array(
0 => 'temp_id',
),
2 =>
array(
0 => 'item_class',
1 => 'item_id',
),
),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeDateTime('expiration_date', array('sql' => 'expiration_date', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('temp_id', array('sql' => 'temp_id', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('item_class', array('sql' => 'item_class', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeObjectKey('item_id', array('class_attcode' => 'item_class', 'sql' => 'item_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeDateTime('creation_date', array('sql' => 'creation_date', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('host_class', array('sql' => 'host_class', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeObjectKey('host_id', array('class_attcode' => 'host_class', 'sql' => 'host_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('host_att_code', array('sql' => 'host_att_code', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeEnum("operation", array("allowed_values" => new ValueSetEnum('create,delete'), "sql" => "operation", "default_value" => "create", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_SetZListItems('details', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
3 => 'creation_date',
4 => 'expiration_date',
5 => 'meta',
));
MetaModel::Init_SetZListItems('standard_search', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
));
MetaModel::Init_SetZListItems('list', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
3 => 'creation_date',
4 => 'expiration_date',
));;
}
public function DBInsertNoReload()
{
$this->SetCurrentDateIfNull('creation_date');
return parent::DBInsertNoReload();
}
/**
* Set/Update all of the '_item' fields
*
* @param object $oItem Container item
*
* @return void
*/
public function SetItem($oItem, $bUpdateOnChange = false)
{
$sClass = get_class($oItem);
$iItemId = $oItem->GetKey();
$this->Set('item_class', $sClass);
$this->Set('item_id', $iItemId);
}
}

View File

@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
/** /**
* Persistent classes (internal): user defined actions * Persistent classes (internal): user defined actions
@@ -226,22 +225,7 @@ class ActionEmail extends ActionNotification
* @since 3.0.1 * @since 3.0.1
*/ */
const ENUM_HEADER_NAME_REFERENCES = 'References'; const ENUM_HEADER_NAME_REFERENCES = 'References';
/**
* @var string
* @since 3.1.0
*/
const TEMPLATE_BODY_CONTENT = '$content$';
/**
* Wraps the 'body' of the message for previewing inside an IFRAME -- i.e. without any of the iTop stylesheets being applied
* @var string
* @since 3.1.0
*/
const CONTENT_HIGHLIGHT = '<div style="border:2px dashed #6800ff;position:relative;padding:2px;margin-top:14px;"><div style="background-color:#6800ff;color:#fff;font-family:Courier New, sans-serif;font-size:14px;line-height:16px;padding:3px;display:block;position:absolute;top:-22px;right:0;">$content$</div>%s</div>';
/**
* Wraps a placeholder of the email's body for previewing inside an IFRAME -- i.e. without any of the iTop stylesheets being applied
* @var string
*/
const FIELD_HIGHLIGHT = '<span style="background-color:#6800ff;color:#fff;font-size:smaller;font-family:Courier New, sans-serif;padding:2px;">\\$$1\\$</span>';
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -273,10 +257,6 @@ class ActionEmail extends ActionNotification
MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values" => null, "sql" => "subject", "default_value" => null, "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values" => null, "sql" => "subject", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values" => null, "sql" => "body", "default_value" => null, "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeTemplateHTML("body", array("allowed_values" => null, "sql" => "body", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values" => new ValueSetEnum('low,normal,high'), "sql" => "importance", "default_value" => 'normal', "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values" => new ValueSetEnum('low,normal,high'), "sql" => "importance", "default_value" => 'normal', "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeBlob("html_template", array("is_null_allowed"=>true, "depends_on"=>array(), "always_load_in_tables"=>false)));
MetaModel::Init_AddAttribute(new AttributeEnum("ignore_notify", array("allowed_values" => new ValueSetEnum('yes,no'), "sql" => "ignore_notify", "default_value" => 'yes', "is_null_allowed" => false, "depends_on" => array())));
// Display lists // Display lists
// - Attributes to be displayed for the complete details // - Attributes to be displayed for the complete details
@@ -286,10 +266,8 @@ class ActionEmail extends ActionNotification
0 => 'name', 0 => 'name',
1 => 'description', 1 => 'description',
2 => 'status', 2 => 'status',
3 => 'language', 3 => 'subject',
4 => 'html_template', 4 => 'body',
5 => 'subject',
6 => 'body',
// 5 => 'importance', not handled when sending the mail, better hide it then // 5 => 'importance', not handled when sending the mail, better hide it then
), ),
'fieldset:ActionEmail:trigger' => array( 'fieldset:ActionEmail:trigger' => array(
@@ -303,21 +281,20 @@ class ActionEmail extends ActionNotification
2 => 'reply_to', 2 => 'reply_to',
3 => 'reply_to_label', 3 => 'reply_to_label',
4 => 'test_recipient', 4 => 'test_recipient',
5 => 'ignore_notify', 5 => 'to',
6 => 'to', 6 => 'cc',
7 => 'cc', 7 => 'bcc',
8 => 'bcc',
), ),
), ),
)); ));
// - Attributes to be displayed for a list // - Attributes to be displayed for a list
MetaModel::Init_SetZListItems('list', array('status', 'to', 'subject', 'language')); MetaModel::Init_SetZListItems('list', array('status', 'to', 'subject'));
// Search criteria // Search criteria
// - Standard criteria of the search // - Standard criteria of the search
MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'status', 'subject', 'language')); MetaModel::Init_SetZListItems('standard_search', array('name', 'description', 'status', 'subject'));
// - Default criteria for the search // - Default criteria for the search
MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status', 'subject', 'language')); MetaModel::Init_SetZListItems('default_search', array('name', 'description', 'status', 'subject'));
} }
// count the recipients found // count the recipients found
@@ -347,15 +324,6 @@ class ActionEmail extends ActionNotification
try try
{ {
$oSearch = DBObjectSearch::FromOQL($sOQL); $oSearch = DBObjectSearch::FromOQL($sOQL);
if ($this->Get('ignore_notify') === 'no') {
// In theory it is possible to notify *any* kind of object,
// as long as there is an email attribute in the class
// So let's not assume that the selected class is a Person
$sFirstSelectedClass = $oSearch->GetClass();
if (MetaModel::IsValidAttCode($sFirstSelectedClass, 'notify')) {
$oSearch->AddCondition('notify', 'yes');
}
}
$oSearch->AllowAllData(); $oSearch->AllowAllData();
} }
catch (OQLException $e) catch (OQLException $e)
@@ -480,27 +448,114 @@ class ActionEmail extends ActionNotification
*/ */
protected function _DoExecute($oTrigger, $aContextArgs, &$oLog) protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
{ {
$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
try
{
$this->m_iRecipients = 0;
$this->m_aMailErrors = array();
// Determine recipients
//
$sTo = $this->FindRecipients('to', $aContextArgs);
$sCC = $this->FindRecipients('cc', $aContextArgs);
$sBCC = $this->FindRecipients('bcc', $aContextArgs);
$sFrom = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
$sFromLabel = MetaModel::ApplyParams($this->Get('from_label'), $aContextArgs);
$sReplyTo = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
$sReplyToLabel = MetaModel::ApplyParams($this->Get('reply_to_label'), $aContextArgs);
$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$sMessageId = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_MESSAGE_ID);
$sReference = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_REFERENCES);
}
catch (Exception $e) {
/** @noinspection PhpUnhandledExceptionInspection */
throw $e;
}
finally {
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
}
if (!is_null($oLog)) {
// Note: we have to secure this because those values are calculated
// inside the try statement, and we would like to keep track of as
// many data as we could while some variables may still be undefined
if (isset($sTo)) {
$oLog->Set('to', $sTo);
}
if (isset($sCC)) {
$oLog->Set('cc', $sCC);
}
if (isset($sBCC)) {
$oLog->Set('bcc', $sBCC);
}
if (isset($sFrom)) {
$oLog->Set('from', $sFrom);
}
if (isset($sSubject)) {
$oLog->Set('subject', $sSubject);
}
if (isset($sBody)) {
$oLog->Set('body', $sBody);
}
}
$sStyles = file_get_contents(APPROOT.'css/email.css'); $sStyles = file_get_contents(APPROOT.'css/email.css');
$sStyles .= MetaModel::GetConfig()->Get('email_css'); $sStyles .= MetaModel::GetConfig()->Get('email_css');
$oEmail = new EMail(); $oEmail = new EMail();
$aEmailContent = $this->PrepareMessageContent($aContextArgs, $oLog); if ($this->IsBeingTested()) {
$oEmail->SetSubject($aEmailContent['subject']); $oEmail->SetSubject('TEST['.$sSubject.']');
$oEmail->SetBody($aEmailContent['body'], 'text/html', $sStyles); $sTestBody = $sBody;
$oEmail->SetRecipientTO($aEmailContent['to']); $sTestBody .= "<div style=\"border: dashed;\">\n";
$oEmail->SetRecipientCC($aEmailContent['cc']); $sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
$oEmail->SetRecipientBCC($aEmailContent['bcc']); $sTestBody .= "<p>The email should be sent with the following properties\n";
$oEmail->SetRecipientFrom($aEmailContent['from'], $aEmailContent['from_label']); $sTestBody .= "<ul>\n";
$oEmail->SetRecipientReplyTo($aEmailContent['reply_to'], $aEmailContent['reply_to_label']); $sTestBody .= "<li>TO: $sTo</li>\n";
$oEmail->SetReferences($aEmailContent['references']); $sTestBody .= "<li>CC: $sCC</li>\n";
$oEmail->SetMessageId($aEmailContent['message_id']); $sTestBody .= "<li>BCC: $sBCC</li>\n";
$oEmail->SetInReplyTo($aEmailContent['in_reply_to']); $sTestBody .= empty($sFromLabel) ? "<li>From: $sFrom</li>\n" : "<li>From: $sFromLabel &lt;$sFrom&gt;</li>\n";
$sTestBody .= empty($sReplyToLabel) ? "<li>Reply-To: $sReplyTo</li>\n" : "<li>Reply-To: $sReplyToLabel &lt;$sReplyTo&gt;</li>\n";
foreach($aEmailContent['attachments'] as $aAttachment) { $sTestBody .= "<li>References: $sReference</li>\n";
$oEmail->AddAttachment($aAttachment['data'], $aAttachment['filename'], $aAttachment['mime_type']); $sTestBody .= "</ul>\n";
$sTestBody .= "</p>\n";
$sTestBody .= "</div>\n";
$oEmail->SetBody($sTestBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($this->Get('test_recipient'));
$oEmail->SetRecipientFrom($sFrom, $sFromLabel);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$oEmail->SetInReplyTo($sReference);
} else {
$oEmail->SetSubject($sSubject);
$oEmail->SetBody($sBody, 'text/html', $sStyles);
$oEmail->SetRecipientTO($sTo);
$oEmail->SetRecipientCC($sCC);
$oEmail->SetRecipientBCC($sBCC);
$oEmail->SetRecipientFrom($sFrom, $sFromLabel);
$oEmail->SetRecipientReplyTo($sReplyTo, $sReplyToLabel);
$oEmail->SetReferences($sReference);
$oEmail->SetMessageId($sMessageId);
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$oEmail->SetInReplyTo($sReference);
} }
if (isset($aContextArgs['attachments']))
{
$aAttachmentReport = array();
foreach($aContextArgs['attachments'] as $oDocument)
{
$oEmail->AddAttachment($oDocument->GetData(), $oDocument->GetFileName(), $oDocument->GetMimeType());
$aAttachmentReport[] = array($oDocument->GetFileName(), $oDocument->GetMimeType(), strlen($oDocument->GetData()));
}
$oLog->Set('attachments', $aAttachmentReport);
}
if (empty($this->m_aMailErrors)) if (empty($this->m_aMailErrors))
{ {
if ($this->m_iRecipients == 0) if ($this->m_iRecipients == 0)
@@ -509,7 +564,6 @@ class ActionEmail extends ActionNotification
} }
else else
{ {
$aErrors = [];
$iRes = $oEmail->Send($aErrors, false, $oLog); // allow asynchronous mode $iRes = $oEmail->Send($aErrors, false, $oLog); // allow asynchronous mode
switch ($iRes) switch ($iRes)
{ {
@@ -534,148 +588,13 @@ class ActionEmail extends ActionNotification
} }
} }
/**
* @param array $aContextArgs
* @param \EventNotification $oLog
*
* @return array
* @throws \CoreException
* @throws \Exception
* @since 3.1.0 N°918
*/
protected function PrepareMessageContent($aContextArgs, &$oLog): array
{
$aMessageContent = [
'to' => '',
'cc' => '',
'bcc' => '',
'from' => '',
'from_label' => '',
'reply_to' => '',
'reply_to_label' => '',
'subject' => '',
'body' => '',
'references' => '',
'message_id' => '',
'in_reply_to' => '',
'attachments' => [],
];
$sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
$sPreviousLanguage = Dict::GetUserLanguage();
$aPreviousPluginProperties = ApplicationContext::GetPluginProperties('QueryLocalizerPlugin');
if ($this->Get('language') !== '') {
// If a language is specified for this action, force this language
// when rendering all placeholders inside this message
Dict::SetUserLanguage($this->Get('language'));
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $this->Get('language'));
}
try
{
$this->m_iRecipients = 0;
$this->m_aMailErrors = array();
// Determine recipients
//
$aMessageContent['to'] = $this->FindRecipients('to', $aContextArgs);
$aMessageContent['cc'] = $this->FindRecipients('cc', $aContextArgs);
$aMessageContent['bcc'] = $this->FindRecipients('bcc', $aContextArgs);
$aMessageContent['from'] = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
$aMessageContent['from_label'] = MetaModel::ApplyParams($this->Get('from_label'), $aContextArgs);
$aMessageContent['reply_to'] = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
$aMessageContent['reply_to_label'] = MetaModel::ApplyParams($this->Get('reply_to_label'), $aContextArgs);
$aMessageContent['subject'] = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
$sBody = $this->BuildMessageBody(false);
$aMessageContent['body'] = MetaModel::ApplyParams($sBody, $aContextArgs);
$oObj = $aContextArgs['this->object()'];
$aMessageContent['message_id'] = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_MESSAGE_ID);
$aMessageContent['references'] = $this->GenerateIdentifierForHeaders($oObj, static::ENUM_HEADER_NAME_REFERENCES);
}
catch (Exception $e) {
/** @noinspection PhpUnhandledExceptionInspection */
throw $e;
}
finally {
ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
Dict::SetUserLanguage($sPreviousLanguage);
AttributeDateTime::LoadFormatFromConfig();
ApplicationContext::SetPluginProperty('QueryLocalizerPlugin', 'language_code', $aPreviousPluginProperties['language_code'] ?? null);
}
if (!is_null($oLog)) {
// Note: we have to secure this because those values are calculated
// inside the try statement, and we would like to keep track of as
// many data as we could while some variables may still be undefined
if (isset($aMessageContent['to'])) {
$oLog->Set('to', $aMessageContent['to']);
}
if (isset($aMessageContent['cc'])) {
$oLog->Set('cc', $aMessageContent['cc']);
}
if (isset($aMessageContent['bcc'])) {
$oLog->Set('bcc', $aMessageContent['bcc']);
}
if (isset($aMessageContent['from'])) {
$oLog->Set('from', $aMessageContent['from']);
}
if (isset($aMessageContent['subject'])) {
$oLog->Set('subject', $aMessageContent['subject']);
}
if (isset($aMessageContent['body'])) {
$oLog->Set('body', HTMLSanitizer::Sanitize($aMessageContent['body']));
}
}
if ($this->IsBeingTested()) {
$sTestBody = $aMessageContent['body'];
$sTestBody .= "<div style=\"border: dashed;\">\n";
$sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
$sTestBody .= "<p>The email should be sent with the following properties\n";
$sTestBody .= "<ul>\n";
$sTestBody .= "<li>TO: {$aMessageContent['to']}</li>\n";
$sTestBody .= "<li>CC: {$aMessageContent['cc']}</li>\n";
$sTestBody .= "<li>BCC: {$aMessageContent['bcc']}</li>\n";
$sTestBody .= empty($aMessageContent['from_label']) ? "<li>From: {$aMessageContent['from']}</li>\n" : "<li>From: {$aMessageContent['from_label']} &lt;{$aMessageContent['from']}&gt;</li>\n";
$sTestBody .= empty($aMessageContent['reply_to_label']) ? "<li>Reply-To: {$aMessageContent['reply_to']}</li>\n" : "<li>Reply-To: {$aMessageContent['reply_to_label']} &lt;{$aMessageContent['reply_to']}&gt;</li>\n";
$sTestBody .= "<li>References: {$aMessageContent['references']}</li>\n";
$sTestBody .= "</ul>\n";
$sTestBody .= "</p>\n";
$sTestBody .= "</div>\n";
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
$aMessageContent['body'] = $sTestBody;
$aMessageContent['to'] = $this->Get('test_recipient');
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
$aMessageContent['cc'] = '';
$aMessageContent['bcc'] = '';
}
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$aMessageContent['in_reply_to'] = $aMessageContent['references'];
if (isset($aContextArgs['attachments']))
{
$aAttachmentReport = array();
foreach($aContextArgs['attachments'] as $oDocument)
{
$aMessageContent['attachments'][] = ['data' => $oDocument->GetData(), 'filename' => $oDocument->GetFileName(), 'mime_type' => $oDocument->GetMimeType()];
$aAttachmentReport[] = array($oDocument->GetFileName(), $oDocument->GetMimeType(), strlen($oDocument->GetData()));
}
$oLog->Set('attachments', $aAttachmentReport);
}
return $aMessageContent;
}
/** /**
* @param \DBObject $oObject * @param \DBObject $oObject
* @param string $sHeaderName {@see \ActionEmail::ENUM_HEADER_NAME_REFERENCES}, {@see \ActionEmail::ENUM_HEADER_NAME_MESSAGE_ID} * @param string $sHeaderName {@see \ActionEmail::ENUM_HEADER_NAME_REFERENCES}, {@see \ActionEmail::ENUM_HEADER_NAME_MESSAGE_ID}
* *
* @return string The formatted identifier for $sHeaderName based on $oObject * @return string The formatted identifier for $sHeaderName based on $oObject
* @throws \Exception * @throws \Exception
* @since 3.1.0 N°4849 * @since 3.0.1 N°4849
*/ */
protected function GenerateIdentifierForHeaders(DBObject $oObject, string $sHeaderName): string protected function GenerateIdentifierForHeaders(DBObject $oObject, string $sHeaderName): string
{ {
@@ -710,65 +629,4 @@ class ActionEmail extends ActionNotification
]); ]);
throw new Exception($sErrorMessage); throw new Exception($sErrorMessage);
} }
/**
* Compose the body of the message from the 'body' attribute and the HTML template (if any)
* @since 3.1.0 N°4849
* @param bool $bHighlightPlaceholders If true add some extra HTML around placeholders to highlight them
* @return string
*/
protected function BuildMessageBody(bool $bHighlightPlaceholders = false): string
{
$sBody = $this->Get('body');
/** @var ormDocument $oHtmlTemplate */
$oHtmlTemplate = $this->Get('html_template');
if ($oHtmlTemplate && !$oHtmlTemplate->IsEmpty()) {
$sHtmlTemplate = $oHtmlTemplate->GetData();
if (false !== mb_strpos($sHtmlTemplate, static::TEMPLATE_BODY_CONTENT)) {
if ($bHighlightPlaceholders) {
// Highlight the $content$ block
$sBody = sprintf(static::CONTENT_HIGHLIGHT, $sBody);
}
$sBody = str_replace(static::TEMPLATE_BODY_CONTENT, $sBody, $oHtmlTemplate->GetData()); // str_replace is ok as long as both strings are well-formed UTF-8
} else {
$sBody = $oHtmlTemplate->GetData();
}
}
if($bHighlightPlaceholders) {
// Highlight all placeholders
$sBody = preg_replace('/\\$([^$]+)\\$/', static::FIELD_HIGHLIGHT, $sBody);
}
return $sBody;
}
/**
* @since 3.1.0 N°4849
* @inheritDoc
* @see cmdbAbstractObject::DisplayBareRelations()
*/
public function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
{
parent::DisplayBareRelations($oPage, false);
if (!$bEditMode) {
$oPage->SetCurrentTab('action_email__preview', Dict::S('ActionEmail:preview_tab'), Dict::S('ActionEmail:preview_tab+'));
$sBody = $this->BuildMessageBody(true);
TwigHelper::RenderIntoPage($oPage, APPROOT.'/', 'templates/datamodel/ActionEmail/email-notification-preview', ['iframe_content' => $sBody]);
}
}
/**
* @since 3.1.0
* @inheritDoc
* @see cmdbAbstractObject::DoCheckToWrite()
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
$oHtmlTemplate = $this->Get('html_template');
if ($oHtmlTemplate && !$oHtmlTemplate->IsEmpty()) {
if (false === mb_strpos($oHtmlTemplate->GetData(), static::TEMPLATE_BODY_CONTENT)) {
$this->m_aCheckWarnings[] = Dict::Format('ActionEmail:content_placeholder_missing', static::TEMPLATE_BODY_CONTENT, Dict::S('Class:ActionEmail/Attribute:body'));
}
}
}
} }

View File

@@ -60,15 +60,14 @@ function apc_store($key, $var = NULL, $ttl = 0)
*/ */
function apc_fetch($key) function apc_fetch($key)
{ {
if (is_array($key)) { if (is_array($key))
$aResult = []; {
foreach ($key as $sKey) { $aResult = array();
foreach($key as $sKey)
{
$aResult[$sKey] = apcFile::FetchOneFile($sKey); $aResult[$sKey] = apcFile::FetchOneFile($sKey);
} }
return $aResult; return $aResult;
} elseif (is_null($key)) {
return false;
} }
return apcFile::FetchOneFile($key); return apcFile::FetchOneFile($key);
} }
@@ -212,16 +211,21 @@ class apcFile
*/ */
static public function StoreOneFile($sKey, $value, $iTTL) static public function StoreOneFile($sKey, $value, $iTTL)
{ {
if (empty($sKey)) { if (empty($sKey))
{
return false; return false;
} }
if (is_file(self::GetCacheFileName($sKey))) {
if (is_file(self::GetCacheFileName($sKey)))
{
@unlink(self::GetCacheFileName($sKey)); @unlink(self::GetCacheFileName($sKey));
} }
if (is_file(self::GetCacheFileName('-'.$sKey))) { if (is_file(self::GetCacheFileName('-'.$sKey)))
{
@unlink(self::GetCacheFileName('-'.$sKey)); @unlink(self::GetCacheFileName('-'.$sKey));
} }
if ($iTTL > 0) { if ($iTTL > 0)
{
// hint for ttl management // hint for ttl management
$sKey = '-'.$sKey; $sKey = '-'.$sKey;
} }
@@ -229,14 +233,15 @@ class apcFile
$sFilename = self::GetCacheFileName($sKey); $sFilename = self::GetCacheFileName($sKey);
// try to create the folder // try to create the folder
$sDirname = dirname($sFilename); $sDirname = dirname($sFilename);
if (!is_dir($sDirname)) { if (!file_exists($sDirname))
if (!@mkdir($sDirname, 0755, true)) { {
if (!@mkdir($sDirname, 0755, true))
{
return false; return false;
} }
} }
$bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false); $bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false);
self::AddFile($sFilename); self::AddFile($sFilename);
return $bRes; return $bRes;
} }
@@ -320,15 +325,19 @@ class apcFile
*/ */
static protected function ReadCacheLocked($sFilename) static protected function ReadCacheLocked($sFilename)
{ {
$sContent = false; if (!is_file($sFilename))
$file = @fopen($sFilename, 'r'); {
if ($file !== false) { return false;
if (flock($file, LOCK_SH)) {
$sContent = file_get_contents($sFilename);
flock($file, LOCK_UN);
}
fclose($file);
} }
$file = @fopen($sFilename, 'r');
if ($file === false)
{
return false;
}
flock($file, LOCK_SH);
$sContent = @fread($file, @filesize($sFilename));
flock($file, LOCK_UN);
fclose($file);
return $sContent; return $sContent;
} }

View File

@@ -9,7 +9,8 @@ use Combodo\iTop\Application\UI\Links\Set\BlockLinkSetDisplayAsProperty;
use Combodo\iTop\Form\Field\LabelField; use Combodo\iTop\Form\Field\LabelField;
use Combodo\iTop\Form\Field\TextAreaField; use Combodo\iTop\Form\Field\TextAreaField;
use Combodo\iTop\Form\Form; use Combodo\iTop\Form\Form;
use Combodo\iTop\Form\Validator\CustomRegexpValidator; use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
use Combodo\iTop\Form\Validator\Validator;
use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\BlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Service\Links\LinkSetModel; use Combodo\iTop\Service\Links\LinkSetModel;
@@ -91,11 +92,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_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_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
define('LINKSET_EDITWHEN_NEVER', 0); // The linkset cannot be edited at all from inside this object define('LINKSET_RELATIONTYPE_PROPERTY', 'property');
define('LINKSET_EDITWHEN_ON_HOST_EDITION', 1); // The only possible action is to open a new window to create a new object define('LINKSET_RELATIONTYPE_LINK', 'link');
define('LINKSET_EDITWHEN_ON_HOST_DISPLAY', 2); // Show the usual 'Actions' popup menu
define('LINKSET_EDITWHEN_ALWAYS', 3); // Show the usual 'Actions' popup menu
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property'); define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
define('LINKSET_DISPLAY_STYLE_TAB', 'tab'); define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
@@ -797,14 +795,14 @@ abstract class AttributeDefinition
public function HasAValue($proposedValue): bool public function HasAValue($proposedValue): bool
{ {
// Default implementation, we don't really know what type $proposedValue will be // Default implementation, we don't really know what type $proposedValue will be
return !(is_null($proposedValue)); return is_null($proposedValue);
} }
/** /**
* force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing! * force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!
* *
* @param mixed $proposedValue * @param $proposedValue
* @param \DBObject $oHostObj * @param $oHostObj
* *
* @return mixed * @return mixed
*/ */
@@ -994,21 +992,17 @@ abstract class AttributeDefinition
} }
/** /**
* Helper to form a value, given JSON decoded data. This way the attribute itself handles the transformation from the JSON structure to the expected data (the one that * Helper to form a value, given JSON decoded data
* needs to be used in the {@see \DBObject::Set()} method).
*
* Note that for CSV and XML this isn't done yet (no delegation to the attribute but switch/case inside controllers) :/
*
* @see GetForJSON for the reverse operation
* *
* @param string $json JSON encoded value * @param string $json JSON encoded value
* *
* @return mixed JSON decoded data, depending on the attribute type * @return mixed JSON decoded data
* *
* @see GetForJSON for the reverse operation
*/ */
public function FromJSONToValue($json) public function FromJSONToValue($json)
{ {
// Pass-through in most of the cases // Passthrough in most of the cases
return $json; return $json;
} }
@@ -1127,7 +1121,7 @@ abstract class AttributeDefinition
// Validation pattern // Validation pattern
if ($this->GetValidationPattern() !== '') { if ($this->GetValidationPattern() !== '') {
$oFormField->AddValidator(new CustomRegexpValidator($this->GetValidationPattern())); $oFormField->AddValidator(new Validator($this->GetValidationPattern()));
} }
// Description // Description
@@ -1156,13 +1150,6 @@ abstract class AttributeDefinition
$oFormField->AddMetadata('value-raw', (string)$oObject->Get($this->GetCode())); $oFormField->AddMetadata('value-raw', (string)$oObject->Get($this->GetCode()));
} }
// We don't want to invalidate field because of old untouched values that are no longer valid
$aModifiedAttCodes = $oObject->ListChanges();
$bAttributeHasBeenModified = array_key_exists($this->GetCode(), $aModifiedAttCodes);
if (false === $bAttributeHasBeenModified) {
$oFormField->SetValidationDisabled(true);
}
return $oFormField; return $oFormField;
} }
@@ -1705,19 +1692,20 @@ class AttributeLinkedSet extends AttributeDefinition
/** /**
* @return string see LINKSET_EDITMODE_* constants * @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() public function GetEditMode()
{ {
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS); return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
} }
/** /**
* @return int see LINKSET_EDITWHEN_* constants * @return string see LINKSET_RELATIONTYPE_* constants
* @since 3.1.1 3.2.0 N°6385 * @since 3.1.0 N°5563
*/ */
public function GetEditWhen(): int public function GetRelationType()
{ {
return $this->GetOptional('edit_when', LINKSET_EDITWHEN_ALWAYS); return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK);
} }
/** /**
@@ -1735,24 +1723,14 @@ class AttributeLinkedSet extends AttributeDefinition
} }
/** /**
* Indicates if the current Attribute has constraints (php constraints or datamodel constraints) * @return boolean
* @return bool true if Attribute has constraints * @since 3.1.0 N°5563
* @since 3.1.0 N°6228
*/ */
public function HasPHPConstraint(): bool public function GetReadOnly()
{ {
return $this->GetOptional('with_php_constraint', false); return $this->GetOptional('read_only', false);
} }
/**
* @return bool true if Attribute has computation (DB_LINKS_CHANGED event propagation, `with_php_computation` attribute xml property), false otherwise
* @since 3.1.1 3.2.0 N°6228
*/
public function HasPHPComputation(): bool
{
return $this->GetOptional('with_php_computation', false);
}
public function GetLinkedClass() public function GetLinkedClass()
{ {
return $this->Get('linked_class'); return $this->Get('linked_class');
@@ -1779,58 +1757,12 @@ class AttributeLinkedSet extends AttributeDefinition
} }
/** @inheritDoc * */ /** @inheritDoc * */
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true): string
{
if($this->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB){
return $this->GetAsHTMLForTab($sValue, $oHostObject, $bLocalize);
}
else{
return $this->GetAsHTMLForProperty($sValue, $oHostObject, $bLocalize);
}
}
public function GetAsHTMLForTab($sValue, $oHostObject = null, $bLocalize = true)
{
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
{
$sValue->Rewind();
$aItems = array();
while ($oObj = $sValue->Fetch())
{
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
{
if ($sAttCode == $this->GetExtKeyToMe())
{
continue;
}
if ($oAttDef->IsExternalField())
{
continue;
}
$sAttValue = $oObj->GetAsHTML($sAttCode);
if (strlen($sAttValue) > 0)
{
$aAttributes[] = $sAttValue;
}
}
$sAttributes = implode(', ', $aAttributes);
$aItems[] = $sAttributes;
}
return implode('<br/>', $aItems);
}
return null;
}
public function GetAsHTMLForProperty($sValue, $oHostObject = null, $bLocalize = true): string
{ {
try { try {
/** @var ormLinkSet $sValue */ /** @var ormLinkSet $sValue */
if (is_null($sValue) || $sValue->Count() === 0) { if ($sValue->Count() === 0) {
return ''; return '';
} }
@@ -2449,61 +2381,43 @@ class AttributeLinkedSet extends AttributeDefinition
{ {
if ($oFormField === null) if ($oFormField === null)
{ {
$sFormFieldClass = static::GetFormFieldClass(); $sFormFieldClass = static::GetFormFieldClass();
$oFormField = new $sFormFieldClass($this->GetCode()); $oFormField = new $sFormFieldClass($this->GetCode());
} }
// Setting target class // Setting target class
if (!$this->IsIndirect()) { if (!$this->IsIndirect()) {
$sTargetClass = $this->GetLinkedClass(); $sTargetClass = $this->GetLinkedClass();
} else { } else {
/** @var \AttributeExternalKey $oRemoteAttDef */ /** @var \AttributeExternalKey $oRemoteAttDef */
/** @var \AttributeLinkedSetIndirect $this */ /** @var \AttributeLinkedSetIndirect $this */
$oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote()); $oRemoteAttDef = MetaModel::GetAttributeDef($this->GetLinkedClass(), $this->GetExtKeyToRemote());
$sTargetClass = $oRemoteAttDef->GetTargetClass(); $sTargetClass = $oRemoteAttDef->GetTargetClass();
/** @var \AttributeLinkedSetIndirect $this */ /** @var \AttributeLinkedSetIndirect $this */
$oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote()); $oFormField->SetExtKeyToRemote($this->GetExtKeyToRemote());
} }
$oFormField->SetTargetClass($sTargetClass); $oFormField->SetTargetClass($sTargetClass);
$oFormField->SetLinkedClass($this->GetLinkedClass()); $oFormField->SetIndirect($this->IsIndirect());
$oFormField->SetIndirect($this->IsIndirect()); // Setting attcodes to display
// Setting attcodes to display $aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list'));
$aAttCodesToDisplay = MetaModel::FlattenZList(MetaModel::GetZListItems($sTargetClass, 'list')); // - Adding friendlyname attribute to the list is not already in it
// - Adding friendlyname attribute to the list is not already in it $sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass);
$sTitleAttCode = MetaModel::GetFriendlyNameAttributeCode($sTargetClass); if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) {
if (($sTitleAttCode !== null) && !in_array($sTitleAttCode, $aAttCodesToDisplay)) { $aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay);
$aAttCodesToDisplay = array_merge(array($sTitleAttCode), $aAttCodesToDisplay); }
} // - Adding attribute labels
// - Adding attribute properties $aAttributesToDisplay = array();
$aAttributesToDisplay = array(); foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) {
foreach ($aAttCodesToDisplay as $sAttCodeToDisplay) { $oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay);
$oAttDefToDisplay = MetaModel::GetAttributeDef($sTargetClass, $sAttCodeToDisplay); $aAttributesToDisplay[$sAttCodeToDisplay] = $oAttDefToDisplay->GetLabel();
$aAttributesToDisplay[$sAttCodeToDisplay] = [ }
'att_code' => $sAttCodeToDisplay, $oFormField->SetAttributesToDisplay($aAttributesToDisplay);
'label' => $oAttDefToDisplay->GetLabel(),
];
}
$oFormField->SetAttributesToDisplay($aAttributesToDisplay);
// Append lnk attributes (filtered from zlist) parent::MakeFormField($oObject, $oFormField);
if ($this->IsIndirect()) {
$aLnkAttDefToDisplay = MetaModel::GetZListAttDefsFilteredForIndirectLinkClass($this->m_sHostClass, $this->m_sCode);
$aLnkAttributesToDisplay = array();
foreach ($aLnkAttDefToDisplay as $oLnkAttDefToDisplay) {
$aLnkAttributesToDisplay[$oLnkAttDefToDisplay->GetCode()] = [
'att_code' => $oLnkAttDefToDisplay->GetCode(),
'label' => $oLnkAttDefToDisplay->GetLabel(),
'mandatory' => !$oLnkAttDefToDisplay->IsNullAllowed(),
];
}
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
}
parent::MakeFormField($oObject, $oFormField); return $oFormField;
}
return $oFormField;
}
public function IsPartOfFingerprint() public function IsPartOfFingerprint()
{ {
@@ -2609,6 +2523,15 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
return $this->GetOptional("duplicates", false); return $this->GetOptional("duplicates", false);
} // The same object may be linked several times... or not... } // 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() public function GetTrackingLevel()
{ {
return $this->GetOptional('tracking_level', return $this->GetOptional('tracking_level',
@@ -3061,11 +2984,6 @@ class AttributeObjectKey extends AttributeDBFieldVoid
return ((int) $proposedValue) !== 0; return ((int) $proposedValue) !== 0;
} }
/**
* @inheritDoc
*
* @param int|DBObject $proposedValue Object key or valid ({@see MetaModel::IsValidObject()}) datamodel object
*/
public function MakeRealValue($proposedValue, $oHostObj) public function MakeRealValue($proposedValue, $oHostObj)
{ {
if (is_null($proposedValue)) if (is_null($proposedValue))
@@ -3078,6 +2996,7 @@ class AttributeObjectKey extends AttributeDBFieldVoid
} }
if (MetaModel::IsValidObject($proposedValue)) if (MetaModel::IsValidObject($proposedValue))
{ {
/** @var \DBObject $proposedValue */
return $proposedValue->GetKey(); return $proposedValue->GetKey();
} }
@@ -3197,7 +3116,7 @@ class AttributeDecimal extends AttributeDBField
$iPrecision = $this->Get('decimals'); $iPrecision = $this->Get('decimals');
$iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below $iNbIntegerDigits = $iNbDigits - $iPrecision - 1; // -1 because the first digit is treated separately in the pattern below
return "^[\-\+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$"; return "^[-+]?[0-9]\d{0,$iNbIntegerDigits}(\.\d{0,$iPrecision})?$";
} }
public function GetBasicFilterOperators() public function GetBasicFilterOperators()
@@ -3919,12 +3838,6 @@ class AttributeApplicationLanguage extends AttributeString
{ {
$aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')'; $aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')';
} }
// N°6462 This should be sorted directly in \Dict during the compilation but we can't for 2 reasons:
// - Additional languages can be added on the fly even though it is not recommended
// - Formatting is done at run time (just above)
natcasesort($aLanguageCodes);
$aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes); $aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes);
parent::__construct($sCode, $aParams); parent::__construct($sCode, $aParams);
} }
@@ -4215,7 +4128,7 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true) public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
{ {
if (utils::IsNullOrEmptyString($sValue)) if (strlen($sValue) == 0)
{ {
return ''; return '';
} }
@@ -4534,6 +4447,7 @@ class AttributeText extends AttributeString
$sStyle = ''; $sStyle = '';
if (count($aStyles) > 0) if (count($aStyles) > 0)
{ {
$aStyles[] = 'overflow:auto';
$sStyle = 'style="'.implode(';', $aStyles).'"'; $sStyle = 'style="'.implode(';', $aStyles).'"';
} }
@@ -5982,14 +5896,6 @@ class AttributeEnum extends AttributeString
$aLocalizedValues[$sKey] = $this->GetValueLabel($sKey); $aLocalizedValues[$sKey] = $this->GetValueLabel($sKey);
} }
// Sort by label only if necessary
// See N°1646 and {@see \MFCompiler::CompileAttributeEnumValues()} for complete information as for why sort on labels is done at runtime while other sorting are done at compile time
/** @var \ValueSetEnum $oValueSetDef */
$oValueSetDef = $this->GetValuesDef();
if ($oValueSetDef->IsSortedByValues()) {
asort($aLocalizedValues);
}
return $aLocalizedValues; return $aLocalizedValues;
} }
@@ -6265,7 +6171,7 @@ class AttributeDateTime extends AttributeDBField
/** /**
* Load the 3 settings: date format, time format and data_time format from the configuration * Load the 3 settings: date format, time format and data_time format from the configuration
*/ */
public static function LoadFormatFromConfig() protected static function LoadFormatFromConfig()
{ {
$aFormats = MetaModel::GetConfig()->Get('date_and_time_format'); $aFormats = MetaModel::GetConfig()->Get('date_and_time_format');
$sLang = Dict::GetUserLanguage(); $sLang = Dict::GetUserLanguage();
@@ -6513,11 +6419,6 @@ class AttributeDateTime extends AttributeDBField
} }
} }
/**
* @inheritDoc
*
* @param int|string $proposedValue timestamp ({@see DateTime::getTimestamp()) or date as string, following the {@see GetInternalFormat} format.
*/
public function MakeRealValue($proposedValue, $oHostObj) public function MakeRealValue($proposedValue, $oHostObj)
{ {
if (is_null($proposedValue)) if (is_null($proposedValue))
@@ -7252,27 +7153,14 @@ class AttributeExternalKey extends AttributeDBFieldVoid
{ {
return 0; return 0;
} }
if (MetaModel::IsValidObject($proposedValue)) { if (MetaModel::IsValidObject($proposedValue))
{
return $proposedValue->GetKey(); return $proposedValue->GetKey();
} }
return (int)$proposedValue; return (int)$proposedValue;
} }
/** @inheritdoc @since 3.1 */
public function WriteExternalValues(DBObject $oHostObject): void
{
$sTargetKey = $oHostObject->Get($this->GetCode());
$oFilter = DBSearch::FromOQL('SELECT `'.TemporaryObjectDescriptor::class.'` WHERE item_class=:class AND item_id=:id');
$oSet = new DBObjectSet($oFilter, [], ['class' => $this->GetTargetClass(), 'id' => $sTargetKey]);
while ($oTemporaryObjectDescriptor = $oSet->Fetch()) {
$oTemporaryObjectDescriptor->Set('host_class', get_class($oHostObject));
$oTemporaryObjectDescriptor->Set('host_id', $oHostObject->GetKey());
$oTemporaryObjectDescriptor->Set('host_att_code', $this->GetCode());
$oTemporaryObjectDescriptor->DBUpdate();
}
}
public function GetMaximumComboLength() public function GetMaximumComboLength()
{ {
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length')); return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
@@ -7336,7 +7224,6 @@ class AttributeExternalKey extends AttributeDBFieldVoid
public function MakeFormField(DBObject $oObject, $oFormField = null) public function MakeFormField(DBObject $oObject, $oFormField = null)
{ {
/** @var \Combodo\iTop\Form\Field\Field $oFormField */
if ($oFormField === null) { if ($oFormField === null) {
// Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value // Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value
$sFormFieldClass = static::GetFormFieldClass(); $sFormFieldClass = static::GetFormFieldClass();
@@ -7367,12 +7254,19 @@ class AttributeExternalKey extends AttributeDBFieldVoid
} }
}); });
} }
else { else
{
$oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression()); $oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression());
$oSearch->SetInternalParams(array('this' => $oObject)); $oSearch->SetInternalParams(array('this' => $oObject));
$oFormField->SetSearch($oSearch); $oFormField->SetSearch($oSearch);
} }
// If ExtKey is mandatory, we add a validator to ensure that the value 0 is not selected
if ($oObject->GetAttributeFlags($this->GetCode()) & OPT_ATT_MANDATORY)
{
$oFormField->AddValidator(new NotEmptyExtKeyValidator());
}
parent::MakeFormField($oObject, $oFormField); parent::MakeFormField($oObject, $oFormField);
return $oFormField; return $oFormField;
@@ -7998,17 +7892,6 @@ class AttributeExternalField extends AttributeDefinition
return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj); return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj);
} }
/**
* @inheritDoc
* @since 3.1.0 N°6271 Delegate to remote attribute to ensure cascading computed values
*/
public function GetSQLValues($value)
{
$oExtAttDef = $this->GetExtAttDef();
return $oExtAttDef->GetSQLValues($value);
}
public function ScalarToSQL($value) public function ScalarToSQL($value)
{ {
// This one could be used in case of filtering only // This one could be used in case of filtering only
@@ -8307,9 +8190,9 @@ class AttributeBlob extends AttributeDefinition
} }
/** /**
* {@inheritDoc} * Users can provide the document from an URL (including an URL on iTop itself)
* * for CSV import. Administrators can even provide the path to a local file
* @param string $proposedValue Can be an URL (including an URL to iTop itself), or a local path (CSV import) * {@inheritDoc}
* *
* @see AttributeDefinition::MakeRealValue() * @see AttributeDefinition::MakeRealValue()
*/ */
@@ -8417,7 +8300,7 @@ class AttributeBlob extends AttributeDefinition
$aValues[$this->GetCode().'_data'] = ''; $aValues[$this->GetCode().'_data'] = '';
$aValues[$this->GetCode().'_mimetype'] = ''; $aValues[$this->GetCode().'_mimetype'] = '';
$aValues[$this->GetCode().'_filename'] = ''; $aValues[$this->GetCode().'_filename'] = '';
$aValues[$this->GetCode().'_downloads_count'] = ormDocument::DEFAULT_DOWNLOADS_COUNT; $aValues[$this->GetCode().'_downloads_count'] = ''; // Note: Should this be set to \ormDocument::DEFAULT_DOWNLOADS_COUNT ?
} }
return $aValues; return $aValues;
@@ -8550,7 +8433,7 @@ class AttributeBlob extends AttributeDefinition
$sFingerprint = ''; $sFingerprint = '';
if ($value instanceOf ormDocument) if ($value instanceOf ormDocument)
{ {
$sFingerprint = $value->GetSignature(); $sFingerprint = md5($value->GetData());
} }
return $sFingerprint; return $sFingerprint;
@@ -8607,22 +8490,6 @@ class AttributeBlob extends AttributeDefinition
return utils::IsNotNullOrEmptyString($proposedValue->GetData()) && utils::IsNotNullOrEmptyString($proposedValue->GetFileName()); return utils::IsNotNullOrEmptyString($proposedValue->GetData()) && utils::IsNotNullOrEmptyString($proposedValue->GetFileName());
} }
/**
* @inheritDoc
* @param \ormDocument $original
* @param \ormDocument $value
* @since N°6502
*/
public function RecordAttChange(DBObject $oObject, $original, $value): void
{
// N°6502 Don't record history if only the download count has changed
if ((null !== $original) && (null !== $value) && $original->EqualsExceptDownloadsCount($value)) {
return;
}
parent::RecordAttChange($oObject, $original, $value);
}
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
{ {
if (is_null($original)) { if (is_null($original)) {
@@ -8740,7 +8607,7 @@ class AttributeImage extends AttributeBlob
$sCssClasses .= ' '.(($bIsCustomImage) ? 'attribute-image-custom' : 'attribute-image-default'); $sCssClasses .= ' '.(($bIsCustomImage) ? 'attribute-image-custom' : 'attribute-image-default');
// Important: If you change this, mind updating edit_image.js as well // Important: If you change this, mind updating edit_image.js as well
return '<div class="'.$sCssClasses.'" style="max-width: min('.$sMaxWidthPx.',100%); max-height: min('.$sMaxHeightPx.',100%); aspect-ratio: '.$iMaxWidth.' / '.$iMaxHeight.'">'.$sRet.'</div>'; return '<div class="'.$sCssClasses.'" style="max-width: '.$sMaxWidthPx.'; max-height: '.$sMaxHeightPx.'; aspect-ratio: '.$iMaxWidth.' / '.$iMaxHeight.'">'.$sRet.'</div>';
} }
/** /**
@@ -8754,7 +8621,7 @@ class AttributeImage extends AttributeBlob
* @since 2.7.0 change visibility to protected * @since 2.7.0 change visibility to protected
*/ */
protected function GetHtmlForImageUrl($sUrl, $iMaxWidthPx, $iMaxHeightPx) { protected function GetHtmlForImageUrl($sUrl, $iMaxWidthPx, $iMaxHeightPx) {
return '<img src="'.$sUrl.'" style="max-width: min('.$iMaxWidthPx.',100%); max-height: min('.$iMaxHeightPx.',100%)">'; return '<img src="'.$sUrl.'" style="max-width: '.$iMaxWidthPx.'; max-height: '.$iMaxHeightPx.'">';
} }
/** /**
@@ -8782,7 +8649,7 @@ class AttributeImage extends AttributeBlob
return 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData()); return 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
} }
return $value->GetDisplayURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode()); return $value->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode());
} }
public static function GetFormFieldClass() public static function GetFormFieldClass()
@@ -8809,11 +8676,8 @@ class AttributeImage extends AttributeBlob
} }
else else
{ {
$oDefaultImage = $this->Get('default_image'); $oFormField->SetDownloadUrl($this->Get('default_image'));
if (is_object($oDefaultImage) && !$oDefaultImage->IsEmpty()) { $oFormField->SetDisplayUrl($this->Get('default_image'));
$oFormField->SetDownloadUrl($oDefaultImage);
$oFormField->SetDisplayUrl($oDefaultImage);
}
} }
return $oFormField; return $oFormField;
@@ -11392,9 +11256,6 @@ class AttributeClassAttCodeSet extends AttributeSet
} }
$aAllowedAttributes[$sAttCode] = $sLabel; $aAllowedAttributes[$sAttCode] = $sLabel;
} }
// N°6460 Always sort on the labels, not on the datamodel definition order
natcasesort($aAllowedAttributes);
return $aAllowedAttributes; return $aAllowedAttributes;
} }
@@ -11508,11 +11369,6 @@ class AttributeClassAttCodeSet extends AttributeSet
} }
return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>'; return '<span class="'.implode(' ', $this->aCSSClasses).'">'.$value.'</span>';
} }
public function IsNull($proposedValue)
{
return (empty($proposedValue));
}
} }
class AttributeQueryAttCodeSet extends AttributeSet class AttributeQueryAttCodeSet extends AttributeSet
@@ -13192,7 +13048,6 @@ class AttributeCustomFields extends AttributeDefinition
public function GetHandler($aValues = null) public function GetHandler($aValues = null)
{ {
$sHandlerClass = $this->Get('handler_class'); $sHandlerClass = $this->Get('handler_class');
/** @var \TemplateFieldsHandler $oHandler */
$oHandler = new $sHandlerClass($this->GetCode()); $oHandler = new $sHandlerClass($this->GetCode());
if (!is_null($aValues)) if (!is_null($aValues))
{ {
@@ -13217,50 +13072,36 @@ class AttributeCustomFields extends AttributeDefinition
/** /**
* Makes the string representation out of the values given by the form defined in GetDisplayForm * Makes the string representation out of the values given by the form defined in GetDisplayForm
*/ */
public function ReadValueFromPostedForm($oHostObject, $sFormPrefix) { public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true); {
$aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
if ($aRawData != null) { if ($aRawData != null) {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData); return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
} } else{
else {
return null; return null;
} }
} }
public function MakeRealValue($proposedValue, $oHostObject) { public function MakeRealValue($proposedValue, $oHostObject)
if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue)) { {
if (false === $oHostObject->IsNew()) { if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue))
// In that case we need additional keys : see \TemplateFieldsHandler::DoBuildForm {
$aRequestTemplateValues = $proposedValue->GetValues();
if (false === array_key_exists('current_template_id', $aRequestTemplateValues)) {
$aRequestTemplateValues['current_template_id'] = $aRequestTemplateValues['template_id'];
$aRequestTemplateValues['current_template_data'] = $aRequestTemplateValues['template_data'];
$proposedValue = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRequestTemplateValues);
}
}
if (is_null($proposedValue->GetHostObject())) {
// the object might not be set : for example in \AttributeCustomFields::FromJSONToValue we don't have the object available :(
$proposedValue->SetHostObject($oHostObject);
}
return $proposedValue; return $proposedValue;
} }
elseif (is_string($proposedValue))
if (is_string($proposedValue)) { {
$aValues = json_decode($proposedValue, true); $aValues = json_decode($proposedValue, true);
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues); return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
} }
elseif (is_array($proposedValue))
if (is_array($proposedValue)) { {
return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $proposedValue); return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $proposedValue);
} }
elseif (is_null($proposedValue))
if (is_null($proposedValue)) { {
return new ormCustomFieldsValue($oHostObject, $this->GetCode()); return new ormCustomFieldsValue($oHostObject, $this->GetCode());
} }
throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue)); throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue));
} }
@@ -13385,11 +13226,27 @@ class AttributeCustomFields extends AttributeDefinition
*/ */
public function CheckValue(DBObject $oHostObject, $value) public function CheckValue(DBObject $oHostObject, $value)
{ {
try { try
{
$oHandler = $this->GetHandler($value->GetValues()); $oHandler = $this->GetHandler($value->GetValues());
$oHandler->BuildForm($oHostObject, ''); $oHandler->BuildForm($oHostObject, '');
$ret = $oHandler->Validate($oHostObject); $oForm = $oHandler->GetForm();
} catch (Exception $e) { $oForm->Validate();
if ($oForm->GetValid())
{
$ret = true;
}
else
{
$aMessages = array();
foreach($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages)
{
$aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages);
}
$ret = 'Invalid value: '.implode(', ', $aMessages);
}
} catch (Exception $e)
{
$ret = $e->getMessage(); $ret = $e->getMessage();
} }
@@ -13531,13 +13388,11 @@ class AttributeCustomFields extends AttributeDefinition
/** /**
* @inheritDoc * @inheritDoc
* *
* @return ?\ormCustomFieldsValue with empty host object as we don't have it here (most consumers don't have an object in their context, for example in \RestUtils::GetObjectSetFromKey) * @return ?\ormCustomFieldsValue
* The host object will be set in {@see MakeRealValue}
* All the necessary checks will be done in {@see CheckValue}
*/ */
public function FromJSONToValue($json) public function FromJSONToValue($json)
{ {
return ormCustomFieldsValue::FromJSONToValue($json, $this); return null;
} }
public function Equals($val1, $val2) public function Equals($val1, $val2)

View File

@@ -35,7 +35,6 @@ MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
MetaModel::IncludeModule('core/backgroundtask.class.inc.php'); MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
MetaModel::IncludeModule('core/inlineimage.class.inc.php'); MetaModel::IncludeModule('core/inlineimage.class.inc.php');
MetaModel::IncludeModule('core/counter.class.inc.php'); MetaModel::IncludeModule('core/counter.class.inc.php');
MetaModel::IncludeModule('core/TemporaryObjectDescriptor.php');
MetaModel::IncludeModule('webservices/webservices.basic.php'); MetaModel::IncludeModule('webservices/webservices.basic.php');

View File

@@ -32,57 +32,23 @@ abstract class CellChangeSpec
return $this->m_proposedValue; return $this->m_proposedValue;
} }
/** public function GetDisplayableValue()
* @throws \Exception
* @since 3.2.0
*/
public function GetCLIValue(bool $bLocalizedValues = false): string
{ {
if (is_object($this->m_proposedValue)) {
if ($this->m_proposedValue instanceof ReportValue) {
return $this->m_proposedValue->GetAsCSV($bLocalizedValues, ',', '"');
}
throw new Exception('Unexpected class : '. get_class($this->m_proposedValue));
}
return $this->m_proposedValue; return $this->m_proposedValue;
} }
/**
* @throws \Exception
* @since 3.2.0
*/
public function GetHTMLValue(bool $bLocalizedValues = false): string
{
if (is_object($this->m_proposedValue)) {
if ($this->m_proposedValue instanceof ReportValue) {
return $this->m_proposedValue->GetAsHTML($bLocalizedValues);
}
throw new Exception('Unexpected class : '. get_class($this->m_proposedValue));
}
return utils::EscapeHtml($this->m_proposedValue);
}
/**
* @since 3.1.0 N°5305
*/
public function SetDisplayableValue(string $sDisplayableValue)
{
$this->m_proposedValue = $sDisplayableValue;
}
public function GetOql() public function GetOql()
{ {
return $this->m_sOql; return $this->m_sOql;
} }
/** /**
* @since 3.2.0 * @since 3.1.0 N°5305
*/ */
public function GetCLIValueAndDescription(): string public function GetDisplayableValueAndDescription(): string
{ {
return sprintf("%s%s", return sprintf("%s%s",
$this->GetCLIValue(), $this->GetDisplayableValue(),
$this->GetDescription() $this->GetDescription()
); );
} }
@@ -131,25 +97,13 @@ class CellStatus_Issue extends CellStatus_Modify
parent::__construct($proposedValue, $previousValue); parent::__construct($proposedValue, $previousValue);
} }
public function GetCLIValue(bool $bLocalizedValues = false): string public function GetDisplayableValue()
{
if (is_null($this->m_proposedValue)) {
return Dict::Format('UI:CSVReport-Value-SetIssue');
}
return Dict::Format('UI:CSVReport-Value-ChangeIssue',$this->m_proposedValue);
}
public function GetHTMLValue(bool $bLocalizedValues = false): string
{ {
if (is_null($this->m_proposedValue)) if (is_null($this->m_proposedValue))
{ {
return Dict::Format('UI:CSVReport-Value-SetIssue'); return Dict::Format('UI:CSVReport-Value-SetIssue');
} }
if ($this->m_proposedValue instanceof ReportValue) return Dict::Format('UI:CSVReport-Value-ChangeIssue', \utils::EscapeHtml($this->m_proposedValue));
{
return Dict::Format('UI:CSVReport-Value-ChangeIssue', $this->m_proposedValue->GetAsHTML($bLocalizedValues));
}
return Dict::Format('UI:CSVReport-Value-ChangeIssue',utils::EscapeHtml($this->m_proposedValue));
} }
public function GetDescription() public function GetDescription()
@@ -157,12 +111,12 @@ class CellStatus_Issue extends CellStatus_Modify
return $this->m_sReason; return $this->m_sReason;
} }
/* /*
* @since 3.2.0 * @since 3.1.0 N°5305
*/ */
public function GetCLIValueAndDescription(): string public function GetDisplayableValueAndDescription(): string
{ {
return sprintf("%s. %s", return sprintf("%s. %s",
$this->GetCLIValue(), $this->GetDisplayableValue(),
$this->GetDescription() $this->GetDescription()
); );
} }
@@ -182,12 +136,6 @@ class CellStatus_SearchIssue extends CellStatus_Issue
/** @var string|null $m_sTargetClass */ /** @var string|null $m_sTargetClass */
private $m_sTargetClass; private $m_sTargetClass;
/**
* @since 3.1.0 N°5305
* @var string $sAllowedValuesSearch
*/
private $sAllowedValuesSearch;
/** /**
* CellStatus_SearchIssue constructor. * CellStatus_SearchIssue constructor.
* @since 3.1.0 N°5305 * @since 3.1.0 N°5305
@@ -196,18 +144,16 @@ class CellStatus_SearchIssue extends CellStatus_Issue
* @param string $sReason : main message * @param string $sReason : main message
* @param null $sClass : used for additional message that provides allowed values for current class $sClass * @param null $sClass : used for additional message that provides allowed values for current class $sClass
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class * @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
* @param string|null $sAllowedValuesSearch : used to search all allowed values
*/ */
public function __construct($sSerializedSearch, $sReason, $sClass = null, $sAllowedValues = null, string $sAllowedValuesSearch = null) public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null)
{ {
parent::__construct(null, null, $sReason); parent::__construct(null, null, $sReason);
$this->sSerializedSearch = $sSerializedSearch; $this->sSerializedSearch = $sSerializedSearch;
$this->m_sAllowedValues = $sAllowedValues; $this->m_sAllowedValues = $sAllowedValues;
$this->m_sTargetClass = $sClass; $this->m_sTargetClass = $sClass;
$this->sAllowedValuesSearch = $sAllowedValuesSearch;
} }
public function GetCLIValue(bool $bLocalizedValues = false): string public function GetDisplayableValue()
{ {
if (null === $this->m_sReason) { if (null === $this->m_sReason) {
return Dict::Format('UI:CSVReport-Value-NoMatch', ''); return Dict::Format('UI:CSVReport-Value-NoMatch', '');
@@ -216,15 +162,6 @@ class CellStatus_SearchIssue extends CellStatus_Issue
return $this->m_sReason; return $this->m_sReason;
} }
public function GetHTMLValue(bool $bLocalizedValues = false): string
{
if (null === $this->m_sReason) {
return Dict::Format('UI:CSVReport-Value-NoMatch', '');
}
return utils::EscapeHtml($this->m_sReason);
}
public function GetDescription() public function GetDescription()
{ {
if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) || if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) ||
@@ -242,18 +179,7 @@ class CellStatus_SearchIssue extends CellStatus_Issue
public function GetSearchLinkUrl() public function GetSearchLinkUrl()
{ {
return sprintf("UI.php?operation=search&filter=%s", return sprintf("UI.php?operation=search&filter=%s",
rawurlencode($this->sSerializedSearch ?? "") rawurlencode($this->sSerializedSearch)
);
}
/**
* @since 3.1.0 N°5305
* @return null|string
*/
public function GetAllowedValuesLinkUrl(): ?string
{
return sprintf("UI.php?operation=search&filter=%s",
rawurlencode($this->sAllowedValuesSearch ?? "")
); );
} }
} }
@@ -271,42 +197,6 @@ class CellStatus_NullIssue extends CellStatus_Issue
} }
} }
/**
* Class to differ formatting depending on the caller
*/
class ReportValue
{
protected DBObject $oObject;
protected string $sAttCode;
protected bool $bOriginal;
/**
* @param DBObject $oObject
* @param string $sAttCode
* @param bool $bOriginal
*/
public function __construct(DBObject $oObject, string $sAttCode, bool $bOriginal)
{
$this->oObject = $oObject;
$this->sAttCode = $sAttCode;
$this->bOriginal = $bOriginal;
}
public function GetAsHTML(bool $bLocalizedValues)
{
if ($this->bOriginal) {
return $this->oObject->GetOriginalAsHTML($this->sAttCode, $bLocalizedValues);
}
return $this->oObject->GetAsHTML($this->sAttCode, $bLocalizedValues);
}
public function GetAsCSV (bool $bLocalizedValues, string $sCsvSep, string $sCsvDelimiter) {
if ($this->bOriginal) {
return $this->oObject->GetOriginalAsCSV($this->sAttCode, $sCsvSep, $sCsvDelimiter, $bLocalizedValues);
}
return $this->oObject->GetAsCSV($this->sAttCode, $sCsvSep, $sCsvDelimiter, $bLocalizedValues);
}
}
class CellStatus_Ambiguous extends CellStatus_Issue class CellStatus_Ambiguous extends CellStatus_Issue
{ {
@@ -345,7 +235,7 @@ class CellStatus_Ambiguous extends CellStatus_Issue
public function GetSearchLinkUrl() public function GetSearchLinkUrl()
{ {
return sprintf("UI.php?operation=search&filter=%s", return sprintf("UI.php?operation=search&filter=%s",
rawurlencode($this->sSerializedSearch ?? "") rawurlencode($this->sSerializedSearch)
); );
} }
} }
@@ -493,6 +383,22 @@ class BulkChange
$this->m_aExtKeysMappingCache = array(); $this->m_aExtKeysMappingCache = array();
} }
protected $m_bReportHtml = false;
protected $m_sReportCsvSep = ',';
protected $m_sReportCsvDelimiter = '"';
public function SetReportHtml()
{
$this->m_bReportHtml = true;
}
public function SetReportCsv($sSeparator = ',', $sDelimiter = '"')
{
$this->m_bReportHtml = false;
$this->m_sReportCsvSep = $sSeparator;
$this->m_sReportCsvDelimiter = $sDelimiter;
}
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults) protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
{ {
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); $oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
@@ -510,7 +416,7 @@ class BulkChange
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues); $value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
} }
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '='); $oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]); $aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
$oExtObjects = new CMDBObjectSet($oReconFilter); $oExtObjects = new CMDBObjectSet($oReconFilter);
@@ -534,7 +440,7 @@ class BulkChange
} }
/** /**
* @param DBObject $oTargetObj * @param \DBObject $oTargetObj
* @param array $aRowData * @param array $aRowData
* @param array $aErrors * @param array $aErrors
* *
@@ -597,7 +503,7 @@ class BulkChange
} }
$aCacheKeys[] = $value; $aCacheKeys[] = $value;
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '='); $oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]); $aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query... $sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
$iForeignKey = null; $iForeignKey = null;
@@ -666,7 +572,7 @@ class BulkChange
foreach ($aReconKeys as $sReconKeyAttCode => $iCol) foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
{ {
// Report the change on reconciliation values as well // Report the change on reconciliation values as well
$aResults[$iCol] = new CellStatus_Modify($aRowData[$iCol]); $aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
} }
} }
} }
@@ -744,32 +650,50 @@ class BulkChange
// Reporting on fields // Reporting on fields
// //
$aChangedFields = $oTargetObj->ListChanges(); $aChangedFields = $oTargetObj->ListChanges();
foreach ($this->m_aAttList as $sAttCode => $iCol) { foreach ($this->m_aAttList as $sAttCode => $iCol)
if ($sAttCode == 'id') { {
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); if ($sAttCode == 'id')
{
$aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
else { else
$sCurValue = new ReportValue($oTargetObj, $sAttCode, false); {
$sOrigValue = new ReportValue($oTargetObj, $sAttCode, true); if ($this->m_bReportHtml)
if (isset($aErrors[$sAttCode])) { {
$aResults[$iCol]= new CellStatus_Issue($aRowData[$iCol], $sOrigValue, $aErrors[$sAttCode]); $sCurValue = $oTargetObj->GetAsHTML($sAttCode, $this->m_bLocalizedValues);
$sOrigValue = $oTargetObj->GetOriginalAsHTML($sAttCode, $this->m_bLocalizedValues);
} }
elseif (array_key_exists($sAttCode, $aChangedFields)){ else
if ($oTargetObj->IsNew()) { {
$sCurValue = $oTargetObj->GetAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
$sOrigValue = $oTargetObj->GetOriginalAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
}
if (isset($aErrors[$sAttCode]))
{
$aResults[$iCol]= new CellStatus_Issue(utils::HtmlEntities($aRowData[$iCol]), $sOrigValue, $aErrors[$sAttCode]);
}
elseif (array_key_exists($sAttCode, $aChangedFields))
{
if ($oTargetObj->IsNew())
{
$aResults[$iCol]= new CellStatus_Void($sCurValue); $aResults[$iCol]= new CellStatus_Void($sCurValue);
} }
else { else
{
$aResults[$iCol]= new CellStatus_Modify($sCurValue, $sOrigValue); $aResults[$iCol]= new CellStatus_Modify($sCurValue, $sOrigValue);
} }
} }
else { else
{
// By default... nothing happens // By default... nothing happens
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
if ($oAttDef instanceof AttributeDateTime) { if ($oAttDef instanceof AttributeDateTime)
{
$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol])); $aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
} }
else { else
$aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]); {
$aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
} }
} }
@@ -821,7 +745,6 @@ class BulkChange
$oDbSearchWithoutAnyCondition->AllowAllData(false); $oDbSearchWithoutAnyCondition->AllowAllData(false);
$oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition); $oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
$iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count(); $iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count();
$sAllowedValuesOql = $oDbSearchWithoutAnyCondition->serialize();
if ($iCurrentUserRightsObjectCount === 0){ if ($iCurrentUserRightsObjectCount === 0){
// No objects visible by current user // No objects visible by current user
@@ -834,7 +757,7 @@ class BulkChange
// Possibles values are displayed to UI user. we have to limit the amount of displayed values // Possibles values are displayed to UI user. we have to limit the amount of displayed values
$oExtObjectSetWithCurrentUserPermissions->SetLimit(4); $oExtObjectSetWithCurrentUserPermissions->SetLimit(4);
for($i = 0; $i < 3; $i++){ for($i = 0; $i < 3; $i++){
/** @var DBObject $oVisibleObject */ /** @var \DBObject $oVisibleObject */
$oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch(); $oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch();
if (is_null($oVisibleObject)){ if (is_null($oVisibleObject)){
break; break;
@@ -862,7 +785,7 @@ class BulkChange
if ($iAllowAllDataObjectCount != $iCurrentUserRightsObjectCount) { if ($iAllowAllDataObjectCount != $iCurrentUserRightsObjectCount) {
// No match and some objects NOT visible by current user. including current search maybe... // No match and some objects NOT visible by current user. including current search maybe...
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser', $oDbSearchWithConditions->GetClass()); $sReason = Dict::Format('UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser', $oDbSearchWithConditions->GetClass());
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues, $sAllowedValuesOql); return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
} }
// No match. This is not linked to any right issue // No match. This is not linked to any right issue
@@ -873,7 +796,7 @@ class BulkChange
} }
$value =implode(" ", $aCurrentValueFields); $value =implode(" ", $aCurrentValueFields);
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value); $sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value);
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues, $sAllowedValuesOql); return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
} }
protected function PrepareMissingObject(&$oTargetObj, &$aErrors) protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
@@ -1201,7 +1124,7 @@ class BulkChange
if (!preg_match($sRegExp, $sValue)) if (!preg_match($sRegExp, $sValue))
{ {
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat')); $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg); $aResult[$iRow][$iCol] = new CellStatus_Issue(utils::HtmlEntities($sValue), null, $sErrorMsg);
} }
else else
@@ -1214,7 +1137,6 @@ class BulkChange
} }
else else
{ {
// almost impossible ti reproduce since even incorrect dates with correct formats are formated and $oDate will not be false
// Leave the cell unchanged // Leave the cell unchanged
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat')); $aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg); $aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
@@ -1354,7 +1276,7 @@ class BulkChange
{ {
if (!array_key_exists($iCol, $aResult[$iRow])) if (!array_key_exists($iCol, $aResult[$iRow]))
{ {
$aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); $aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
} }
foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts) foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts)
@@ -1368,7 +1290,7 @@ class BulkChange
if (!array_key_exists($iCol, $aResult[$iRow])) if (!array_key_exists($iCol, $aResult[$iRow]))
{ {
// The foreign attribute is one of our reconciliation key // The foreign attribute is one of our reconciliation key
$aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]); $aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
} }
} }
} }

View File

@@ -149,9 +149,7 @@ abstract class BulkExport
$this->oSearch = null; $this->oSearch = null;
$this->iChunkSize = 0; $this->iChunkSize = 0;
$this->sFormatCode = null; $this->sFormatCode = null;
$this->aStatusInfo = [ $this->aStatusInfo = array();
'show_obsolete_data' => utils::ShowObsoleteData(),
];
$this->oBulkExportResult = null; $this->oBulkExportResult = null;
$this->sTmpFile = ''; $this->sTmpFile = '';
$this->bLocalizeOutput = false; $this->bLocalizeOutput = false;
@@ -205,17 +203,15 @@ abstract class BulkExport
if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId())) if ($oInfo && ($oInfo->Get('user_id') == UserRights::GetUserId()))
{ {
$sFormatCode = $oInfo->Get('format'); $sFormatCode = $oInfo->Get('format');
$aStatusInfo = json_decode($oInfo->Get('status_info'),true);
$oSearch = DBObjectSearch::unserialize($oInfo->Get('search')); $oSearch = DBObjectSearch::unserialize($oInfo->Get('search'));
$oSearch->SetShowObsoleteData($aStatusInfo['show_obsolete_data']);
$oBulkExporter = self::FindExporter($sFormatCode, $oSearch); $oBulkExporter = self::FindExporter($sFormatCode, $oSearch);
if ($oBulkExporter) if ($oBulkExporter)
{ {
$oBulkExporter->SetFormat($sFormatCode); $oBulkExporter->SetFormat($sFormatCode);
$oBulkExporter->SetObjectList($oSearch); $oBulkExporter->SetObjectList($oSearch);
$oBulkExporter->SetChunkSize($oInfo->Get('chunk_size')); $oBulkExporter->SetChunkSize($oInfo->Get('chunk_size'));
$oBulkExporter->SetStatusInfo($aStatusInfo); $oBulkExporter->SetStatusInfo(json_decode($oInfo->Get('status_info'), true));
$oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output')); $oBulkExporter->SetLocalizeOutput($oInfo->Get('localize_output'));
@@ -293,7 +289,6 @@ abstract class BulkExport
*/ */
public function SetObjectList(DBSearch $oSearch) public function SetObjectList(DBSearch $oSearch)
{ {
$oSearch->SetShowObsoleteData($this->aStatusInfo['show_obsolete_data']);
$this->oSearch = $oSearch; $this->oSearch = $oSearch;
} }

View File

@@ -19,17 +19,17 @@ class CMDBChange extends DBObject
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "date", "name_attcode" => "date",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_change", "db_table" => "priv_change",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
'indexes' => array( 'indexes' => array(
array('origin'), array('origin'),
), )
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();

View File

@@ -52,17 +52,17 @@ class CMDBChangeOp extends DBObject implements iCMDBChangeOp
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop", "db_table" => "priv_changeop",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "optype", "db_finalclass_field" => "optype",
'indexes' => array( 'indexes' => array(
array('objclass', 'objkey'), array('objclass', 'objkey'),
), )
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes(); //MetaModel::Init_InheritAttributes();
@@ -121,13 +121,13 @@ class CMDBChangeOpCreate extends CMDBChangeOp
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_create", "db_table" => "priv_changeop_create",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -157,13 +157,13 @@ class CMDBChangeOpDelete extends CMDBChangeOp
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_delete", "db_table" => "priv_changeop_delete",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -198,13 +198,13 @@ class CMDBChangeOpSetAttribute extends CMDBChangeOp
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt", "db_table" => "priv_changeop_setatt",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -231,13 +231,13 @@ class CMDBChangeOpSetAttributeScalar extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_scalar", "db_table" => "priv_changeop_setatt_scalar",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -290,14 +290,14 @@ class CMDBChangeOpSetAttributeTagSet extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_tagset", "db_table" => "priv_changeop_setatt_tagset",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritAttributes();
@@ -350,7 +350,7 @@ class CMDBChangeOpSetAttributeURL extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
@@ -419,13 +419,13 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_data", "db_table" => "priv_changeop_setatt_data",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -498,13 +498,13 @@ class CMDBChangeOpSetAttributeOneWayPassword extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_pwd", "db_table" => "priv_changeop_setatt_pwd",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -561,13 +561,13 @@ class CMDBChangeOpSetAttributeEncrypted extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_encrypted", "db_table" => "priv_changeop_setatt_encrypted",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -627,13 +627,13 @@ class CMDBChangeOpSetAttributeText extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_text", "db_table" => "priv_changeop_setatt_text",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -695,13 +695,13 @@ class CMDBChangeOpSetAttributeLongText extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_longtext", "db_table" => "priv_changeop_setatt_longtext",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -760,13 +760,13 @@ class CMDBChangeOpSetAttributeHTML extends CMDBChangeOpSetAttributeLongText
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_html", "db_table" => "priv_changeop_setatt_html",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -824,13 +824,13 @@ class CMDBChangeOpSetAttributeCaseLog extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_log", "db_table" => "priv_changeop_setatt_log",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -903,13 +903,13 @@ class CMDBChangeOpPlugin extends CMDBChangeOp
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_plugin", "db_table" => "priv_changeop_plugin",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -944,13 +944,13 @@ abstract class CMDBChangeOpSetAttributeLinks extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_links", "db_table" => "priv_changeop_links",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -977,13 +977,13 @@ class CMDBChangeOpSetAttributeLinksAddRemove extends CMDBChangeOpSetAttributeLin
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_links_addremove", "db_table" => "priv_changeop_links_addremove",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -1044,13 +1044,13 @@ class CMDBChangeOpSetAttributeLinksTune extends CMDBChangeOpSetAttributeLinks
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_links_tune", "db_table" => "priv_changeop_links_tune",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
@@ -1134,13 +1134,13 @@ class CMDBChangeOpSetAttributeCustomFields extends CMDBChangeOpSetAttribute
{ {
$aParams = array $aParams = array
( (
"category" => "core/cmdb, grant_by_profile", "category" => "core/cmdb",
"key_type" => "", "key_type" => "",
"name_attcode" => "change", "name_attcode" => "change",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array(), "reconc_keys" => array(),
"db_table" => "priv_changeop_setatt_custfields", "db_table" => "priv_changeop_setatt_custfields",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);

View File

@@ -335,7 +335,7 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this))); $oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this)));
$oMyChangeOp->Set("objkey", $objkey); $oMyChangeOp->Set("objkey", $objkey);
$oMyChangeOp->Set("fclass", get_class($this)); $oMyChangeOp->Set("fclass", get_class($this));
$oMyChangeOp->SetTrim("fname", $this->GetRawName()); // Protect against very long friendly names $oMyChangeOp->Set("fname", substr($this->GetRawName(), 0, 255)); // Protect against very long friendly names
$iId = $oMyChangeOp->DBInsertNoReload(); $iId = $oMyChangeOp->DBInsertNoReload();
} }
@@ -438,8 +438,7 @@ abstract class CMDBObject extends DBObject
} }
/** /**
* @deprecated 3.1.0 N°5232 N°6966 simply use {@see DBObject::DBClone()} instead, that will automatically create and persist a CMDBChange object. * @deprecated 3.1.0 N°5232 not used
* If you need to persist your own, call {@see CMDBObject::SetCurrentChange()} before.
*/ */
public function DBCloneTracked(CMDBChange $oChange, $newKey = null) public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
{ {
@@ -447,9 +446,6 @@ abstract class CMDBObject extends DBObject
$this->DBCloneTracked_Internal($newKey); $this->DBCloneTracked_Internal($newKey);
} }
/**
* @deprecated 3.1.1 3.2.0 N°6966 We will have only one DBClone method in the future
*/
protected function DBCloneTracked_Internal($newKey = null) protected function DBCloneTracked_Internal($newKey = null)
{ {
$newKey = parent::DBClone($newKey); $newKey = parent::DBClone($newKey);
@@ -473,14 +469,21 @@ abstract class CMDBObject extends DBObject
*/ */
public function DBDelete(&$oDeletionPlan = null) public function DBDelete(&$oDeletionPlan = null)
{ {
$this->LogCRUDEnter(__METHOD__); return $this->DBDeleteTracked_Internal($oDeletionPlan);
$oDeletionPlan = parent::DBDelete($oDeletionPlan);
$this->LogCRUDExit(__METHOD__);
return $oDeletionPlan;
} }
/** /**
* @deprecated 3.1.1 3.2.0 N°6967 We will have only one DBDelete method in the future * @param null $oDeletionPlan
*
* @return \DeletionPlan|null
* @throws \ArchivedObjectException
* @throws \CoreCannotSaveObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \DeleteException
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/ */
protected function DBDeleteTracked_Internal(&$oDeletionPlan = null) protected function DBDeleteTracked_Internal(&$oDeletionPlan = null)
{ {

View File

@@ -3,7 +3,7 @@
// //
// This file is part of iTop. // This file is part of iTop.
// //
// iTop is free software; you can redistribute it and/or modify // iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // 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 // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
@@ -42,12 +42,6 @@ class CMDBSource
const ENUM_DB_VENDOR_MARIADB = 'MariaDB'; const ENUM_DB_VENDOR_MARIADB = 'MariaDB';
const ENUM_DB_VENDOR_PERCONA = 'Percona'; const ENUM_DB_VENDOR_PERCONA = 'Percona';
/**
* @since 2.7.10 3.0.4 3.1.2 3.0.2 N°6889 constant creation
* @internal will be removed in a future version
*/
const MYSQL_DEFAULT_PORT = 3306;
/** /**
* Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT) * Error: 1205 SQLSTATE: HY000 (ER_LOCK_WAIT_TIMEOUT)
* Message: Lock wait timeout exceeded; try restarting transaction * Message: Lock wait timeout exceeded; try restarting transaction
@@ -218,19 +212,16 @@ class CMDBSource
/** /**
* @param string $sDbHost initial value ("p:domain:port" syntax) * @param string $sDbHost initial value ("p:domain:port" syntax)
* @param string $sServer server variable to update * @param string $sServer server variable to update
* @param int|null $iPort port variable to update, will return null if nothing is specified in $sDbHost * @param int $iPort port variable to update
*
* @since 2.7.10 3.0.4 3.1.2 3.2.0 N°6889 will return null in $iPort if port isn't present in $sDbHost. Use {@see MYSQL_DEFAULT_PORT} if needed
*
* @link http://php.net/manual/en/mysqli.persistconns.php documentation for the "p:" prefix (persistent connexion)
*/ */
public static function InitServerAndPort($sDbHost, &$sServer, &$iPort) public static function InitServerAndPort($sDbHost, &$sServer, &$iPort)
{ {
$aConnectInfo = explode(':', $sDbHost); $aConnectInfo = explode(':', $sDbHost);
$bUsePersistentConnection = false; $bUsePersistentConnection = false;
if (strcasecmp($aConnectInfo[0], 'p') === 0) if (strcasecmp($aConnectInfo[0], 'p') == 0)
{ {
// we might have "p:" prefix to use persistent connections (see http://php.net/manual/en/mysqli.persistconns.php)
$bUsePersistentConnection = true; $bUsePersistentConnection = true;
$sServer = $aConnectInfo[0].':'.$aConnectInfo[1]; $sServer = $aConnectInfo[0].':'.$aConnectInfo[1];
} }
@@ -248,6 +239,10 @@ class CMDBSource
{ {
$iPort = (int)($aConnectInfo[1]); $iPort = (int)($aConnectInfo[1]);
} }
else
{
$iPort = 3306;
}
} }
/** /**
@@ -385,7 +380,7 @@ class CMDBSource
public static function GetDBVendor() public static function GetDBVendor()
{ {
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL; $sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
$sVersionComment = static::GetServerVariable('version') . ' - ' . static::GetServerVariable('version_comment'); $sVersionComment = static::GetServerVariable('version') . ' - ' . static::GetServerVariable('version_comment');
if(preg_match('/mariadb/i', $sVersionComment) === 1) if(preg_match('/mariadb/i', $sVersionComment) === 1)
{ {
@@ -395,7 +390,7 @@ class CMDBSource
{ {
$sDBVendor = static::ENUM_DB_VENDOR_PERCONA; $sDBVendor = static::ENUM_DB_VENDOR_PERCONA;
} }
return $sDBVendor; return $sDBVendor;
} }
@@ -436,7 +431,6 @@ class CMDBSource
{ {
self::$m_sDBName = ''; self::$m_sDBName = '';
} }
self::_TablesInfoCacheReset(); // reset the table info cache!
} }
public static function CreateTable($sQuery) public static function CreateTable($sQuery)
@@ -549,9 +543,10 @@ class CMDBSource
/** /**
* @param string $sSQLQuery * @param string $sSQLQuery
* *
* @return mysqli_result|null * @return \mysqli_result|null
* @throws MySQLException * @throws \MySQLException
* @throws MySQLHasGoneAwayException * @throws \MySQLHasGoneAwayException
* @throws \CoreException
* *
* @since 2.7.0 N°679 handles nested transactions * @since 2.7.0 N°679 handles nested transactions
*/ */
@@ -612,9 +607,8 @@ class CMDBSource
{ {
self::LogDeadLock($e, true); self::LogDeadLock($e, true);
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e)); throw new MySQLException('Failed to issue SQL query', array('query' => $sSql, $e));
} finally { }
$oKPI->ComputeStats('Query exec (mySQL)', $sSql); $oKPI->ComputeStats('Query exec (mySQL)', $sSql);
}
if ($oResult === false) { if ($oResult === false) {
$aContext = array('query' => $sSql); $aContext = array('query' => $sSql);
@@ -632,24 +626,18 @@ class CMDBSource
} }
/** /**
* @param Exception $e * @param \Exception $e
* @param bool $bForQuery to get the proper DB connection * @param bool $bForQuery to get the proper DB connection
* @param bool $bCheckMysqliErrno if false won't try to check for mysqli::errno value
* *
* @since 2.7.1 * @since 2.7.1
* @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection * @since 3.0.0 N°4325 add new optional parameter to use the correct DB connection
* @since 3.0.4 3.1.1 3.2.0 N°6643 new bCheckMysqliErrno parameter as a workaround for mysqli::errno cannot be mocked
*/ */
private static function LogDeadLock(Exception $e, $bForQuery = false, $bCheckMysqliErrno = true) private static function LogDeadLock(Exception $e, $bForQuery = false)
{ {
// checks MySQL error code // checks MySQL error code
if ($bCheckMysqliErrno) { $iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno;
$iMySqlErrorNo = DbConnectionWrapper::GetDbConnection($bForQuery)->errno; if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) {
if (!in_array($iMySqlErrorNo, array(self::MYSQL_ERRNO_WAIT_TIMEOUT, self::MYSQL_ERRNO_DEADLOCK))) { return;
return;
}
} else {
$iMySqlErrorNo = "N/A";
} }
// Get error info // Get error info
@@ -676,10 +664,7 @@ class CMDBSource
); );
DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext); DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext);
IssueLog::Error($sMessage, LogChannels::DEADLOCK, [ IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage());
'exception.class' => get_class($e),
'exception.message' => $e->getMessage(),
]);
} }
/** /**
@@ -938,7 +923,7 @@ class CMDBSource
{ {
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql)); throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
} }
while ($aRow = $oResult->fetch_array($iMode)) while ($aRow = $oResult->fetch_array($iMode))
{ {
$aData[] = $aRow; $aData[] = $aRow;
@@ -1092,7 +1077,7 @@ class CMDBSource
if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false; if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false;
$aFieldData = $aTableInfo["Fields"][$iKey]; $aFieldData = $aTableInfo["Fields"][$iKey];
if (!array_key_exists("Key", $aFieldData)) return false; if (!array_key_exists("Key", $aFieldData)) return false;
return ($aFieldData["Key"] == "PRI"); return ($aFieldData["Key"] == "PRI");
} }
public static function IsAutoIncrement($sTable, $sField) public static function IsAutoIncrement($sTable, $sField)
@@ -1103,7 +1088,7 @@ class CMDBSource
$aFieldData = $aTableInfo["Fields"][$sField]; $aFieldData = $aTableInfo["Fields"][$sField];
if (!array_key_exists("Extra", $aFieldData)) return false; if (!array_key_exists("Extra", $aFieldData)) return false;
//MyHelpers::debug_breakpoint($aFieldData); //MyHelpers::debug_breakpoint($aFieldData);
return (strstr($aFieldData["Extra"], "auto_increment")); return (strstr($aFieldData["Extra"], "auto_increment"));
} }
public static function IsField($sTable, $sField) public static function IsField($sTable, $sField)
@@ -1169,8 +1154,8 @@ class CMDBSource
*/ */
public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType) public static function IsSameFieldTypes($sItopGeneratedFieldType, $sDbFieldType)
{ {
[$sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType); list($sItopFieldDataType, $sItopFieldTypeOptions, $sItopFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sItopGeneratedFieldType);
[$sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions] = static::GetFieldDataTypeAndOptions($sDbFieldType); list($sDbFieldDataType, $sDbFieldTypeOptions, $sDbFieldOtherOptions) = static::GetFieldDataTypeAndOptions($sDbFieldType);
if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0) if (strcasecmp($sItopFieldDataType, $sDbFieldDataType) !== 0)
{ {
@@ -1370,13 +1355,13 @@ class CMDBSource
public static function GetTableFieldsList($sTable) public static function GetTableFieldsList($sTable)
{ {
assert(!empty($sTable)); assert(!empty($sTable));
$aTableInfo = self::GetTableInfo($sTable); $aTableInfo = self::GetTableInfo($sTable);
if (empty($aTableInfo)) return array(); // #@# or an error ? if (empty($aTableInfo)) return array(); // #@# or an error ?
return array_keys($aTableInfo["Fields"]); return array_keys($aTableInfo["Fields"]);
} }
// Cache the information about existing tables, and their fields // Cache the information about existing tables, and their fields
private static $m_aTablesInfo = array(); private static $m_aTablesInfo = array();
private static function _TablesInfoCacheReset($sTableName = null) private static function _TablesInfoCacheReset($sTableName = null)
@@ -1509,7 +1494,7 @@ class CMDBSource
{ {
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql)); throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
} }
$aRows = array(); $aRows = array();
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC)) while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
{ {
@@ -1518,7 +1503,7 @@ class CMDBSource
$oResult->free(); $oResult->free();
return $aRows; return $aRows;
} }
/** /**
* Returns the value of the specified server variable * Returns the value of the specified server variable
* @param string $sVarName Name of the server variable * @param string $sVarName Name of the server variable
@@ -1534,7 +1519,7 @@ class CMDBSource
/** /**
* Returns the privileges of the current user * Returns the privileges of the current user
* @return string privileges in a raw format * @return string privileges in a raw format
*/ */
public static function GetRawPrivileges() public static function GetRawPrivileges()
{ {
try try
@@ -1560,8 +1545,8 @@ class CMDBSource
/** /**
* Determine the slave status of the server * Determine the slave status of the server
* @return bool true if the server is slave * @return bool true if the server is slave
*/ */
public static function IsSlaveServer() public static function IsSlaveServer()
{ {
try try
@@ -1603,19 +1588,7 @@ class CMDBSource
return false; return false;
} }
public static function GetClusterNb() /**
{
$result = 0;
$sSql = "SHOW STATUS LIKE 'wsrep_cluster_size';";
$aRows = self::QueryToArray($sSql);
if (count($aRows) > 0)
{
$result = $aRows[0]['Value'];
}
return intval($result);
}
/**
* @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html * @see https://dev.mysql.com/doc/refman/5.7/en/charset-database.html
* @return string query to upgrade database charset and collation if needed, null if not * @return string query to upgrade database charset and collation if needed, null if not
* @throws \MySQLException * @throws \MySQLException

View File

@@ -29,7 +29,7 @@ define('ITOP_APPLICATION_SHORT', 'iTop');
* *
* @see ITOP_CORE_VERSION to get iTop core version * @see ITOP_CORE_VERSION to get iTop core version
*/ */
define('ITOP_VERSION', '3.1.1-dev'); define('ITOP_VERSION', '3.1.0-dev');
define('ITOP_VERSION_NAME', 'Fullmoon'); define('ITOP_VERSION_NAME', 'Fullmoon');
define('ITOP_REVISION', 'svn'); define('ITOP_REVISION', 'svn');
@@ -137,7 +137,7 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'log_purge.max_keep_days' => [ 'log_purge.max_keep_days' => [
'type' => 'integer', 'type' => 'integer',
'description' => 'Optional purge number of days to keep logs.', 'description' => 'Optional purge number of days to keep logs.',
'default' => 365, 'default' => 365,
@@ -145,7 +145,7 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'event_service.debug.filter_events' => [ 'event_service.debug.filter_events' => [
'type' => 'array', 'type' => 'array',
'description' => 'List of events name to filter Event Service debug messages', 'description' => 'List of events name to filter Event Service debug messages',
'default' => [], 'default' => [],
@@ -153,7 +153,7 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'event_service.debug.filter_sources' => [ 'event_service.debug.filter_sources' => [
'type' => 'array', 'type' => 'array',
'description' => 'List of event sources to filter Event Service debug messages', 'description' => 'List of event sources to filter Event Service debug messages',
'default' => '', 'default' => '',
@@ -161,38 +161,6 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'temporary_object.force_creation' => [
'type' => 'bool',
'description' => 'If true, all the objects created by the external key are temporary',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.lifetime' => [
'type' => 'integer',
'description' => 'Seconds for temporary objects created',
'default' => 300,
'value' => 300,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.watchdog_interval' => [
'type' => 'integer',
'description' => 'Seconds between watchdog signals',
'default' => 60,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.garbage_interval' => [
'type' => 'integer',
'description' => 'Seconds between garbage collections',
'default' => 60,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'app_env_label' => [ 'app_env_label' => [
'type' => 'string', 'type' => 'string',
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")', 'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
@@ -217,7 +185,7 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'db_host' => [ 'db_host' => [
'type' => 'string', 'type' => 'string',
'default' => null, 'default' => null,
'value' => '', 'value' => '',
@@ -515,7 +483,7 @@ class Config
'synchro_obsolete_replica_locks_object' => [ 'synchro_obsolete_replica_locks_object' => [
'type' => 'bool', 'type' => 'bool',
'description' => 'Obsolete synchro replicas prevent object modification by any mean (eg. anonymization)', 'description' => 'Obsolete synchro replicas prevent object modification by any mean (eg. anonymization)',
'default' => true, 'default' => 'true',
'value' => '', 'value' => '',
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => true, 'show_in_conf_sample' => true,
@@ -656,22 +624,22 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'email_transport_smtp.allow_self_signed' => [ 'email_transport_smtp.allow_self_signed' => array(
'type' => 'bool', 'type' => 'bool',
'description' => 'Allow self signed peer certificates', 'description' => 'Allow self signed peer certificates',
'default' => false, 'default' => false,
'value' => false, 'value' => false,
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ),
'email_transport_smtp.verify_peer' => [ 'email_transport_smtp.verify_peer' => array(
'type' => 'bool', 'type' => 'bool',
'description' => 'Verify peer certificate', 'description' => 'Verify peer certificate',
'default' => true, 'default' => true,
'value' => true, 'value' => true,
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ),
'email_css' => [ 'email_css' => [
'type' => 'string', 'type' => 'string',
'description' => 'CSS that will override the standard stylesheet used for the notifications', 'description' => 'CSS that will override the standard stylesheet used for the notifications',
@@ -978,14 +946,6 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'lifecycle.transitions_sort_type' => [
'type' => 'string',
'description' => 'How transitions will be sorted in the GUI. Possible values are "xml", "alphabetical", "fixed" or "relative"',
'default' => DBObject::DEFAULT_TRANSITIONS_SORT_TYPE,
'value' => DBObject::DEFAULT_TRANSITIONS_SORT_TYPE,
'source_of_value' => '',
'show_in_conf_sample' => true,
],
'url_validation_pattern' => [ 'url_validation_pattern' => [
'type' => 'string', 'type' => 'string',
'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)', 'description' => 'Regular expression to validate/detect the format of an URL (URL attributes and Wiki formatting for Text attributes)',
@@ -1069,14 +1029,6 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'log_kpi_generate_legacy_report' => [
'type' => 'bool',
'description' => 'Generate the legacy KPI report (kpi.html)',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'max_linkset_output' => [ 'max_linkset_output' => [
'type' => 'integer', 'type' => 'integer',
'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.', 'description' => 'Maximum number of items shown when getting a list of related items in an email, using the form $this->some_list$. 0 means no limit.',
@@ -1193,30 +1145,6 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'sessions_tracking.enabled' => [
'type' => 'bool',
'description' => 'Whether or not the whole mechanism to track active sessions is enabled. See PHP session.gc_maxlifetime setting to configure session expiration.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'sessions_tracking.gc_threshold' => [
'type' => 'integer',
'description' => 'fallback in case cron is not active: probability in percent that session files are cleanup during any itop request (100 means always)',
'default' => 1,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'sessions_tracking.gc_duration_in_seconds' => [
'type' => 'integer',
'description' => 'fallback in case cron is not active: when a cleanup is triggered cleanup duration will not exceed this duration (in seconds).',
'default' => 1,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'transaction_storage' => [ 'transaction_storage' => [
'type' => 'string', 'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).', 'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
@@ -1643,14 +1571,6 @@ class Config
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
'security.enable_header_xcontent_type_options' => [
'type' => 'bool',
'description' => 'If set to false, iTop will stop sending the X-Content-Type-Options HTTP header. This header could trigger CORB protection on certain resources (JSON, XML, HTML, text) therefore blocking them.',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'security.disable_inline_documents_sandbox' => [ 'security.disable_inline_documents_sandbox' => [
'type' => 'bool', 'type' => 'bool',
'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!', 'description' => 'If true then the sandbox for documents displayed in a browser tab will be disabled; enabling scripts and other interactive content. Note that setting this to true will open the application to potential XSS attacks!',
@@ -1710,8 +1630,8 @@ class Config
'audit.enable_selection_landing_page' => [ 'audit.enable_selection_landing_page' => [
'type' => 'bool', '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)', 'description' => 'If true audit categories must be selected before results are computed (use this setting in case of a lot of audit categories)',
'default' => true, 'default' => false,
'value' => true, 'value' => false,
'source_of_value' => '', 'source_of_value' => '',
'show_in_conf_sample' => false, 'show_in_conf_sample' => false,
], ],
@@ -2321,24 +2241,6 @@ class Config
$this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes); $this->m_sAllowedLoginTypes = implode('|', $aAllowedLoginTypes);
} }
/**
* @since 2.7.11 N°7085
* Add login mode if not configured already
* @param string $sLoginMode
*
* @return void
*/
public function AddAllowedLoginTypes($sLoginMode)
{
$aAllowedLoginTypes = $this->GetAllowedLoginTypes();
if (in_array($sLoginMode, $aAllowedLoginTypes)){
return;
}
$aAllowedLoginTypes[] = $sLoginMode;
$this->SetAllowedLoginTypes($aAllowedLoginTypes);
}
public function SetExternalAuthenticationVariable($sExtAuthVariable) public function SetExternalAuthenticationVariable($sExtAuthVariable)
{ {
$this->m_sExtAuthVariable = $sExtAuthVariable; $this->m_sExtAuthVariable = $sExtAuthVariable;

View File

@@ -3,7 +3,7 @@
// //
// This file is part of iTop. // This file is part of iTop.
// //
// iTop is free software; you can redistribute it and/or modify // iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // 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 // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
@@ -58,16 +58,6 @@ class ContextTag
public const TAG_SETUP = 'Setup'; public const TAG_SETUP = 'Setup';
public const TAG_SYNCHRO = 'Synchro'; public const TAG_SYNCHRO = 'Synchro';
public const TAG_REST = 'REST/JSON'; public const TAG_REST = 'REST/JSON';
/**
* @since 3.1.0 N°6047
*/
public const TAG_IMPORT = 'Import';
/**
* @since 3.1.0 N°6047
*/
public const TAG_EXPORT = 'Export';
/** /**
* @var string * @var string
* @since 3.1.0 N°3200 * @since 3.1.0 N°3200
@@ -85,7 +75,7 @@ class ContextTag
{ {
static::$aStack[] = $sTag; static::$aStack[] = $sTag;
} }
public static function AddContext($sTag) public static function AddContext($sTag)
{ {
static::$aStack[] = $sTag; static::$aStack[] = $sTag;

View File

@@ -1,11 +1,8 @@
<?php <?php
/** /**
* This file is only here for compatibility reasons. * This file is only here for compatibility issues. Will be removed in iTop 3.1.0 (N°3664)
* It will be removed in future iTop versions (N°6533)
* *
* @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require ! * @deprecated 3.0.0 N°3663 Exception classes were moved to `/application/exceptions`, use autoloader instead of require !
*/ */
require_once '../approot.inc.php';
require_once __DIR__ . '/../approot.inc.php'; DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions');
DeprecatedCallsLog::NotifyDeprecatedFile('Classes were moved to /application/exceptions and can be used directly with the autoloader');

View File

@@ -188,8 +188,8 @@ final class ItopCounter
if (!$hDBLink) if (!$hDBLink)
{ {
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser)); throw new Exception("Could not connect to the DB server (host=$sDBHost, user=$sDBUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
} }
return $hDBLink; return $hDBLink;
} }

View File

@@ -12,7 +12,6 @@ use Combodo\iTop\Application\UI\Base\Component\Input\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
/** /**
* Bulk export: CSV export * Bulk export: CSV export
@@ -115,7 +114,6 @@ class CSVBulkExport extends TabularBulkExport
case 'csv_options': case 'csv_options':
$oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:CSVOptions')); $oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:CSVOptions'));
$oPanel->AddSubBlock(ExportHelper::GetAlertForExcelMaliciousInjection());
$oMulticolumn = MultiColumnUIBlockFactory::MakeStandard(); $oMulticolumn = MultiColumnUIBlockFactory::MakeStandard();
$oPanel->AddSubBlock($oMulticolumn); $oPanel->AddSubBlock($oMulticolumn);
@@ -139,7 +137,7 @@ class CSVBulkExport extends TabularBulkExport
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.utils::EscapeHtml($sOtherSeparator).'"/>'; $aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.utils::EscapeHtml($sOtherSeparator).'"/>';
foreach ($aSep as $sVal => $sLabel) { foreach ($aSep as $sVal => $sLabel) {
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", $sVal, $sLabel, "radio"); $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", utils::EscapeHtml($sVal), $sLabel, "radio");
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator)); $oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
$oRadio->SetBeforeInput(false); $oRadio->SetBeforeInput(false);
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right'); $oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
@@ -165,8 +163,8 @@ class CSVBulkExport extends TabularBulkExport
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.utils::EscapeHtml($sOtherQualifier).'"/>'; $aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.utils::EscapeHtml($sOtherQualifier).'"/>';
foreach ($aQualifiers as $sVal => $sLabel) { foreach ($aQualifiers as $sVal => $sLabel) {
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", $sVal, $sLabel, "radio"); $oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", utils::EscapeHtml($sVal), $sLabel, "radio");
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawQualifier)); $oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
$oRadio->SetBeforeInput(false); $oRadio->SetBeforeInput(false);
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right'); $oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
$oRadio->GetInput()->AddCSSClass('ibo-input-checkbox'); $oRadio->GetInput()->AddCSSClass('ibo-input-checkbox');

View File

@@ -23,22 +23,10 @@
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
*/ */
abstract class CustomFieldsHandler { abstract class CustomFieldsHandler
/** @var string $sAttCode */ {
protected $sAttCode; protected $sAttCode;
/** @var array{
* legacy: int,
* extradata_id: string,
* _template_name: string,
* template_id: string,
* template_data: string,
* user_data: array<string, mixed>,
* current_template_id: string,
* current_template_data: string,
* } $aValues same as {@see \ormCustomFieldsValue::$aCurrentValues}
*/
protected $aValues; protected $aValues;
/** @var \Combodo\iTop\Form\Form $oForm */
protected $oForm; protected $oForm;
/** /**
@@ -47,47 +35,20 @@ abstract class CustomFieldsHandler {
* *
* @param $sAttCode * @param $sAttCode
*/ */
final public function __construct($sAttCode) { final public function __construct($sAttCode)
{
$this->sAttCode = $sAttCode; $this->sAttCode = $sAttCode;
$this->aValues = null; $this->aValues = null;
} }
abstract public function BuildForm(DBObject $oHostObject, $sFormId); abstract public function BuildForm(DBObject $oHostObject, $sFormId);
/**
* @returns true|string true if no error found, error message otherwise
* @throws \ApplicationException if {@link static::$oForm} attribute not initialized yet
* @since 3.1.0 N°6322 N°1150 Add template_id checks
*/
public function Validate(DBObject $oHostObject) {
if (false === isset($this->oForm)) {
throw new ApplicationException('oForm attribute not init yet. You must call BuildForm before this method !');
}
try {
$this->oForm->Validate();
if ($this->oForm->GetValid()) {
$ret = true;
}
else {
$aMessages = array();
foreach ($this->oForm->GetErrorMessages() as $sFieldId => $aFieldMessages) {
$aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages);
}
$ret = 'Invalid value: '.implode(', ', $aMessages);
}
} catch (Exception $e) {
$ret = $e->getMessage();
}
return $ret;
}
/** /**
* *
* @return \Combodo\iTop\Form\Form * @return \Combodo\iTop\Form\Form
*/ */
public function GetForm() { public function GetForm()
{
return $this->oForm; return $this->oForm;
} }
@@ -96,14 +57,16 @@ abstract class CustomFieldsHandler {
$this->aValues = $aValues; $this->aValues = $aValues;
} }
public static function GetPrerequisiteAttributes($sClass = null) { static public function GetPrerequisiteAttributes($sClass = null)
{
return array(); return array();
} }
/** /**
* List the available verbs for 'GetForTemplate' * List the available verbs for 'GetForTemplate'
*/ */
public static function EnumTemplateVerbs() { static public function EnumTemplateVerbs()
{
return array(); return array();
} }
@@ -155,21 +118,6 @@ abstract class CustomFieldsHandler {
return null; return null;
} }
/**
* @param \stdClass|null $json
* @param string $sAttCode
*
* @return \ormCustomFieldsValue|null
*
* @since 3.1.0 N°1150 Method creation
*/
public function FromJSONToValue(?stdClass $json, string $sAttCode): ?ormCustomFieldsValue
{
// Default impl doing nothing, to avoid errors on children not having this method
return null;
}
/** /**
* @param DBObject $oHostObject * @param DBObject $oHostObject
* *

View File

@@ -58,7 +58,6 @@
<parent>cmdbAbstractObject</parent> <parent>cmdbAbstractObject</parent>
<properties> <properties>
<category>addon/userrights,grant_by_profile</category> <category>addon/userrights,grant_by_profile</category>
<is_link>1</is_link>
</properties> </properties>
<fields> <fields>
<field id="userid" xsi:type="AttributeExternalKey"> <field id="userid" xsi:type="AttributeExternalKey">
@@ -219,19 +218,6 @@
<field id="friendlyname" xsi:type="AttributeFriendlyName"/> <field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields> </fields>
</class> </class>
<class id="AuditDomain" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>application, grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="icon" xsi:type="AttributeImage"/>
<field id="categories_list" xsi:type="AttributeLinkedSet"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="Query" _delta="define"> <class id="Query" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php --> <!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent> <parent>cmdbAbstractObject</parent>
@@ -496,24 +482,6 @@
<type>boolean</type> <type>boolean</type>
<default>true</default> <default>true</default>
</property> </property>
<property id="with_php_constraint">
<php_param>with_php_constraint</php_param>
<mandatory>false</mandatory>
<type>boolean</type>
<default>false</default>
</property>
<property id="with_php_computation">
<php_param>with_php_computation</php_param>
<mandatory>false</mandatory>
<type>boolean</type>
<default>false</default>
</property>
<property id="create_temporary_object">
<php_param>create_temporary_object</php_param>
<mandatory>false</mandatory>
<type>boolean</type>
<default>false</default>
</property>
<property id="on_target_delete"> <property id="on_target_delete">
<php_param>on_target_delete</php_param> <php_param>on_target_delete</php_param>
<mandatory>false</mandatory> <mandatory>false</mandatory>

File diff suppressed because it is too large Load Diff

View File

@@ -400,7 +400,7 @@ class DBObjectSearch extends DBSearch
} }
/** /**
* Important: If you need to add a condition on the same $sFilterCode several times with different $value values; do not use this method as the previous $value occurrences will be replaced by the last. Instead use: * Important: If you need to add a condition on the same $sFilterCode several times with different $value values; do not use this method as the previous $value occurences will be replaced by the last. Instead use:
* * {@see \DBObjectSearch::AddConditionExpression()} in loops to add conditions one by one * * {@see \DBObjectSearch::AddConditionExpression()} in loops to add conditions one by one
* * {@see \DBObjectSearch::AddConditionForInOperatorUsingParam()} for IN/NOT IN queries with lots of params at once * * {@see \DBObjectSearch::AddConditionForInOperatorUsingParam()} for IN/NOT IN queries with lots of params at once
* *

View File

@@ -767,10 +767,7 @@ class DBObjectSet implements iDBObjectSetIterator
try try
{ {
$oKPI = new ExecutionKPI();
$this->m_oSQLResult = CMDBSource::Query($sSQL); $this->m_oSQLResult = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, $this->GetRealSortOrder(), $this->m_iLimitCount, $this->m_iLimitStart, false);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
} catch (MySQLException $e) } catch (MySQLException $e)
{ {
// 1116 = ER_TOO_MANY_TABLES // 1116 = ER_TOO_MANY_TABLES
@@ -850,11 +847,8 @@ class DBObjectSet implements iDBObjectSetIterator
{ {
if (is_null($this->m_iNumTotalDBRows)) if (is_null($this->m_iNumTotalDBRows))
{ {
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true); $sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, 0, 0, true);
$resQuery = CMDBSource::Query($sSQL); $resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), 0, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if (!$resQuery) return 0; if (!$resQuery) return 0;
$aRow = CMDBSource::FetchArray($resQuery); $aRow = CMDBSource::FetchArray($resQuery);
@@ -865,42 +859,6 @@ class DBObjectSet implements iDBObjectSetIterator
return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ?? return $this->m_iNumTotalDBRows + count($this->m_aAddedObjects); // Does it fix Trac #887 ??
} }
/**
* @param \DBSearch $oFilter
* @param array $aOrder
* @param int $iLimitCount
* @param int $iLimitStart
* @param bool $bCount
*
* @return string
*/
private function GetPseudoOQL($oFilter, $aOrder, $iLimitCount, $iLimitStart, $bCount)
{
$sOQL = '';
if ($bCount) {
$sOQL .= 'COUNT ';
}
$sOQL .= $oFilter->ToOQL();
if ($iLimitCount > 0) {
$sOQL .= ' LIMIT ';
if ($iLimitStart > 0) {
$sOQL .= "$iLimitStart, ";
}
$sOQL .= "$iLimitCount";
}
if (count($aOrder) > 0) {
$sOQL .= ' ORDER BY ';
$aOrderBy = [];
foreach ($aOrder as $sAttCode => $bAsc) {
$aOrderBy[] = $sAttCode.' '.($bAsc ? 'ASC' : 'DESC');
}
$sOQL .= implode(', ', $aOrderBy);
}
return $sOQL;
}
/** /**
* Check if the count exceeds a given limit * Check if the count exceeds a given limit
* *
@@ -917,11 +875,8 @@ class DBObjectSet implements iDBObjectSetIterator
{ {
if (is_null($this->m_iNumTotalDBRows)) if (is_null($this->m_iNumTotalDBRows))
{ {
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true); $sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL); $resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery) if ($resQuery)
{ {
$aRow = CMDBSource::FetchArray($resQuery); $aRow = CMDBSource::FetchArray($resQuery);
@@ -932,7 +887,7 @@ class DBObjectSet implements iDBObjectSetIterator
{ {
$iCount = 0; $iCount = 0;
} }
} }
else else
{ {
$iCount = $this->m_iNumTotalDBRows; $iCount = $this->m_iNumTotalDBRows;
@@ -957,11 +912,8 @@ class DBObjectSet implements iDBObjectSetIterator
{ {
if (is_null($this->m_iNumTotalDBRows)) if (is_null($this->m_iNumTotalDBRows))
{ {
$oKPI = new ExecutionKPI();
$sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true); $sSQL = $this->m_oFilter->MakeSelectQuery(array(), $this->m_aArgs, null, null, $iLimit + 2, 0, true);
$resQuery = CMDBSource::Query($sSQL); $resQuery = CMDBSource::Query($sSQL);
$sOQL = $this->GetPseudoOQL($this->m_oFilter, array(), $iLimit + 2, 0, true);
$oKPI->ComputeStats('OQL Query Exec', $sOQL);
if ($resQuery) if ($resQuery)
{ {
$aRow = CMDBSource::FetchArray($resQuery); $aRow = CMDBSource::FetchArray($resQuery);
@@ -972,7 +924,7 @@ class DBObjectSet implements iDBObjectSetIterator
{ {
$iCount = 0; $iCount = 0;
} }
} }
else else
{ {
$iCount = $this->m_iNumTotalDBRows; $iCount = $this->m_iNumTotalDBRows;

View File

@@ -851,11 +851,11 @@ abstract class DBSearch
return; return;
} }
if (count($aColumns) == 0) if (count($aColumns) == 0)
{ {
$aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass())); $aColumns = array_keys(MetaModel::ListAttributeDefs($this->GetClass()));
// Add the standard id (as first column) // Add the standard id (as first column)
array_unshift($aColumns, 'id'); array_unshift($aColumns, 'id');
} }
$aQueryCols = CMDBSource::GetColumns($resQuery, $sSQL); $aQueryCols = CMDBSource::GetColumns($resQuery, $sSQL);
@@ -885,55 +885,6 @@ abstract class DBSearch
return $aRes; return $aRes;
} }
/**
* Selects a column ($sAttCode) from the specified class ($sClassAlias - default main class) of the DBsearch object and gives the result as an array
* @param string $sAttCode
* @param string|null $sClassAlias
*
* @return array
* @throws ConfigException
* @throws CoreException
* @throws MissingQueryArgument
* @throws MySQLException
* @throws MySQLHasGoneAwayException
*/
public function SelectAttributeToArray(string $sAttCode, ?string $sClassAlias = null):array
{
if(is_null($sClassAlias)) {
$sClassAlias = $this->GetClassAlias();
}
$sClass = $this->GetClass();
if($sAttCode === 'id'){
$aAttToLoad[$sClassAlias]=[];
} else {
$aAttToLoad[$sClassAlias][$sAttCode] = MetaModel::GetAttributeDef($sClass, $sAttCode);
}
$sSQL = $this->MakeSelectQuery([], [], $aAttToLoad);
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery)
{
return [];
}
$sColName = $sClassAlias.$sAttCode;
$aRes = [];
while ($aRow = CMDBSource::FetchArray($resQuery))
{
$aMappedRow = array();
if($sAttCode === 'id') {
$aMappedRow[$sAttCode] = $aRow[$sColName];
} else {
$aMappedRow[$sAttCode] = $aAttToLoad[$sClassAlias][$sAttCode]->FromSQLToValue($aRow, $sColName);
}
$aRes[] = $aMappedRow;
}
CMDBSource::FreeResult($resQuery);
return $aRes;
}
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// //
// Construction of the SQL queries // Construction of the SQL queries

View File

@@ -224,10 +224,6 @@ class DBUnionSearch extends DBSearch
} }
/** /**
* Set the selected classes for this query.
* The selected classes can be either in the selected classes of all the queries,
* or they should exist in all the sub-queries of the union.
*
* @param array $aSelectedClasses array of aliases * @param array $aSelectedClasses array of aliases
* *
* @throws \Exception * @throws \Exception
@@ -240,31 +236,22 @@ class DBUnionSearch extends DBSearch
$aAliasesToColumn = array_flip(array_keys($oFirstSearch->GetSelectedClasses())); $aAliasesToColumn = array_flip(array_keys($oFirstSearch->GetSelectedClasses()));
foreach ($aSelectedClasses as $sSelectedAlias) { foreach ($aSelectedClasses as $sSelectedAlias) {
if (!isset($aAliasesToColumn[$sSelectedAlias])) { if (!isset($aAliasesToColumn[$sSelectedAlias])) {
// The selected class is not in the selected classes of the union, throw new CoreException("SetSelectedClasses: Invalid class alias $sSelectedAlias");
// try to delegate the feature to the sub-queries
$aSelectedColumns = [];
break;
} }
$aSelectedColumns[] = $aAliasesToColumn[$sSelectedAlias]; $aSelectedColumns[] = $aAliasesToColumn[$sSelectedAlias];
} }
// 1 - change for each search // 1 - change for each search
foreach ($this->aSearches as $iPos => $oSearch) { foreach ($this->aSearches as $iPos => $oSearch)
{
$aCurrentSelectedAliases = []; $aCurrentSelectedAliases = [];
if (count($aSelectedColumns) === 0) { foreach ($aSelectedColumns as $iColumn) {
// Default to the list of aliases given $aCurrentSelectedAliases[] = $this->aColumnToAliases[$iColumn][$iPos];
$aCurrentSelectedAliases = $aSelectedClasses;
} else {
// Map the aliases for each query
foreach ($aSelectedColumns as $iColumn) {
$aCurrentSelectedAliases[] = $this->aColumnToAliases[$iColumn][$iPos];
}
} }
// Throws an exception if not valid // Throws an exception if not valid
$oSearch->SetSelectedClasses($aCurrentSelectedAliases); $oSearch->SetSelectedClasses($aCurrentSelectedAliases);
} }
// 2 - update the lowest common ancestors // 2 - update the lowest common ancestors
$this->ComputeSelectedClasses(); $this->ComputeSelectedClasses();
} }

View File

@@ -28,13 +28,8 @@ namespace Combodo\iTop;
use DOMDocument; use DOMDocument;
use DOMFormatException; use DOMFormatException;
use DOMNode;
use DOMNodeList;
use DOMXPath;
use Exception;
use IssueLog; use IssueLog;
use LogAPI; use LogAPI;
use MFElement;
use utils; use utils;
/** /**
@@ -46,11 +41,6 @@ use utils;
*/ */
class DesignDocument extends DOMDocument class DesignDocument extends DOMDocument
{ {
/** To fix DOMNode::getLineNo() ref https://www.php.net/manual/en/domnode.getlineno.php */
public const XML_PARSE_BIG_LINES = 4194304;
/** /**
* @throws \Exception * @throws \Exception
*/ */
@@ -79,12 +69,10 @@ class DesignDocument extends DOMDocument
*/ */
public function load($filename, $options = null) public function load($filename, $options = null)
{ {
if (is_file($filename)) { libxml_clear_errors();
libxml_clear_errors(); if (parent::load($filename, LIBXML_NOBLANKS) === false) {
if (parent::load($filename, LIBXML_NOBLANKS | LIBXML_BIGLINES | LIBXML_PARSEHUGE | self::XML_PARSE_BIG_LINES) === false) { $aErrors = libxml_get_errors();
$aErrors = libxml_get_errors(); IssueLog::Error("Error loading $filename", LogAPI::CHANNEL_DEFAULT, $aErrors);
IssueLog::Error("Error loading $filename", LogAPI::CHANNEL_DEFAULT, $aErrors);
}
} }
} }
@@ -239,56 +227,6 @@ class DesignElement extends \DOMElement
return ''; return '';
} }
/**
* Compatibility with PHP8.0
*
* @return \DOMElement|null
*
* @since 3.1.2
*/
public function GetFirstElementChild()
{
if (property_exists($this, 'firstElementChild')) {
return $this->firstElementChild;
}
$oChildNode = $this->firstChild;
while (!is_null($oChildNode)) {
if ($oChildNode instanceof \DOMElement) {
return $oChildNode;
}
$oChildNode = $oChildNode->nextSibling;
}
return null;
}
/**
* Compatibility with PHP8.0
*
* @return \DOMElement|null
*
* @since 3.1.2
*/
public function GetNextElementSibling()
{
if (property_exists($this, 'nextElementSibling')) {
return $this->nextElementSibling;
}
$oSibling = $this->nextSibling;
while (!is_null($oSibling)) {
if ($oSibling instanceof \DOMElement) {
return $oSibling;
}
$oSibling = $oSibling->nextSibling;
}
return null;
}
/** /**
* Returns the node directly under the given node * Returns the node directly under the given node
* @param $sTagName * @param $sTagName
@@ -369,146 +307,4 @@ class DesignElement extends \DOMElement
} }
return $sRet; return $sRet;
} }
/**
* Check that the current node is actually a class node, under classes
* @since 3.1.2 3.2.0 N°6974
*/
public function IsClassNode(): bool
{
if ($this->tagName == 'class') {
// Beware: classes/class also exists in the group definition
if (($this->parentNode->tagName == 'classes') && ($this->parentNode->parentNode->tagName == 'itop_design')) {
return true;
}
}
return false;
}
/**
* True if the node is contained in a _delta="merge" tree
* @return bool
*/
public function IsInSpecifiedMerge(): bool
{
// Iterate through the parents: reset the flag if any of them has a flag set
for ($oParent = $this; $oParent instanceof MFElement; $oParent = $oParent->parentNode) {
$sDeltaSpec = $oParent->getAttribute('_delta');
if ($sDeltaSpec === 'merge') {
return true;
}
if (in_array($sDeltaSpec, ['define', 'define_if_not_exists', 'force', 'redefine'])) {
return false;
}
}
return false;
}
/**
* Find the child node matching the given node.
* UNSAFE: may return nodes marked as _alteration="removed"
* A method with the same signature MUST exist in MFDocument for the recursion to work fine
*
* @param DesignElement $oRefNode The node to search for
* @param null|string $sSearchId substitutes to the value of the 'id' attribute
*
* @return DesignElement|null
* @throws \Exception
* @since 3.1.2 3.2.0 N°6974
*/
public function _FindChildNode(DesignElement $oRefNode, $sSearchId = null): ?DesignElement
{
return self::_FindNode($this, $oRefNode, $sSearchId);
}
/**
* Find the child node matching the given node.
* UNSAFE: may return nodes marked as _alteration="removed"
* A method with the same signature MUST exist in MFDocument for the recursion to work fine
*
* @param DesignElement $oRefNode The node to search for
*
* @return DesignElement|null
* @throws \Exception
* @since 3.1.2 3.2.0 N°6974
*/
public function _FindChildNodes(DesignElement $oRefNode): ?DesignElement
{
return self::_FindNodes($this, $oRefNode);
}
/**
* Find the child node matching the given node under the specified parent.
* UNSAFE: may return nodes marked as _alteration="removed"
*
* @param \DOMNode $oParent
* @param DesignElement $oRefNode
* @param string|null $sSearchId
*
* @return DesignElement|null
* @throws Exception
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNode(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null): ?DesignElement
{
$oNodes = self::_FindNodes($oParent, $oRefNode, $sSearchId);
if ($oNodes instanceof DOMNodeList) {
/** @var DesignElement $oNode */
$oNode = $oNodes->item(0);
return $oNode;
}
return null;
}
/**
* Find the child node matching the given node under the specified parent.
* UNSAFE: may return nodes marked as _alteration="removed"
*
* @param \DOMNode $oParent
* @param DesignElement $oRefNode
* @param string|null $sSearchId
*
* @return \DOMNodeList|false|mixed
* @since 3.1.2 3.2.0 N°6974
*/
public static function _FindNodes(DOMNode $oParent, DesignElement $oRefNode, string $sSearchId = null)
{
if ($oParent instanceof DOMDocument)
{
$oDoc = $oParent->firstChild->ownerDocument;
$oRoot = $oParent;
}
else
{
$oDoc = $oParent->ownerDocument;
$oRoot = $oParent;
}
$oXPath = new DOMXPath($oDoc);
if ($oRefNode->hasAttribute('id'))
{
// Find the elements having the same tag name and id
if (!$sSearchId)
{
$sSearchId = $oRefNode->getAttribute('id');
}
$sXPath = './'.$oRefNode->tagName."[@id='$sSearchId']";
$oRes = $oXPath->query($sXPath, $oRoot);
}
else
{
// Get the elements having the same tag name
$sXPath = './'.$oRefNode->tagName;
$oRes = $oXPath->query($sXPath, $oRoot);
}
return $oRes;
}
} }

View File

@@ -3,7 +3,7 @@
// //
// This file is part of iTop. // This file is part of iTop.
// //
// iTop is free software; you can redistribute it and/or modify // iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // 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 // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
@@ -56,11 +56,10 @@ class Dict
* @param $sLanguageCode * @param $sLanguageCode
* *
* @throws \DictExceptionUnknownLanguage * @throws \DictExceptionUnknownLanguage
* @since 3.0.4 3.1.1 3.2.0 Param $sLanguageCode becomes nullable
*/ */
public static function SetUserLanguage($sLanguageCode = null) public static function SetUserLanguage($sLanguageCode)
{ {
if (!is_null($sLanguageCode) && !array_key_exists($sLanguageCode, self::$m_aLanguages)) if (!array_key_exists($sLanguageCode, self::$m_aLanguages))
{ {
throw new DictExceptionUnknownLanguage($sLanguageCode); throw new DictExceptionUnknownLanguage($sLanguageCode);
} }
@@ -107,59 +106,42 @@ class Dict
} }
/** /**
* Returns a localised string from the dictionary * Returns a localised string from the dictonary
* *
* @param string $sStringCode The code identifying the dictionary entry * @param string $sStringCode The code identifying the dictionary entry
* @param string $sDefault Default value if there is no match in the dictionary, if no default provided, returns $sStringCode unchanged * @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 False to allow the use of the default language as a fallback, true otherwise
* *
* @return string * @return string
*/ */
public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false) public static function S($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
$aInfo = self::GetLabelAndLangCode($sStringCode, $sDefault, $bUserLanguageOnly);
return $aInfo['label'];
}
/**
* Returns a localised string from the dictonary with its associated lang code
*
* @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 True to allow the use of the default language as a fallback, false otherwise
*
* @return array{
* lang: string, label: string
* } with localized label string and used lang code
*/
private static function GetLabelAndLangCode($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{ {
// Attempt to find the string in the user language // Attempt to find the string in the user language
// //
$sLangCode = self::GetUserLanguage(); $sLangCode = self::GetUserLanguage();
self::InitLangIfNeeded($sLangCode); self::InitLangIfNeeded($sLangCode);
if (! array_key_exists($sLangCode, self::$m_aData)) if (!array_key_exists($sLangCode, self::$m_aData))
{ {
IssueLog::Warning("Cannot find $sLangCode in all registered dictionaries."); IssueLog::Warning("Cannot find $sLangCode in dictionnaries. default labels displayed");
// It may happen, when something happens before the dictionaries get loaded // It may happen, when something happens before the dictionaries get loaded
return [ 'label' => $sStringCode, 'lang' => $sLangCode ]; return $sStringCode;
} }
$aCurrentDictionary = self::$m_aData[$sLangCode]; $aCurrentDictionary = self::$m_aData[$sLangCode];
if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary)) if (is_array($aCurrentDictionary) && array_key_exists($sStringCode, $aCurrentDictionary))
{ {
return [ 'label' => $aCurrentDictionary[$sStringCode], 'lang' => $sLangCode ]; return $aCurrentDictionary[$sStringCode];
} }
if (!$bUserLanguageOnly) if (!$bUserLanguageOnly)
{ {
// Attempt to find the string in the default language // Attempt to find the string in the default language
// //
self::InitLangIfNeeded(self::$m_sDefaultLanguage); self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage]; $aDefaultDictionary = self::$m_aData[self::$m_sDefaultLanguage];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary)) if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{ {
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => self::$m_sDefaultLanguage ]; return $aDefaultDictionary[$sStringCode];
} }
// Attempt to find the string in english // Attempt to find the string in english
// //
@@ -168,17 +150,17 @@ class Dict
$aDefaultDictionary = self::$m_aData['EN US']; $aDefaultDictionary = self::$m_aData['EN US'];
if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary)) if (is_array($aDefaultDictionary) && array_key_exists($sStringCode, $aDefaultDictionary))
{ {
return [ 'label' => $aDefaultDictionary[$sStringCode], 'lang' => 'EN US' ]; return $aDefaultDictionary[$sStringCode];
} }
} }
// Could not find the string... // Could not find the string...
// //
if (is_null($sDefault)) if (is_null($sDefault))
{ {
return [ 'label' => $sStringCode, 'lang' => null ]; return $sStringCode;
} }
return [ 'label' => $sDefault, 'lang' => null ]; return $sDefault;
} }
@@ -194,25 +176,19 @@ class Dict
*/ */
public static function Format($sFormatCode /*, ... arguments ... */) public static function Format($sFormatCode /*, ... arguments ... */)
{ {
['label' => $sLocalizedFormat, 'lang' => $sLangCode] = self::GetLabelAndLangCode($sFormatCode); $sLocalizedFormat = self::S($sFormatCode);
$aArguments = func_get_args(); $aArguments = func_get_args();
array_shift($aArguments); array_shift($aArguments);
if ($sLocalizedFormat == $sFormatCode) if ($sLocalizedFormat == $sFormatCode)
{ {
// Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded) // Make sure the information will be displayed (ex: an error occuring before the dictionary gets loaded)
return $sFormatCode.' - '.implode(', ', $aArguments); return $sFormatCode.' - '.implode(', ', $aArguments);
} }
try{ return vsprintf($sLocalizedFormat, $aArguments);
return vsprintf($sLocalizedFormat, $aArguments);
} catch(\Throwable $e){
\IssueLog::Error("Cannot format dict key", null, ["sFormatCode" => $sFormatCode, "sLangCode" => $sLangCode, 'exception_msg' => $e->getMessage() ]);
return $sFormatCode.' - '.implode(', ', $aArguments);
}
} }
/** /**
* Initialize a the entries for a given language (replaces the former Add() method) * Initialize a the entries for a given language (replaces the former Add() method)
* @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US' * @param string $sLanguageCode Code identifying the language i.e. 'FR-FR', 'EN-US'
@@ -222,7 +198,7 @@ class Dict
{ {
self::$m_aData[$sLanguageCode] = $aEntries; self::$m_aData[$sLanguageCode] = $aEntries;
} }
/** /**
* Set the list of available languages * Set the list of available languages
* @param hash $aLanguagesList * @param hash $aLanguagesList
@@ -283,7 +259,7 @@ class Dict
{ {
$sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php'; $sDictFile = APPROOT.'env-'.utils::GetCurrentEnvironment().'/dictionaries/'.str_replace(' ', '-', strtolower($sLangCode)).'.dict.php';
require_once($sDictFile); require_once($sDictFile);
if (self::GetApcService()->function_exists('apc_store') if (self::GetApcService()->function_exists('apc_store')
&& (self::$m_sApplicationPrefix !== null)) && (self::$m_sApplicationPrefix !== null))
{ {
@@ -293,7 +269,7 @@ class Dict
} }
return $bResult; return $bResult;
} }
/** /**
* Enable caching (cached using APC) * Enable caching (cached using APC)
* @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance * @param string $sApplicationPrefix The prefix for uniquely identiying this iTop instance
@@ -336,14 +312,14 @@ class Dict
} }
} }
} }
public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US') public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
{ {
$aMissing = array(); // Strings missing for the target language $aMissing = array(); // Strings missing for the target language
$aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary $aUnexpected = array(); // Strings defined for the target language, but not found in the reference dictionary
$aNotTranslated = array(); // Strings having the same value in both dictionaries $aNotTranslated = array(); // Strings having the same value in both dictionaries
$aOK = array(); // Strings having different values in both dictionaries $aOK = array(); // Strings having different values in both dictionaries
foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue) foreach (self::$m_aData[$sLanguageRef] as $sStringCode => $sValue)
{ {
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode])) if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageCode]))
@@ -351,7 +327,7 @@ class Dict
$aMissing[$sStringCode] = $sValue; $aMissing[$sStringCode] = $sValue;
} }
} }
foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue) foreach (self::$m_aData[$sLanguageCode] as $sStringCode => $sValue)
{ {
if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef])) if (!array_key_exists($sStringCode, self::$m_aData[$sLanguageRef]))
@@ -374,7 +350,7 @@ class Dict
} }
return array($aMissing, $aUnexpected, $aNotTranslated, $aOK); return array($aMissing, $aUnexpected, $aNotTranslated, $aOK);
} }
public static function Dump() public static function Dump()
{ {
MyHelpers::var_dump_html(self::$m_aData); MyHelpers::var_dump_html(self::$m_aData);
@@ -397,7 +373,7 @@ class Dict
// No need to actually load the strings since it's only used to know the list of languages // No need to actually load the strings since it's only used to know the list of languages
// at setup time !! // at setup time !!
} }
/** /**
* Export all the dictionary entries - of the given language - whose code matches the given prefix * Export all the dictionary entries - of the given language - whose code matches the given prefix
* missing entries in the current language will be replaced by entries in the default language * missing entries in the current language will be replaced by entries in the default language
@@ -410,7 +386,7 @@ class Dict
self::InitLangIfNeeded(self::$m_sDefaultLanguage); self::InitLangIfNeeded(self::$m_sDefaultLanguage);
$aEntries = array(); $aEntries = array();
$iLength = strlen($sStartingWith); $iLength = strlen($sStartingWith);
// First prefill the array with entries from the default language // First prefill the array with entries from the default language
foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry) foreach(self::$m_aData[self::$m_sDefaultLanguage] as $sCode => $sEntry)
{ {
@@ -419,7 +395,7 @@ class Dict
$aEntries[$sCode] = $sEntry; $aEntries[$sCode] = $sEntry;
} }
} }
// Now put (overwrite) the entries for the user language // Now put (overwrite) the entries for the user language
foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry) foreach(self::$m_aData[self::GetUserLanguage()] as $sCode => $sEntry)
{ {

View File

@@ -16,11 +16,8 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
use Combodo\iTop\Application\Helper\WebResourcesHelper; use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon; use Combodo\iTop\Application\UI\Base\Component\MedallionIcon\MedallionIcon;
use Combodo\iTop\Application\UI\Base\Component\Panel\Panel; use Combodo\iTop\Application\UI\Base\Component\Panel\Panel;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Renderer\BlockRenderer; use Combodo\iTop\Renderer\BlockRenderer;
/** /**
@@ -62,7 +59,7 @@ class DisplayableNode extends GraphNode
public function GetWidth() public function GetWidth()
{ {
return max(32, 5 * mb_strlen($this->GetProperty('label'))); // approximation of the text's bounding box return max(32, 5*strlen($this->GetProperty('label'))); // approximation of the text's bounding box
} }
public function GetHeight() public function GetHeight()
@@ -491,7 +488,7 @@ class DisplayableNode extends GraphNode
if ($bNoLabel) if ($bNoLabel)
{ {
// simulate a fake label with the approximate same size as the true label // simulate a fake label with the approximate same size as the true label
$sLabel = str_repeat('x', mb_strlen($this->GetProperty('label', $this->GetId()))); $sLabel = str_repeat('x',strlen($this->GetProperty('label', $this->GetId())));
$sDot = 'label="'.$sLabel.'"'; $sDot = 'label="'.$sLabel.'"';
} }
else else
@@ -1415,8 +1412,6 @@ class DisplayableGraph extends SimpleGraph
/** /**
* Display the graph inside the given page, with the "filter" drawer above it * Display the graph inside the given page, with the "filter" drawer above it
* *
* @deprecated 3.1.1 3.2.0 N°3767 Use \DisplayableGraph::DisplayFilterBox() and \DisplayableGraph::DisplayGraph() instead
*
* @param WebPage $oP * @param WebPage $oP
* @param array $aResults * @param array $aResults
* @param string $sRelation * @param string $sRelation
@@ -1430,35 +1425,10 @@ class DisplayableGraph extends SimpleGraph
* *
* @throws \CoreException * @throws \CoreException
* @throws \DictExceptionMissingString * @throws \DictExceptionMissingString
*
*/ */
function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $bLazyLoading = false) function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $bLazyLoading = false)
{ {
$oP->AddSubBlock($this->DisplayFilterBox($oP, $aResults, $bLazyLoading)); list($aExcludedByClass, $aAdditionalContexts) = $this->DisplayFiltering($sContextKey, $aContextParams, $aExcludedObjects, $oP, $aResults, $bLazyLoading);
$this->DisplayGraph($oP, $sRelation, $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams, $bLazyLoading);
}
/**
* Display only the graph inside the given page, with the parameters of filter box draw with DisplayFilterBox
*
* @param WebPage $oP
* @param string $sRelation
* @param ApplicationContext $oAppContext
* @param array $aExcludedObjects
* @param string $sObjClass
* @param int $iObjKey
* @param string $sContextKey
* @param array $aContextParams
* @param bool $bLazyLoading
*
* @throws \CoreException
* @throws \DictExceptionMissingString
*
* @since 3.1.1 3.2.0 N°3767
*/
function DisplayGraph(WebPage $oP, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects, $sObjClass, $iObjKey, $sContextKey, $aContextParams = array(), bool $bLazyLoading = false): void
{
list($aExcludedByClass, $aAdditionalContexts) = $this->GetFilteringData($sContextKey, $aContextParams, $aExcludedObjects);
$iGroupingThreshold = utils::ReadParam('g', 5); $iGroupingThreshold = utils::ReadParam('g', 5);
@@ -1543,10 +1513,12 @@ class DisplayableGraph extends SimpleGraph
$oP->add_ready_script(" $('#$sId').simple_graph(".json_encode($aParams).");"); $oP->add_ready_script(" $('#$sId').simple_graph(".json_encode($aParams).");");
} else { } else {
$oP->add_script("function Load(){var aExcluded = []; $('input[name^=excluded]').each( function() {if (!$(this).prop('checked')) { aExcluded.push($(this).val()); }} ); var params= $.extend(".json_encode($aParams).", {excluded_classes: aExcluded}); $('#$sId').simple_graph(params);}"); $oP->add_script("function Load(){var aExcluded = []; $('input[name^=excluded]').each( function() {if (!$(this).prop('checked')) { aExcluded.push($(this).val()); }} ); var params= $.extend(".json_encode($aParams).", {excluded_classes: aExcluded}); $('#$sId').simple_graph(params);}");
$oP->add_ready_script("$('#graph').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');$('#impacted_objects_lists').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');$('#impacted_groups').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');"); $oP->add_ready_script("$('#impacted_objects_lists').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');$('#impacted_groups').html('".utils::TextToHtml(Dict::S('Relation:impacts/NoFilteredData'))."');");
} }
} }
catch (Exception $e) { catch(Exception $e)
{
$oP->add('<div>'.$e->getMessage().'</div>'); $oP->add('<div>'.$e->getMessage().'</div>');
} }
$oP->add_script( $oP->add_script(
@@ -1591,41 +1563,23 @@ EOF
* @throws \Twig\Error\LoaderError * @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError * @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError * @throws \Twig\Error\SyntaxError
*
* @deprecated 3.1.1 3.2.0 N°3767 Use \DisplayableGraph::DisplayFilterBox() and \DisplayableGraph::GetFilteringData() instead
*/ */
public function DisplayFiltering(string $sContextKey, array $aContextParams, array $aExcludedObjects, WebPage $oP, array $aResults, bool $bLazyLoading = false): array public function DisplayFiltering(string $sContextKey, array $aContextParams, array $aExcludedObjects, WebPage $oP, array $aResults, bool $bLazyLoading = false): array
{ {
$oP->Add($this->DisplayFilterBox($oP, $aResults, $bLazyLoading)); $aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
$aExcludedByClass = array();
return $this->GetFilteringData($sContextKey, $aContextParams, $aExcludedObjects); foreach ($aExcludedObjects as $oObj) {
} if (!array_key_exists(get_class($oObj), $aExcludedByClass)) {
$aExcludedByClass[get_class($oObj)] = array();
/** }
* @param \WebPage $oP $aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
* @param array $aResults }
* @param bool $bLazyLoading
*
* @return UIContentBlock
* @throws \CoreException
* @throws \DictExceptionMissingString
* @throws \ReflectionException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @since 3.1.1 3.2.0 N°3767
*/
public function DisplayFilterBox(WebPage $oP, array $aResults, bool $bLazyLoading = false): UIContentBlock
{
$sSftShort = Dict::S('UI:ElementsDisplayed'); $sSftShort = Dict::S('UI:ElementsDisplayed');
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ['not-printable']); $oP->add("<div class=\"not-printable\">\n");
$oUiSearchBlock = new Panel($sSftShort, [], Panel::ENUM_COLOR_SCHEME_CYAN, 'dh_flash'); $oUiSearchBlock = new Panel($sSftShort, [], Panel::ENUM_COLOR_SCHEME_CYAN, 'dh_flash');
$oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]) $oUiSearchBlock->SetCSSClasses(["ibo-search-form-panel", "display_block"]);
->SetIsCollapsible(true); $oUiSearchBlock->SetIsCollapsible(true);
$oUiHtmlBlock = new Combodo\iTop\Application\UI\Base\Component\Html\Html(
$oUiHtmlBlock = new Html(
<<<EOF <<<EOF
<div id="ds_flash" class="search_box ibo-display-graph--search-box"> <div id="ds_flash" class="search_box ibo-display-graph--search-box">
@@ -1672,23 +1626,11 @@ EOF
$oUiHtmlBlock->AddHtml("<button type=\"button\" id=\"ReloadMovieBtn\" class=\"ibo-button ibo-is-neutral ibo-is-regular\" onClick=\"$sOnCLick\">".Dict::S('UI:Button:Refresh')."</button></div></form>"); $oUiHtmlBlock->AddHtml("<button type=\"button\" id=\"ReloadMovieBtn\" class=\"ibo-button ibo-is-neutral ibo-is-regular\" onClick=\"$sOnCLick\">".Dict::S('UI:Button:Refresh')."</button></div></form>");
} }
$oUiHtmlBlock->AddHtml("</div>\n"); $oUiHtmlBlock->AddHtml("</div>\n");
$oUiHtmlBlock->AddHtml("</div>\n"); // class="not-printable"
$oUiSearchBlock->AddSubBlock($oUiHtmlBlock); $oUiSearchBlock->AddSubBlock($oUiHtmlBlock);
$oBlock->AddSubBlock($oUiSearchBlock); $oP->AddUiBlock($oUiSearchBlock);
return $oBlock;
}
public function GetFilteringData(string $sContextKey, array $aContextParams, array $aExcludedObjects): array
{
$aContextDefs = static::GetContextDefinitions($sContextKey, true, $aContextParams);
$aExcludedByClass = array();
foreach ($aExcludedObjects as $oObj) {
if (!array_key_exists(get_class($oObj), $aExcludedByClass)) {
$aExcludedByClass[get_class($oObj)] = array();
}
$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
}
$aAdditionalContexts = array(); $aAdditionalContexts = array();
foreach ($aContextDefs as $sKey => $aDefinition) { foreach ($aContextDefs as $sKey => $aDefinition) {
$aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes'))); $aAdditionalContexts[] = array('key' => $sKey, 'label' => Dict::S($aDefinition['dict']), 'oql' => $aDefinition['oql'], 'default' => (array_key_exists('default', $aDefinition) && ($aDefinition['default'] == 'yes')));

View File

@@ -242,33 +242,44 @@ class EventIssue extends Event
{ {
if (is_string($sValue)) if (is_string($sValue))
{ {
if (mb_strlen($sValue) < 256) { if (strlen($sValue) < 256)
{
$aPost[$sKey] = $sValue; $aPost[$sKey] = $sValue;
} else {
$aPost[$sKey] = "!long string: ".mb_strlen($sValue)." chars";
} }
} else { else
{
$aPost[$sKey] = "!long string: ".strlen($sValue). " chars";
}
}
else
{
// Not a string (avoid warnings in case the value cannot be easily casted into a string) // Not a string (avoid warnings in case the value cannot be easily casted into a string)
$aPost[$sKey] = @(string)$sValue; $aPost[$sKey] = @(string) $sValue;
} }
} }
$this->Set('arguments_post', $aPost); $this->Set('arguments_post', $aPost);
} else { }
else
{
$this->Set('arguments_post', array()); $this->Set('arguments_post', array());
} }
$sLength = mb_strlen($this->Get('issue'));
if ($sLength > 255) { $sLength = strlen($this->Get('issue'));
$this->Set('issue', mb_substr($this->Get('issue'), 0, 210)." -truncated ($sLength chars)"); if ($sLength > 255)
{
$this->Set('issue', substr($this->Get('issue'), 0, 200)." -truncated ($sLength chars)");
} }
$sLength = mb_strlen($this->Get('impact')); $sLength = strlen($this->Get('impact'));
if ($sLength > 255) { if ($sLength > 255)
$this->Set('impact', mb_substr($this->Get('impact'), 0, 210)." -truncated ($sLength chars)"); {
$this->Set('impact', substr($this->Get('impact'), 0, 200)." -truncated ($sLength chars)");
} }
$sLength = mb_strlen($this->Get('page')); $sLength = strlen($this->Get('page'));
if ($sLength > 255) { if ($sLength > 255)
$this->Set('page', mb_substr($this->Get('page'), 0, 210)." -truncated ($sLength chars)"); {
$this->Set('page', substr($this->Get('page'), 0, 200)." -truncated ($sLength chars)");
} }
} }
} }

View File

@@ -10,7 +10,6 @@ use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory; use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\Helper\ExportHelper;
require_once(APPROOT.'application/xlsxwriter.class.php'); require_once(APPROOT.'application/xlsxwriter.class.php');
@@ -83,7 +82,6 @@ class ExcelBulkExport extends TabularBulkExport
case 'xlsx_options': case 'xlsx_options':
$oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:XLSXOptions')); $oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('Core:BulkExport:XLSXOptions'));
$oPanel->AddSubBlock(ExportHelper::GetAlertForExcelMaliciousInjection());
$oMulticolumn = MultiColumnUIBlockFactory::MakeStandard(); $oMulticolumn = MultiColumnUIBlockFactory::MakeStandard();
$oPanel->AddSubBlock($oMulticolumn); $oPanel->AddSubBlock($oMulticolumn);

View File

@@ -101,7 +101,7 @@ EOF;
EOF; EOF;
SetupUtils::builddir(dirname($sFilePath)); SetupUtils::builddir(dirname($sFilePath));
file_put_contents($sFilePath, $content, LOCK_EX); file_put_contents($sFilePath, $content);
} }
} }
Dict::SetUserLanguage($sUserLang); Dict::SetUserLanguage($sUserLang);

View File

@@ -1,15 +1,27 @@
<?php <?php
/** // Copyright (C) 2010-2023 Combodo SARL
* @copyright Copyright (C) 2010-2023 Combodo SARL //
* @license http://opensource.org/licenses/AGPL-3.0 // This file is part of iTop.
*/ //
// iTop is free software; you can redistribute it and/or modify
use Combodo\iTop\Core\Kpi\KpiLogData; // it under the terms of the GNU Affero General Public License as published by
use Combodo\iTop\Service\Module\ModuleService; // 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/>
/** /**
* Measures operations duration, memory usage, etc. (and some other KPIs) * Measures operations duration, memory usage, etc. (and some other KPIs)
*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/ */
class ExecutionKPI class ExecutionKPI
@@ -18,8 +30,6 @@ class ExecutionKPI
static protected $m_bEnabled_Memory = false; static protected $m_bEnabled_Memory = false;
static protected $m_bBlameCaller = false; static protected $m_bBlameCaller = false;
static protected $m_sAllowedUser = '*'; static protected $m_sAllowedUser = '*';
static protected $m_bGenerateLegacyReport = true;
static protected $m_fSlowQueries = 0;
static protected $m_aStats = []; // Recurrent operations static protected $m_aStats = []; // Recurrent operations
static protected $m_aExecData = []; // One shot operations static protected $m_aExecData = []; // One shot operations
@@ -76,39 +86,14 @@ class ExecutionKPI
return false; return false;
} }
static public function SetGenerateLegacyReport($bReportExtensionsOnly)
{
self::$m_bGenerateLegacyReport = $bReportExtensionsOnly;
}
static public function SetSlowQueries($fSlowQueries)
{
self::$m_fSlowQueries = $fSlowQueries;
}
static public function GetDescription() static public function GetDescription()
{ {
$aFeatures = array(); $aFeatures = array();
if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration'; if (self::$m_bEnabled_Duration) $aFeatures[] = 'Duration';
if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage'; if (self::$m_bEnabled_Memory) $aFeatures[] = 'Memory usage';
$sFeatures = 'Measures: '.implode(', ', $aFeatures); $sFeatures = implode(', ', $aFeatures);
$sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'"; $sFor = self::$m_sAllowedUser == '*' ? 'EVERYBODY' : "'".trim(self::$m_sAllowedUser)."'";
$sSlowQueries = ''; return "KPI logging is active for $sFor. Measures: $sFeatures";
if (self::$m_fSlowQueries > 0) {
$sSlowQueries = ". Slow Queries: ".self::$m_fSlowQueries."s";
}
$aExtensions = [];
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$aExtensions[] = ModuleService::GetInstance()->GetModuleNameFromObject($oExtensionInstance);
}
$sExtensions = '';
if (count($aExtensions) > 0) {
$sExtensions = '. KPI Extensions: ['.implode(', ', $aExtensions).']';
}
return "KPI logging is active for $sFor. $sFeatures$sSlowQueries$sExtensions";
} }
static public function ReportStats() static public function ReportStats()
@@ -116,28 +101,7 @@ class ExecutionKPI
if (!self::IsEnabled()) return; if (!self::IsEnabled()) return;
global $fItopStarted; global $fItopStarted;
global $iItopInitialMemory;
$sExecId = microtime(); // id to differentiate the hrefs! $sExecId = microtime(); // id to differentiate the hrefs!
$sRequest = $_SERVER['REQUEST_URI'].' ('.$_SERVER['REQUEST_METHOD'].')';
if (isset($_POST['operation'])) {
$sRequest .= ' operation: '.$_POST['operation'];
}
$fStop = MyHelpers::getmicrotime();
if (($fStop - $fItopStarted) > self::$m_fSlowQueries) {
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oKPILogData = new KpiLogData(KpiLogData::TYPE_REQUEST, 'Page', $sRequest, $fItopStarted, $fStop, '', $iItopInitialMemory, $iCurrentMemory, $iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!self::$m_bGenerateLegacyReport) {
return;
}
$aBeginTimes = array(); $aBeginTimes = array();
foreach (self::$m_aExecData as $aOpStats) foreach (self::$m_aExecData as $aOpStats)
@@ -150,9 +114,9 @@ class ExecutionKPI
$sHtml = "<hr/>"; $sHtml = "<hr/>";
$sHtml .= "<div style=\"background-color: grey; padding: 10px;\">"; $sHtml .= "<div style=\"background-color: grey; padding: 10px;\">";
$sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - $sRequest</h3>"; $sHtml .= "<h3><a name=\"".md5($sExecId)."\">KPIs</a> - ".$_SERVER['REQUEST_URI']." (".$_SERVER['REQUEST_METHOD'].")</h3>";
$oStarted = DateTime::createFromFormat('U.u', $fItopStarted); $oStarted = DateTime::createFromFormat('U.u', $fItopStarted);
$sHtml .= '<p>'.$oStarted->format('Y-m-d H:i:s.u').'</p>'; $sHtml .= "<p>".$oStarted->format('Y-m-d H:i:s.u')."</p>";
$sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>"; $sHtml .= "<p>log_kpi_user_id: ".UserRights::GetUserId()."</p>";
$sHtml .= "<div>"; $sHtml .= "<div>";
$sHtml .= "<table border=\"1\" style=\"$sTableStyle\">"; $sHtml .= "<table border=\"1\" style=\"$sTableStyle\">";
@@ -293,7 +257,7 @@ class ExecutionKPI
$sTotalInter = round($fTotalInter, 3); $sTotalInter = round($fTotalInter, 3);
$sMinInter = round($fMinInter, 3); $sMinInter = round($fMinInter, 3);
$sMaxInter = round($fMaxInter, 3); $sMaxInter = round($fMaxInter, 3);
if (($fTotalInter >= self::$m_fSlowQueries)) if (($fTotalInter >= $fSlowQueries))
{ {
if ($bDisplayHeader) if ($bDisplayHeader)
{ {
@@ -321,19 +285,37 @@ class ExecutionKPI
self::Report($sHtml); self::Report($sHtml);
} }
public static function InitStats()
{
// Invoke extensions to initialize the KPI statistics
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$oExtensionInstance->InitStats();
}
}
public function __construct() public function __construct()
{ {
$this->ResetCounters(); $this->ResetCounters();
} self::Push($this);
}
/**
* Stack executions to remove children duration from stats
*
* @param \ExecutionKPI $oExecutionKPI
*/
private static function Push(ExecutionKPI $oExecutionKPI)
{
self::$m_aExecutionStack[] = $oExecutionKPI;
}
/**
* Pop current child and count its duration in its parent
*
* @param float|int $fChildDuration
*/
private static function Pop(float $fChildDuration = 0)
{
array_pop(self::$m_aExecutionStack);
// Update the parent's children duration
$oPrevExecutionKPI = end(self::$m_aExecutionStack);
if ($oPrevExecutionKPI) {
$oPrevExecutionKPI->m_fChildrenDuration += $fChildDuration;
}
}
// Get the duration since startup, and reset the counter for the next measure // Get the duration since startup, and reset the counter for the next measure
// //
@@ -341,15 +323,9 @@ class ExecutionKPI
{ {
global $fItopStarted; global $fItopStarted;
if (!self::IsEnabled()) {
return;
}
$aNewEntry = null; $aNewEntry = null;
$fStarted = $this->m_fStarted; if (self::$m_bEnabled_Duration) {
$fStopped = $this->m_fStarted;
if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime(); $fStopped = MyHelpers::getmicrotime();
$aNewEntry = array( $aNewEntry = array(
'op' => $sOperationDesc, 'op' => $sOperationDesc,
@@ -360,9 +336,6 @@ class ExecutionKPI
$this->m_fStarted = $fStopped; $this->m_fStarted = $fStopped;
} }
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory) if (self::$m_bEnabled_Memory)
{ {
$iCurrentMemory = self::memory_get_usage(); $iCurrentMemory = self::memory_get_usage();
@@ -372,118 +345,40 @@ class ExecutionKPI
} }
$aNewEntry['mem_begin'] = $this->m_iInitialMemory; $aNewEntry['mem_begin'] = $this->m_iInitialMemory;
$aNewEntry['mem_end'] = $iCurrentMemory; $aNewEntry['mem_end'] = $iCurrentMemory;
$iPeakMemory = self::memory_get_peak_usage(); if (function_exists('memory_get_peak_usage'))
$aNewEntry['mem_peak'] = $iPeakMemory; {
$aNewEntry['mem_peak'] = memory_get_peak_usage();
}
// Reset for the next operation (if the object is recycled) // Reset for the next operation (if the object is recycled)
$this->m_iInitialMemory = $iCurrentMemory; $this->m_iInitialMemory = $iCurrentMemory;
} }
if (self::$m_bEnabled_Duration || self::$m_bEnabled_Memory) { if (!is_null($aNewEntry))
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach(MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance)
{
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_REPORT,
'Step',
$sOperationDesc,
$fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
if (!is_null($aNewEntry) && self::$m_bGenerateLegacyReport)
{ {
self::$m_aExecData[] = $aNewEntry; self::$m_aExecData[] = $aNewEntry;
} }
$this->ResetCounters(); $this->ResetCounters();
} }
/**
* Compute statistics for a call to an extension
* Note: not working in dev mode (with links to env-production)
*
* @param object|string $object object called
* @param string $sMethod method called on the object
* @param string $sMessage additional message
*
* @return bool true if an extension was found for this object::method
* @throws \ReflectionException
*/
public function ComputeStatsForExtension($object, string $sMethod, string $sMessage = ''): bool
{
if (!self::IsEnabled()) {
return true;
}
$sSignature = ModuleService::GetInstance()->GetModuleMethodSignature($object, $sMethod);
if (utils::StartsWith($sSignature, '[')) {
$this->ComputeStats('Extension', "$sSignature $sMessage");
return true;
}
return false;
}
public function ComputeStats($sOperation, $sArguments) public function ComputeStats($sOperation, $sArguments)
{ {
if (!self::IsEnabled()) {
return;
}
$fDuration = 0; $fDuration = 0;
if (self::$m_bEnabled_Duration) { if (self::$m_bEnabled_Duration) {
$fStopped = MyHelpers::getmicrotime(); $fStopped = MyHelpers::getmicrotime();
$fDuration = $fStopped - $this->m_fStarted; $fDuration = $fStopped - $this->m_fStarted;
$aCallstack = []; $fSelfDuration = $fDuration - $this->m_fChildrenDuration;
if (self::$m_bGenerateLegacyReport) { if (self::$m_bBlameCaller) {
if (self::$m_bBlameCaller) { self::$m_aStats[$sOperation][$sArguments][] = array(
$aCallstack = MyHelpers::get_callstack(1); 'time' => $fSelfDuration,
self::$m_aStats[$sOperation][$sArguments][] = [ 'callers' => MyHelpers::get_callstack(1),
'time' => $fDuration, );
'callers' => $aCallstack, } else {
]; self::$m_aStats[$sOperation][$sArguments][] = array(
} else { 'time' => $fSelfDuration,
self::$m_aStats[$sOperation][$sArguments][] = [ );
'time' => $fDuration }
]; }
} self::Pop($fDuration);
}
$iInitialMemory = is_null($this->m_iInitialMemory) ? 0 : $this->m_iInitialMemory;
$iCurrentMemory = 0;
$iPeakMemory = 0;
if (self::$m_bEnabled_Memory)
{
$iCurrentMemory = self::memory_get_usage();
$iPeakMemory = self::memory_get_peak_usage();
}
// Invoke extensions to log the KPI operation
/** @var \iKPILoggerExtension $oExtensionInstance */
foreach (MetaModel::EnumPlugins('iKPILoggerExtension') as $oExtensionInstance) {
$sExtension = ModuleService::GetInstance()->GetModuleNameFromCallStack(1);
$oKPILogData = new KpiLogData(
KpiLogData::TYPE_STATS,
$sOperation,
$sArguments,
$this->m_fStarted,
$fStopped,
$sExtension,
$iInitialMemory,
$iCurrentMemory,
$iPeakMemory,
$aCallstack);
$oExtensionInstance->LogOperation($oKPILogData);
}
}
} }
protected function ResetCounters() protected function ResetCounters()
@@ -513,7 +408,35 @@ class ExecutionKPI
static protected function memory_get_usage() static protected function memory_get_usage()
{ {
return memory_get_usage(true); if (function_exists('memory_get_usage'))
{
return memory_get_usage(true);
}
// Copied from the PHP manual
//
//If its Windows
//Tested on Win XP Pro SP2. Should work on Win 2003 Server too
//Doesn't work for 2000
//If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642
if (substr(PHP_OS,0,3) == 'WIN')
{
$output = array();
exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output);
return preg_replace( '/[\D]/', '', $output[5] ) * 1024;
}
else
{
//We now assume the OS is UNIX
//Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4
//This should work on most UNIX systems
$pid = getmypid();
exec("ps -eo%mem,rss,pid | grep $pid", $output);
$output = explode(" ", $output[0]);
//rss is given in 1024 byte units
return $output[1] * 1024;
}
} }
static public function memory_get_peak_usage($bRealUsage = false) static public function memory_get_peak_usage($bRealUsage = false)

View File

@@ -3,7 +3,7 @@
// //
// This file is part of iTop. // This file is part of iTop.
// //
// iTop is free software; you can redistribute it and/or modify // iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // 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 // the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version. // (at your option) any later version.
@@ -576,11 +576,6 @@ class LogChannels
public const DATATABLE = 'Datatable'; public const DATATABLE = 'Datatable';
public const DEADLOCK = 'DeadLock'; public const DEADLOCK = 'DeadLock';
/**
* @var string Everything related to PHP sessions tracking
* @since 3.1.1 3.2.0 N°6901
*/
public const SESSIONTRACKER = 'SessionTracker';
/** /**
* @var string Everything related to the datamodel CRUD * @var string Everything related to the datamodel CRUD
@@ -588,12 +583,6 @@ class LogChannels
*/ */
public const DM_CRUD = 'DMCRUD'; public const DM_CRUD = 'DMCRUD';
/**
* @var string Everything related to the datamodel CRUD
* @since 3.1.0
*/
public const WEB_REQUEST = 'WebRequest';
/** /**
* @var string Everything related to the event service * @var string Everything related to the event service
* @since 3.1.0 * @since 3.1.0
@@ -617,8 +606,6 @@ class LogChannels
public const PORTAL = 'portal'; public const PORTAL = 'portal';
public const TEMPORARY_OBJECTS = 'TemporaryObjects';
/** /**
* @var string * @var string
* @since 3.1.0 * @since 3.1.0
@@ -1143,13 +1130,9 @@ class DeprecatedCallsLog extends LogAPI
parent::Enable($sTargetFile); parent::Enable($sTargetFile);
if ( if (
( (false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))
(false === defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME))
|| (defined(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME) && (constant(ITOP_PHPUNIT_RUNNING_CONSTANT_NAME) !== true))
)
&& static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD) && static::IsLogLevelEnabledSafe(self::LEVEL_WARNING, self::ENUM_CHANNEL_PHP_LIBMETHOD)
) { ) {
IssueLog::Trace('Setting '.static::class.' error handler to catch DEPRECATED', static::ENUM_CHANNEL_PHP_LIBMETHOD);
set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED); set_error_handler([static::class, 'DeprecatedNoticesErrorHandler'], E_DEPRECATED | E_USER_DEPRECATED);
} }
} }
@@ -1247,9 +1230,7 @@ class DeprecatedCallsLog extends LogAPI
} }
/** /**
* @since 3.0.1 3.1.0 N°4725 silently handles ConfigException * @throws \ConfigException
* @since 3.0.4 3.1.0 N°4725 remove forgotten throw PHPDoc annotation
*
* @link https://www.php.net/debug_backtrace * @link https://www.php.net/debug_backtrace
* @uses \debug_backtrace() * @uses \debug_backtrace()
*/ */
@@ -1301,7 +1282,19 @@ class DeprecatedCallsLog extends LogAPI
} }
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); $aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
$sMessage = self::GetMessageFromStack($aStack); $iStackDeprecatedMethodLevel = 1; // level 0 = current method, level 1 = method containing the `NotifyDeprecatedPhpMethod` call
$sDeprecatedObject = $aStack[$iStackDeprecatedMethodLevel]['class'];
$sDeprecatedMethod = $aStack[$iStackDeprecatedMethodLevel]['function'];
$sCallerFile = $aStack[$iStackDeprecatedMethodLevel]['file'];
$sCallerLine = $aStack[$iStackDeprecatedMethodLevel]['line'];
$sMessage = "Call to {$sDeprecatedObject}::{$sDeprecatedMethod} in {$sCallerFile}#L{$sCallerLine}";
$iStackCallerMethodLevel = $iStackDeprecatedMethodLevel + 1; // level 2 = caller of the deprecated method
if (array_key_exists($iStackCallerMethodLevel, $aStack)) {
$sCallerObject = $aStack[$iStackCallerMethodLevel]['class'];
$sCallerMethod = $aStack[$iStackCallerMethodLevel]['function'];
$sMessage .= " ({$sCallerObject}::{$sCallerMethod})";
}
if (!is_null($sAdditionalMessage)) { if (!is_null($sAdditionalMessage)) {
$sMessage .= ' : '.$sAdditionalMessage; $sMessage .= ' : '.$sAdditionalMessage;
@@ -1310,45 +1303,6 @@ class DeprecatedCallsLog extends LogAPI
static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD); static::Warning($sMessage, self::ENUM_CHANNEL_PHP_METHOD);
} }
/**
* @param array $aDebugBacktrace data from {@see debug_backtrace()}
*
* @return string message to print to the log
*/
private static function GetMessageFromStack(array $aDebugBacktrace): string
{
// level 0 = current method
// level 1 = deprecated method, containing the `NotifyDeprecatedPhpMethod` call
$sMessage = 'Call'.self::GetMessageForCurrentStackLevel($aDebugBacktrace[1], " to ");
// level 2 = caller of the deprecated method
if (array_key_exists(2, $aDebugBacktrace)) {
$sMessage .= ' (from ';
$sMessage .= self::GetMessageForCurrentStackLevel($aDebugBacktrace[2]);
$sMessage .= ')';
}
return $sMessage;
}
private static function GetMessageForCurrentStackLevel(array $aCurrentLevelDebugTrace, ?string $sPrefix = ""): string
{
$sMessage = "";
if (array_key_exists('class', $aCurrentLevelDebugTrace)) {
$sDeprecatedObject = $aCurrentLevelDebugTrace['class'];
$sDeprecatedMethod = $aCurrentLevelDebugTrace['function'] ?? "";
$sMessage = "{$sPrefix}{$sDeprecatedObject}::{$sDeprecatedMethod} in ";
}
if (array_key_exists('file', $aCurrentLevelDebugTrace)) {
$sCallerFile = $aCurrentLevelDebugTrace['file'];
$sCallerLine = $aCurrentLevelDebugTrace['line'] ?? "";
$sMessage .= "{$sCallerFile}#L{$sCallerLine}";
}
return $sMessage;
}
/** /**
* @param string|null $sAdditionalMessage * @param string|null $sAdditionalMessage
* @since 3.1.0 * @since 3.1.0
@@ -1450,7 +1404,7 @@ class LogFileRotationProcess implements iScheduledProcess
$iMaxDays = MetaModel::GetConfig()->Get(LogAPI::ENUM_CONFIG_PARAM_PURGE_MAX_KEEP_DAYS); $iMaxDays = MetaModel::GetConfig()->Get(LogAPI::ENUM_CONFIG_PARAM_PURGE_MAX_KEEP_DAYS);
// Files iterator (*.*) // Files iterator (*.*)
$oIterator = new \GlobIterator(APPROOT.'log'.DIRECTORY_SEPARATOR.'*.*'); $oIterator = new \GlobIterator(APPROOT.'log'.DIRECTORY_SEPARATOR.'/*.*');
$aLogFiles = iterator_to_array($oIterator); $aLogFiles = iterator_to_array($oIterator);
// Reference date // Reference date
@@ -1462,11 +1416,6 @@ class LogFileRotationProcess implements iScheduledProcess
// File real path // File real path
$sFileRealPath = $oLogFile->getRealPath(); $sFileRealPath = $oLogFile->getRealPath();
// Check file extension
if(!in_array($oLogFile->getExtension(), ['log','sql','xml'])){
continue;
}
// Compute number of days since last modification // Compute number of days since last modification
$oDateFileLastModification = new DateTime(); $oDateFileLastModification = new DateTime();
$oDateFileLastModification->setTimestamp($oLogFile->getMTime()); $oDateFileLastModification->setTimestamp($oLogFile->getMTime());
@@ -1707,8 +1656,6 @@ class ExceptionLog extends LogAPI
*/ */
private static function GetLastEventIssue() private static function GetLastEventIssue()
{ {
$oRet = self::$oLastEventIssue; return self::$oLastEventIssue;
self::$oLastEventIssue = null;
return $oRet;
} }
} }

View File

@@ -17,10 +17,7 @@
// along with iTop. If not, see <http://www.gnu.org/licenses/> // along with iTop. If not, see <http://www.gnu.org/licenses/>
// //
use Combodo\iTop\Application\EventRegister\ApplicationEvents;
use Combodo\iTop\Core\MetaModel\FriendlyNameType; use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
require_once APPROOT.'core/modulehandler.class.inc.php'; require_once APPROOT.'core/modulehandler.class.inc.php';
require_once APPROOT.'core/querymodifier.class.inc.php'; require_once APPROOT.'core/querymodifier.class.inc.php';
@@ -1241,7 +1238,7 @@ abstract class MetaModel
} }
$sTable = self::DBGetTable($sClass); $sTable = self::DBGetTable($sClass);
// Could be completed later with all the classes that are using a given table // Could be completed later with all the classes that are using a given table
if (!array_key_exists($sTable, $aTables)) { if (!array_key_exists($sTable, $aTables)) {
$aTables[$sTable] = array(); $aTables[$sTable] = array();
} }
@@ -1445,10 +1442,8 @@ abstract class MetaModel
* *
* @return AttributeDefinition[] * @return AttributeDefinition[]
* @throws \CoreException * @throws \CoreException
*
* @see GetAttributesList for attcode list
*/ */
final public static function ListAttributeDefs($sClass) final static public function ListAttributeDefs($sClass)
{ {
self::_check_subclass($sClass); self::_check_subclass($sClass);
return self::$m_aAttribDefs[$sClass]; return self::$m_aAttribDefs[$sClass];
@@ -1461,10 +1456,8 @@ abstract class MetaModel
* @param string[] $aDesiredAttTypes Array of AttributeDefinition classes to filter the list on * @param string[] $aDesiredAttTypes Array of AttributeDefinition classes to filter the list on
* @param string|null $sListCode If provided, attributes will be limited to those in this zlist * @param string|null $sListCode If provided, attributes will be limited to those in this zlist
* *
* @return string[] list of attcodes * @return array
* @throws \CoreException * @throws \CoreException
*
* @see ListAttributeDefs to get AttributeDefinition array instead
*/ */
final public static function GetAttributesList(string $sClass, array $aDesiredAttTypes = [], ?string $sListCode = null) final public static function GetAttributesList(string $sClass, array $aDesiredAttTypes = [], ?string $sListCode = null)
{ {
@@ -2931,7 +2924,52 @@ abstract class MetaModel
} }
self::$m_sTablePrefix = $sTablePrefix; self::$m_sTablePrefix = $sTablePrefix;
self::InitExtensions();
// Build the list of available extensions
//
$aInterfaces = [
'iApplicationUIExtension',
'iPreferencesExtension',
'iApplicationObjectExtension',
'iLoginFSMExtension',
'iLoginUIExtension',
'iLogoutExtension',
'iQueryModifier',
'iOnClassInitialization',
'iPopupMenuExtension',
'iPageUIExtension',
'iPageUIBlockExtension',
'iBackofficeLinkedScriptsExtension',
'iBackofficeEarlyScriptExtension',
'iBackofficeScriptExtension',
'iBackofficeInitScriptExtension',
'iBackofficeReadyScriptExtension',
'iBackofficeLinkedStylesheetsExtension',
'iBackofficeStyleExtension',
'iBackofficeDictEntriesExtension',
'iBackofficeDictEntriesPrefixesExtension',
'iPortalUIExtension',
'ModuleHandlerApiInterface',
'iNewsroomProvider',
'iModuleExtension',
];
foreach($aInterfaces as $sInterface)
{
self::$m_aExtensionClassNames[$sInterface] = array();
}
foreach(get_declared_classes() as $sPHPClass)
{
$oRefClass = new ReflectionClass($sPHPClass);
$oExtensionInstance = null;
foreach($aInterfaces as $sInterface)
{
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable())
{
self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass;
}
}
}
// Initialize the classes (declared attributes, etc.) // Initialize the classes (declared attributes, etc.)
// //
@@ -3526,7 +3564,7 @@ abstract class MetaModel
} }
// Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well // Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well
// and this needs to be know early (for Init_IsKnowClass 19 lines below) // and this needs to be know early (for Init_IsKnowClass 19 lines below)
$oAtt->SetHostClass($sTargetClass); $oAtt->SetHostClass($sTargetClass);
// Some attributes could refer to a class // Some attributes could refer to a class
@@ -3568,7 +3606,7 @@ abstract class MetaModel
self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt;
self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass;
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
} }
/** /**
@@ -3580,7 +3618,8 @@ abstract class MetaModel
{ {
MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos);
if (!$sTargetClass) { if (!$sTargetClass)
{
$sTargetClass = self::GetCallersPHPClass("Init"); $sTargetClass = self::GetCallersPHPClass("Init");
} }
@@ -3768,7 +3807,7 @@ abstract class MetaModel
self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus; self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus;
// I wanted to simplify the syntax of the declaration of objects in the biz model // I wanted to simplify the syntax of the declaration of objects in the biz model
// Therefore, the reference to the host class is set there // Therefore, the reference to the host class is set there
$oStimulus->SetHostClass($sTargetClass); $oStimulus->SetHostClass($sTargetClass);
} }
@@ -4223,78 +4262,40 @@ abstract class MetaModel
} }
else else
{ {
$aCurrentUser = []; $aCurrentUser = array();
$aCurrentContact = []; $aCurrentContact = array();
foreach ($aExpectedArgs as $expression) foreach ($aExpectedArgs as $expression)
{ {
$aName = explode('->', $expression->GetName()); $aName = explode('->', $expression->GetName());
if ($aName[0] == 'current_contact_id') { if ($aName[0] == 'current_contact_id') {
$aPlaceholders['current_contact_id'] = UserRights::GetContactId(); $aPlaceholders['current_contact_id'] = UserRights::GetContactId();
} else if ($aName[0] == 'current_user') { }
if ($aName[0] == 'current_user') {
array_push($aCurrentUser, $aName[1]); array_push($aCurrentUser, $aName[1]);
} else if ($aName[0] == 'current_contact') { }
if ($aName[0] == 'current_contact') {
array_push($aCurrentContact, $aName[1]); array_push($aCurrentContact, $aName[1]);
} }
} }
if (count($aCurrentUser) > 0) { if (count($aCurrentUser) > 0) {
static::FillObjectPlaceholders($aPlaceholders, 'current_user', UserRights::GetUserObject(), $aCurrentUser); $oUser = UserRights::GetUserObject();
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField) {
$aPlaceholders['current_user->'.$sField] = $oUser->Get($sField);
}
} }
if (count($aCurrentContact) > 0) { if (count($aCurrentContact) > 0) {
static::FillObjectPlaceholders($aPlaceholders, 'current_contact', UserRights::GetContactObject(), $aCurrentContact); $oPerson = UserRights::GetContactObject();
$aPlaceholders['current_contact->object()'] = $oPerson;
foreach ($aCurrentContact as $sField) {
$aPlaceholders['current_contact->'.$sField] = $oPerson->Get($sField);
}
} }
} }
return $aPlaceholders; return $aPlaceholders;
} }
/**
* @since 3.1.1 N°6824
* @param array $aPlaceholders
* @param string $sPlaceHolderPrefix
* @param ?\DBObject $oObject
* @param array $aCurrentUser
*
* @return void
*
*/
private static function FillObjectPlaceholders(array &$aPlaceholders, string $sPlaceHolderPrefix, ?\DBObject $oObject, array $aCurrentUser) : void {
$sPlaceHolderKey = $sPlaceHolderPrefix."->object()";
if (is_null($oObject)){
$aContext = [
"current_user_id" => UserRights::GetUserId(),
"null object type" => $sPlaceHolderPrefix,
"fields" => $aCurrentUser,
];
IssueLog::Warning("Unresolved placeholders due to null object in current context", null,
$aContext);
$aPlaceholders[$sPlaceHolderKey] = Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
foreach ($aCurrentUser as $sField) {
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
$aPlaceholders[$sPlaceHolderKey] = Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
}
} else {
$aPlaceholders[$sPlaceHolderKey] = $oObject;
foreach ($aCurrentUser as $sField) {
$sPlaceHolderKey = $sPlaceHolderPrefix . "->$sField";
// Mind that the "id" is not viewed as a valid att. code by \MetaModel::IsValidAttCode() so we have to test it manually
if ($sField !== "id" && false === MetaModel::IsValidAttCode(get_class($oObject), $sField)){
$aContext = [
"current_user_id" => UserRights::GetUserId(),
"obj_class" => get_class($oObject),
"placeholder" => $sPlaceHolderKey,
"invalid_field" => $sField,
];
IssueLog::Warning("Unresolved placeholder due to invalid attribute", null,
$aContext);
$aPlaceholders[$sPlaceHolderKey] = Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
continue;
}
$aPlaceholders[$sPlaceHolderKey] = $oObject->Get($sField);
}
}
}
/** /**
* @param \DBSearch $oFilter * @param \DBSearch $oFilter
* *
@@ -5156,7 +5157,7 @@ abstract class MetaModel
*/ */
protected static function DBCreateTables($aCallback = null) protected static function DBCreateTables($aCallback = null)
{ {
[$aErrors, $aSugFix, $aCondensedQueries] = self::DBCheckFormat(); list($aErrors, $aSugFix, $aCondensedQueries) = self::DBCheckFormat();
//$sSQL = implode('; ', $aCondensedQueries); Does not work - multiple queries not allowed //$sSQL = implode('; ', $aCondensedQueries); Does not work - multiple queries not allowed
foreach($aCondensedQueries as $sQuery) foreach($aCondensedQueries as $sQuery)
@@ -5178,7 +5179,7 @@ abstract class MetaModel
*/ */
protected static function DBCreateViews() protected static function DBCreateViews()
{ {
[$aErrors, $aSugFix] = self::DBCheckViews(); list($aErrors, $aSugFix) = self::DBCheckViews();
foreach($aSugFix as $sClass => $aTarget) foreach($aSugFix as $sClass => $aTarget)
{ {
@@ -6340,46 +6341,38 @@ abstract class MetaModel
*/ */
public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production') public static function Startup($config, $bModelOnly = false, $bAllowCache = true, $bTraceSourceFiles = false, $sEnvironment = 'production')
{ {
// Startup on a new environment is not supported
static $bStarted = false;
if ($bStarted) {
return;
}
$bStarted = true;
self::$m_sEnvironment = $sEnvironment; self::$m_sEnvironment = $sEnvironment;
try { if (!defined('MODULESROOT'))
if (!defined('MODULESROOT')) { {
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/'); define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
self::$m_bTraceSourceFiles = $bTraceSourceFiles; self::$m_bTraceSourceFiles = $bTraceSourceFiles;
// $config can be either a filename, or a Configuration object (volatile!) // $config can be either a filename, or a Configuration object (volatile!)
if ($config instanceof Config) { if ($config instanceof Config)
self::LoadConfig($config, $bAllowCache); {
} else { self::LoadConfig($config, $bAllowCache);
self::LoadConfig(new Config($config), $bAllowCache); }
} else
{
if ($bModelOnly) { self::LoadConfig(new Config($config), $bAllowCache);
return;
}
} }
CMDBSource::SelectDB(self::$m_sDBName); if ($bModelOnly)
{
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) { return;
$oPHPClass::OnMetaModelStarted();
} }
}
ExpressionCache::Warmup(); CMDBSource::SelectDB(self::$m_sDBName);
}
finally { foreach(MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass)
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface {
EventService::InitService(); $oPHPClass::OnMetaModelStarted();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED)); }
}
ExpressionCache::Warmup();
} }
/** /**
@@ -6425,9 +6418,7 @@ abstract class MetaModel
ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration')); ExecutionKPI::EnableDuration(self::$m_oConfig->Get('log_kpi_duration'));
ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory')); ExecutionKPI::EnableMemory(self::$m_oConfig->Get('log_kpi_memory'));
ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id')); ExecutionKPI::SetAllowedUser(self::$m_oConfig->Get('log_kpi_user_id'));
ExecutionKPI::SetGenerateLegacyReport(self::$m_oConfig->Get('log_kpi_generate_legacy_report'));
ExecutionKPI::SetSlowQueries(self::$m_oConfig->Get('log_kpi_slow_queries'));
self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write'); self::$m_bSkipCheckToWrite = self::$m_oConfig->Get('skip_check_to_write');
self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys'); self::$m_bSkipCheckExtKeys = self::$m_oConfig->Get('skip_check_ext_keys');
@@ -6521,7 +6512,7 @@ abstract class MetaModel
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames; $aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
$aCache['m_Category2Class'] = self::$m_Category2Class; $aCache['m_Category2Class'] = self::$m_Category2Class;
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass" $aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass") $aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
$aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass") $aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass")
$aCache['m_aClassParams'] = self::$m_aClassParams; // array of ("classname" => array of class information) $aCache['m_aClassParams'] = self::$m_aClassParams; // array of ("classname" => array of class information)
$aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes) $aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes)
@@ -6546,7 +6537,6 @@ abstract class MetaModel
CMDBSource::InitFromConfig(self::$m_oConfig); CMDBSource::InitFromConfig(self::$m_oConfig);
// Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone); // Later when timezone implementation is correctly done: CMDBSource::SetTimezone($sDBTimezone);
ExecutionKPI::InitStats();
} }
/** /**
@@ -6578,19 +6568,6 @@ abstract class MetaModel
return $value; return $value;
} }
/**
* @internal Used for resetting the configuration during automated tests
* @param \Config $oConfiguration
*
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
public static function SetConfig(Config $oConfiguration)
{
self::$m_oConfig = $oConfiguration;
}
/** /**
* @return Config * @return Config
*/ */
@@ -6779,13 +6756,7 @@ abstract class MetaModel
if ($bMustBeFound && empty($aRow)) if ($bMustBeFound && empty($aRow))
{ {
$sNotFoundErrorMessage = "No result for the single row query"; throw new CoreException("No result for the single row query: '$sSQL'");
IssueLog::Info($sNotFoundErrorMessage, LogChannels::CMDB_SOURCE, [
'class' => $sClass,
'key' => $iKey,
'sql_query' => $sSQL,
]);
throw new CoreException($sNotFoundErrorMessage);
} }
return $aRow; return $aRow;
@@ -6846,15 +6817,24 @@ abstract class MetaModel
$sClass = $aRow[$sClassAlias."finalclass"]; $sClass = $aRow[$sClassAlias."finalclass"];
} }
// 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.
if (array_key_exists($sClassAlias.'id', $aRow)) {
$iKey = $aRow[$sClassAlias."id"];
$oObject = self::GetReentranceObject($sClass, $iKey);
if ($oObject !== false) {
return $oObject;
}
}
return new $sClass($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec); return new $sClass($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
} }
/** /**
* Instantiate an object already persisted to the Database. * Instantiate an object already persisted to the Database.
* *
* Note that LinkedSet attributes are not loaded.
* DBObject::Reload() will be called when getting a LinkedSet attribute
*
* @api * @api
* @see MetaModel::GetObjectWithArchive to get object even if it's archived * @see MetaModel::GetObjectWithArchive to get object even if it's archived
* @see utils::PushArchiveMode() to enable search on archived objects * @see utils::PushArchiveMode() to enable search on archived objects
@@ -6869,21 +6849,25 @@ abstract class MetaModel
* $bMustBeFound=false) * $bMustBeFound=false)
* @throws CoreException if no result found and $bMustBeFound=true * @throws CoreException if no result found and $bMustBeFound=true
* @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true * @throws ArchivedObjectException if archive mode disabled and result is archived and $bMustBeFound=true
* @throws \Exception
*
*/ */
public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null) public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
{ {
$oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties); $oObject = self::GetObjectWithArchive($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
if (empty($oObject)) { if (empty($oObject))
{
return null; return null;
} }
if (!utils::IsArchiveMode() && $oObject->IsArchived()) { if (!utils::IsArchiveMode() && $oObject->IsArchived())
{
if ($bMustBeFound) { if ($bMustBeFound) {
throw new ArchivedObjectException("The object $sClass::$iKey is archived"); throw new ArchivedObjectException("The object $sClass::$iKey is archived");
} else {
return null;
} }
return null;
} }
return $oObject; return $oObject;
@@ -6918,22 +6902,6 @@ abstract class MetaModel
return $iCount === 1; return $iCount === 1;
} }
public static function GetFinalClassName(string $sClass, int $iKey): string
{
if (MetaModel::IsStandaloneClass($sClass)) {
return $sClass;
}
$sRootClass = MetaModel::GetRootClass($sClass);
$sTable = MetaModel::DBGetTable($sRootClass);
$sKeyCol = MetaModel::DBGetKey($sRootClass);
$sEscapedKey = CMDBSource::Quote($iKey);
$sFinalClassField = Metamodel::DBGetClassField($sRootClass);
$sQuery = "SELECT `{$sFinalClassField}` FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
return CMDBSource::QueryToScalar($sQuery);
}
/** /**
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4 * Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
* module compatibility, see N.1108) * module compatibility, see N.1108)
@@ -7671,57 +7639,6 @@ abstract class MetaModel
unset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()]); unset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()]);
} }
} }
/**
* For test purpose
* @throws \ReflectionException
* @since 3.1.0
*/
public static function InitExtensions()
{
// Build the list of available extensions
//
$aInterfaces = [
'iLoginFSMExtension',
'iLogoutExtension',
'iLoginUIExtension',
'iPreferencesExtension',
'iApplicationUIExtension',
'iApplicationObjectExtension',
'iPopupMenuExtension',
'iPageUIExtension',
'iPageUIBlockExtension',
'iBackofficeLinkedScriptsExtension',
'iBackofficeEarlyScriptExtension',
'iBackofficeScriptExtension',
'iBackofficeInitScriptExtension',
'iBackofficeReadyScriptExtension',
'iBackofficeLinkedStylesheetsExtension',
'iBackofficeStyleExtension',
'iBackofficeDictEntriesExtension',
'iBackofficeDictEntriesPrefixesExtension',
'iPortalUIExtension',
'iQueryModifier',
'iOnClassInitialization',
'iModuleExtension',
'iKPILoggerExtension',
'ModuleHandlerApiInterface',
'iNewsroomProvider',
];
foreach ($aInterfaces as $sInterface) {
self::$m_aExtensionClassNames[$sInterface] = array();
}
foreach (get_declared_classes() as $sPHPClass) {
$oRefClass = new ReflectionClass($sPHPClass);
$oExtensionInstance = null;
foreach ($aInterfaces as $sInterface) {
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) {
self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass;
}
}
}
}
} }

View File

@@ -22,9 +22,7 @@
* A class to serialize the execution of some code sections * A class to serialize the execution of some code sections
* Emulates the API of PECL Mutex class * Emulates the API of PECL Mutex class
* Relies on MySQL locks because the API sem_get is not always present in the * Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP. * installed PHP.
*
* @link https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html MySQL locking functions documentation
* *
* @copyright Copyright (C) 2013-2023 Combodo SARL * @copyright Copyright (C) 2013-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0 * @license http://opensource.org/licenses/AGPL-3.0
@@ -259,7 +257,7 @@ class iTopMutex
$this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false); $this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false);
if (!$this->hDBLink) { if (!$this->hDBLink) {
throw new MySQLException('Could not connect to the DB server '.mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno(), array('host' => $sDBHost, 'user' => $sDBUser)); throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')');
} }
// Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection, // Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection,

View File

@@ -3526,7 +3526,6 @@ class CharConcatWSExpression extends CharConcatExpression
$aRes = array(); $aRes = array();
foreach ($this->m_aExpressions as $oExpr) foreach ($this->m_aExpressions as $oExpr)
{ {
// TODO: Seems weird, this should rather be $aRes[] = $oExpr->Evaluate($aArgs);
$aRes .= $oExpr->Evaluate($aArgs); $aRes .= $oExpr->Evaluate($aArgs);
} }
return implode($this->m_separator, $aRes); return implode($this->m_separator, $aRes);

View File

@@ -169,7 +169,7 @@ class ormCaseLog {
} }
// Process the case of an eventual remainder (quick migration of AttributeText fields) // Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (utils::StrLen($this->m_sLog) - 1)) if ($iPos < (strlen($this->m_sLog) - 1))
{ {
$sTextEntry = substr($this->m_sLog, $iPos); $sTextEntry = substr($this->m_sLog, $iPos);
@@ -292,7 +292,7 @@ class ormCaseLog {
} }
// Process the case of an eventual remainder (quick migration of AttributeText fields) // Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (utils::StrLen($this->m_sLog) - 1)) { if ($iPos < (strlen($this->m_sLog) - 1)) {
$sTextEntry = substr($this->m_sLog, $iPos); $sTextEntry = substr($this->m_sLog, $iPos);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry)); $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry));
@@ -373,7 +373,7 @@ class ormCaseLog {
} }
// Process the case of an eventual remainder (quick migration of AttributeText fields) // Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (utils::StrLen($this->m_sLog) - 1)) { if ($iPos < (strlen($this->m_sLog) - 1)) {
$sTextEntry = substr($this->m_sLog, $iPos); $sTextEntry = substr($this->m_sLog, $iPos);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry)); $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry));
@@ -467,7 +467,7 @@ class ormCaseLog {
$oBlock->AddSubBlock($oCollapsibleBlock); $oBlock->AddSubBlock($oCollapsibleBlock);
} }
// Process the case of an eventual remainder (quick migration of AttributeText fields) // Process the case of an eventual remainder (quick migration of AttributeText fields)
if ($iPos < (utils::StrLen($this->m_sLog) - 1)) { if ($iPos < (strlen($this->m_sLog) - 1)) {
// In this case the format is always "text" // In this case the format is always "text"
$sTextEntry = substr($this->m_sLog, $iPos); $sTextEntry = substr($this->m_sLog, $iPos);
$sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry)); $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", utils::EscapeHtml($sTextEntry));

View File

@@ -35,11 +35,10 @@ class ormCustomFieldsValue
* _template_name: string, * _template_name: string,
* template_id: string, * template_id: string,
* template_data: string, * template_data: string,
* user_data: array<string, mixed>, * user_data: string,
* current_template_id: string, * current_template_id: string,
* current_template_data: string, * current_template_data: string,
* } $aCurrentValues Containing JSON encoded strings in template_data/current_template_data. * } $aCurrentValues Containing JSON encoded strings in template_data/current_template_data, user_data.
* The user_data key contains an array with field code as key and field value as value
* Warning, current_* are mandatory for data to be saved in a DBUpdate() call ! * Warning, current_* are mandatory for data to be saved in a DBUpdate() call !
*/ */
protected $aCurrentValues; protected $aCurrentValues;
@@ -49,31 +48,13 @@ class ormCustomFieldsValue
* @param string $sAttCode * @param string $sAttCode
* @param array $aCurrentValues * @param array $aCurrentValues
*/ */
public function __construct(?DBObject $oHostObject, $sAttCode, $aCurrentValues = null) public function __construct(DBObject $oHostObject, $sAttCode, $aCurrentValues = null)
{ {
$this->oHostObject = $oHostObject; $this->oHostObject = $oHostObject;
$this->sAttCode = $sAttCode; $this->sAttCode = $sAttCode;
$this->aCurrentValues = $aCurrentValues; $this->aCurrentValues = $aCurrentValues;
} }
/**
* @return \DBObject|null
*/
public function GetHostObject(): ?DBObject
{
return $this->oHostObject;
}
/**
* @param \DBObject|null $oHostObject
*
* @return void
*/
public function SetHostObject(?DBObject $oHostObject): void
{
$this->oHostObject = $oHostObject;
}
public function GetValues() public function GetValues()
{ {
return $this->aCurrentValues; return $this->aCurrentValues;
@@ -81,7 +62,6 @@ class ormCustomFieldsValue
/** /**
* Wrapper used when the only thing you have is the value... * Wrapper used when the only thing you have is the value...
*
* @return \Combodo\iTop\Form\Form * @return \Combodo\iTop\Form\Form
*/ */
public function GetForm($sFormPrefix = null) public function GetForm($sFormPrefix = null)
@@ -116,19 +96,6 @@ class ormCustomFieldsValue
return $this->GetHandler()->GetAsJSON($this->aCurrentValues); return $this->GetHandler()->GetAsJSON($this->aCurrentValues);
} }
/**
* @param string|null $json
* @param \AttributeDefinition $oAttDef
*
* @return \ormCustomFieldsValue
*
* @since 3.1.0 N°1150 Method creation
*/
public static function FromJSONToValue(?stdClass $json, AttributeCustomFields $oAttDef)
{
return $oAttDef->GetHandler()->FromJSONToValue($json, $oAttDef->GetCode());
}
/** /**
* @return \CustomFieldsHandler * @return \CustomFieldsHandler
* @throws \Exception * @throws \Exception
@@ -136,7 +103,6 @@ class ormCustomFieldsValue
*/ */
final protected function GetHandler() final protected function GetHandler()
{ {
/** @var \AttributeCustomFields $oAttDef */
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode); $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
return $oAttDef->GetHandler($this->GetValues()); return $oAttDef->GetHandler($this->GetValues());

View File

@@ -86,33 +86,6 @@ class ormDocument
{ {
return ($this->m_data == null); return ($this->m_data == null);
} }
/**
* @param \ormDocument $oCompared
*
* @return bool True if the current ormDocument is equals to $oCompared EXCEPT for its download count. False if any other property is different OR if count is the same.
* @since 3.1.0 N°6502
*/
public function EqualsExceptDownloadsCount(ormDocument $oCompared): bool
{
// First checking equality on others properties
if ($oCompared->GetData() !== $this->GetData()) {
return false;
}
if ($oCompared->GetMimeType() !== $this->GetMimeType()) {
return false;
}
if ($oCompared->GetFileName() !== $this->GetFileName()) {
return false;
}
// Finally check equality of the download count
if ($oCompared->GetDownloadsCount() === $this->GetDownloadsCount()) {
return false;
} else {
return true;
}
}
public function GetMimeType() public function GetMimeType()
{ {
@@ -343,6 +316,6 @@ class ormDocument
*/ */
public function GetSignature(): string public function GetSignature(): string
{ {
return md5($this->GetData() ?? ''); return md5($this->GetData());
} }
} }

View File

@@ -98,14 +98,4 @@ class PluginManager
return $oInstance; return $oInstance;
} }
/**
* For test purpose
* @return void
* @since 3.1.0
*/
protected static function ResetPlugins()
{
self::$m_aExtensionClasses = null;
}
} }

View File

@@ -43,12 +43,6 @@
class SimpleCrypt class SimpleCrypt
{ {
/**
* @var \SimpleCrypt
* @since 3.1.0 N°5388
*/
protected $oEngine;
public static function GetNewDefaultParams() public static function GetNewDefaultParams()
{ {
if(function_exists('sodium_crypto_secretbox_open') && function_exists('random_bytes')){ if(function_exists('sodium_crypto_secretbox_open') && function_exists('random_bytes')){
@@ -68,7 +62,6 @@ class SimpleCrypt
$sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine'; $sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine';
return $sEngineName::GetNewDefaultParams(); return $sEngineName::GetNewDefaultParams();
} }
/** /**
* Constructor * Constructor
* @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt, Sodium or OpenSSL * @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt, Sodium or OpenSSL

View File

@@ -537,7 +537,7 @@ EOF
} }
else else
{ {
throw new Exception('graphviz not found'); throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
} }
return $sHtml; return $sHtml;
} }
@@ -592,7 +592,7 @@ EOF
} }
else else
{ {
throw new Exception('graphviz not found'); throw new Exception('graphviz not found (executable path: '.$sDotExecutable.')');
} }
return $sHtml; return $sHtml;
} }

View File

@@ -218,8 +218,8 @@ class SQLObjectQueryBuilder
continue; continue;
} }
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
// Skip this attribute if not made of SQL columns nor in current table // Skip this attribute if not made of SQL columns
if (count($oAttDef->GetSQLExpressions()) == 0 || $oAttDef->IsExternalField()) if (count($oAttDef->GetSQLExpressions()) == 0)
{ {
continue; continue;
} }

View File

@@ -50,7 +50,7 @@ abstract class Trigger extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list", 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()))); 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(); $aTags = ContextTag::GetTags();
MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags, true), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12))); 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)));
// "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info // "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info
MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
@@ -121,9 +121,7 @@ abstract class Trigger extends cmdbAbstractObject
$oAction = MetaModel::GetObject('Action', $iActionId); $oAction = MetaModel::GetObject('Action', $iActionId);
if ($oAction->IsActive()) if ($oAction->IsActive())
{ {
$oKPI = new ExecutionKPI();
$oAction->DoExecute($this, $aContextArgs); $oAction->DoExecute($this, $aContextArgs);
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
} }
} }
} }
@@ -256,38 +254,6 @@ abstract class TriggerOnObject extends Trigger
} }
} }
/**
* Activate trigger based on attribute list given instead of changed attributes
*
* @param array $aContextArgs
* @param array|null $aAttributes if null default to changed attributes
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
* @since 3.1.1 3.2.0 N°6228
*/
public function DoActivateForSpecificAttributes(array $aContextArgs, ?array $aAttributes)
{
if (isset($aContextArgs['this->object()']))
{
/** @var \DBObject $oObject */
$oObject = $aContextArgs['this->object()'];
if (is_null($aAttributes)) {
$aChanges = $oObject->ListPreviousValuesForUpdatedAttributes();
} else {
$aChanges = array_fill_keys($aAttributes, true);
}
if (false === $this->IsTargetObject($oObject->GetKey(), $aChanges)) {
return;
}
}
parent::DoActivate($aContextArgs);
}
/** /**
* @param $iObjectId * @param $iObjectId
* @param array $aChanges * @param array $aChanges

View File

@@ -161,7 +161,7 @@ abstract class UserRightsAddOnAPI
$oSearchSharers->AllowAllData(); $oSearchSharers->AllowAllData();
$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id'); $oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
$aSharers = array(); $aSharers = array();
foreach($oSearchSharers->SelectAttributeToArray('id') as $aRow) foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
{ {
$aSharers[] = $aRow['id']; $aSharers[] = $aRow['id'];
} }
@@ -186,7 +186,7 @@ abstract class UserRightsAddOnAPI
$oOrgField = new FieldExpression('org_id', $sShareClass); $oOrgField = new FieldExpression('org_id', $sShareClass);
$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr)); $oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
$aShared = array(); $aShared = array();
foreach($oSearchShares->SelectAttributeToArray($sShareAttCode) as $aRow) foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
{ {
$aShared[] = $aRow[$sShareAttCode]; $aShared[] = $aRow[$sShareAttCode];
} }
@@ -235,25 +235,15 @@ abstract class User extends cmdbAbstractObject
MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values" => null, "sql" => "login", "default_value" => null, "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeString("login", array("allowed_values" => null, "sql" => "login", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql" => "language", "default_value" => "EN US", "is_null_allowed" => false, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeApplicationLanguage("language", array("sql"=>"language", "default_value"=>"EN US", "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeEnum("status", array( MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values" => new ValueSetEnum('enabled,disabled'), "styled_values"=>['enabled' => new ormStyle('ibo-dm-enum--User-status-enabled', 'ibo-dm-enum-alt--User-status-enabled', 'var(--ibo-dm-enum--User-status-enabled--main-color)', 'var(--ibo-dm-enum--User-status-enabled--complementary-color)', null, null),'disabled' => new ormStyle('ibo-dm-enum--User-status-disabled', 'ibo-dm-enum-alt--User-status-disabled', 'var(--ibo-dm-enum--User-status-disabled--main-color)', 'var(--ibo-dm-enum--User-status-disabled--complementary-color)', null, null)], "sql"=>"status", "default_value"=>"enabled", "is_null_allowed"=>false, "depends_on"=>array())));
"allowed_values" => new ValueSetEnum('enabled,disabled'),
"styled_values" => [
'enabled' => new ormStyle('ibo-dm-enum--User-status-enabled', 'ibo-dm-enum-alt--User-status-enabled', 'var(--ibo-dm-enum--User-status-enabled--main-color)', 'var(--ibo-dm-enum--User-status-enabled--complementary-color)', null, null),
'disabled' => new ormStyle('ibo-dm-enum--User-status-disabled', 'ibo-dm-enum-alt--User-status-disabled', 'var(--ibo-dm-enum--User-status-disabled--main-color)', 'var(--ibo-dm-enum--User-status-disabled--complementary-color)', null, null),
],
"sql" => "status",
"default_value" => "enabled",
"is_null_allowed" => false,
"depends_on" => array(),
)));
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list",array("linked_class" => "URP_UserProfile", "ext_key_to_me" => "userid", "ext_key_to_remote" => "profileid", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), "display_style" => 'property', "with_php_constraint" => true, "with_php_computation" => true))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("profile_list",
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class" => "URP_UserOrg", "ext_key_to_me" => "userid", "ext_key_to_remote" => "allowed_org_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), 'with_php_constraint' => true))); array("linked_class" => "URP_UserProfile", "ext_key_to_me" => "userid", "ext_key_to_remote" => "profileid", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array(), "display_style" => 'property')));
MetaModel::Init_AddAttribute(new AttributeCaseLog("log", array("sql" => 'log', "is_null_allowed" => true, "default_value" => '', "allowed_values" => null, "depends_on" => array(), "always_load_in_tables" => false))); MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("allowed_org_list", array("linked_class" => "URP_UserOrg", "ext_key_to_me" => "userid", "ext_key_to_remote" => "allowed_org_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list', 'log')); // Unused as it's an abstract class ! MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'language', 'status', 'profile_list', 'allowed_org_list')); // Unused as it's an abstract class !
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'email', 'language', 'status', 'org_id')); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'email', 'language', 'status', 'org_id')); // Criteria of the std search form
@@ -650,23 +640,23 @@ abstract class UserInternal extends User
{ {
$aParams = array $aParams = array
( (
"category" => "core,grant_by_profile,silo", "category" => "core,grant_by_profile,silo",
"key_type" => "autoincrement", "key_type" => "autoincrement",
"name_attcode" => "login", "name_attcode" => "login",
"state_attcode" => "", "state_attcode" => "",
"reconc_keys" => array('login'), "reconc_keys" => array('login'),
"db_table" => "priv_internaluser", "db_table" => "priv_internaluser",
"db_key_field" => "id", "db_key_field" => "id",
"db_finalclass_field" => "", "db_finalclass_field" => "",
); );
MetaModel::Init_Params($aParams); MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes(); MetaModel::Init_InheritAttributes();
// When set, this token allows for password reset // When set, this token allows for password reset
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values" => null, "default_value" => null, "is_null_allowed" => true, "depends_on" => array()))); MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values"=>null, "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists // Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'status', 'language', 'profile_list', 'allowed_org_list', 'log')); // Attributes to be displayed for the complete details MetaModel::Init_SetZListItems('details', array('contactid', 'org_id', 'email', 'login', 'status', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list MetaModel::Init_SetZListItems('list', array('finalclass', 'first_name', 'last_name', 'status', 'org_id')); // Attributes to be displayed for a list
// Search criteria // Search criteria
MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status', 'org_id')); // Criteria of the std search form MetaModel::Init_SetZListItems('standard_search', array('login', 'contactid', 'status', 'org_id')); // Criteria of the std search form
@@ -761,25 +751,14 @@ class UserRights
protected static $m_aCacheContactPictureAbsUrl = []; protected static $m_aCacheContactPictureAbsUrl = [];
/** @var UserRightsAddOnAPI $m_oAddOn */ /** @var UserRightsAddOnAPI $m_oAddOn */
protected static $m_oAddOn; protected static $m_oAddOn;
protected static $m_oUser = null; protected static $m_oUser;
protected static $m_oRealUser = null; protected static $m_oRealUser;
protected static $m_sSelfRegisterAddOn = null; protected static $m_sSelfRegisterAddOn = null;
protected static $m_aAdmins = array(); protected static $m_aAdmins = array();
protected static $m_aPortalUsers = array(); protected static $m_aPortalUsers = array();
/** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */ /** @var array array('sName' => $sName, 'bSuccess' => $bSuccess); */
private static $m_sLastLoginStatus = null; private static $m_sLastLoginStatus = null;
/**
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
protected static function ResetCurrentUserData()
{
self::$m_oUser = null;
self::$m_oRealUser = null;
self::$m_sLastLoginStatus = null;
}
/** /**
* @param string $sModuleName * @param string $sModuleName
* *
@@ -798,7 +777,8 @@ class UserRights
} }
self::$m_oAddOn = new $sModuleName; self::$m_oAddOn = new $sModuleName;
self::$m_oAddOn->Init(); self::$m_oAddOn->Init();
self::ResetCurrentUserData(); self::$m_oUser = null;
self::$m_oRealUser = null;
} }
/** /**
@@ -856,8 +836,6 @@ class UserRights
} }
/** /**
* Set the current user (as part of the login process)
*
* @param string $sLogin Login of the concerned user * @param string $sLogin Login of the concerned user
* @param string $sAuthentication * @param string $sAuthentication
* *
@@ -884,19 +862,6 @@ class UserRights
return true; return true;
} }
/**
* Reset current user and cleanup associated SESSION data
*
* @return void
* @since 3.0.4 3.1.1 3.2.0
*/
public static function Logoff()
{
self::ResetCurrentUserData();
Dict::SetUserLanguage(null);
self::_ResetSessionCache();
}
/** /**
* @param string $sLogin Login of the user to check the credentials for * @param string $sLogin Login of the user to check the credentials for
* @param string $sPassword * @param string $sPassword
@@ -1121,7 +1086,9 @@ class UserRights
} }
/** /**
* @return string connected {@see User} login field value, otherwise empty string * Return the current user login or an empty string if nobody connected.
*
* @return string
*/ */
public static function GetUser() public static function GetUser()
{ {
@@ -1569,9 +1536,9 @@ class UserRights
/** /**
* @param string $sClass * @param string $sClass
* @param int $iActionCode see UR_ACTION_* constants * @param int $iActionCode
* @param DBObjectSet $oInstanceSet * @param \DBObjectSet $oInstanceSet
* @param User $oUser * @param \User $oUser
* *
* @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS) * @return int (UR_ALLOWED_YES|UR_ALLOWED_NO|UR_ALLOWED_DEPENDS)
* @throws \CoreException * @throws \CoreException

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