mirror of
https://github.com/Combodo/iTop.git
synced 2026-03-11 03:54:21 +01:00
Compare commits
103 Commits
feature/79
...
saas/3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8c963c4d | ||
|
|
dcb40a832f | ||
|
|
998586a14c | ||
|
|
c61565101e | ||
|
|
d1ece12c44 | ||
|
|
fc2ba866f2 | ||
|
|
6a0695f347 | ||
|
|
8854f159a2 | ||
|
|
02a293c464 | ||
|
|
84f36eeebb | ||
|
|
ff9827ad17 | ||
|
|
bc5db3cd7d | ||
|
|
220be64f6e | ||
|
|
f22921d8f8 | ||
|
|
235c63147f | ||
|
|
5485b0d9b0 | ||
|
|
cc56413ee7 | ||
|
|
b3820560f1 | ||
|
|
144ca490ed | ||
|
|
70d271fcb1 | ||
|
|
7443fdd525 | ||
|
|
ff351d6b4b | ||
|
|
6f6b385550 | ||
|
|
1d525e92e8 | ||
|
|
f25e9045c9 | ||
|
|
0b5a9aa5da | ||
|
|
03568f2fa5 | ||
|
|
965659cdb4 | ||
|
|
d07747d82d | ||
|
|
15900720c8 | ||
|
|
6d3f7f4976 | ||
|
|
6dc88c372b | ||
|
|
ebab4f8f96 | ||
|
|
ac792e7a4c | ||
|
|
204a6d8e51 | ||
|
|
7670c8723a | ||
|
|
4c26a7a682 | ||
|
|
bcb110d99e | ||
|
|
446a13ad72 | ||
|
|
87ecbe9d37 | ||
|
|
5bceee3bdb | ||
|
|
1378afbfa2 | ||
|
|
58f4d3b53c | ||
|
|
427fc6f9f9 | ||
|
|
07eadb3ea7 | ||
|
|
97f4818076 | ||
|
|
ad46d47e21 | ||
|
|
cd3f7d7ead | ||
|
|
c6b203fc4e | ||
|
|
b8d04e40e4 | ||
|
|
d8c7888eac | ||
|
|
9d6f4569ef | ||
|
|
1e41e805a2 | ||
|
|
bd1e4389f7 | ||
|
|
b059fb72a2 | ||
|
|
1b3b2e8a69 | ||
|
|
6b448e29f5 | ||
|
|
7cb6af0a2b | ||
|
|
ddc9952ec1 | ||
|
|
a1a9ffe192 | ||
|
|
7728082c00 | ||
|
|
970183ef45 | ||
|
|
6c2db1e687 | ||
|
|
32d74fbc8e | ||
|
|
477f2f51e9 | ||
|
|
94ea8e60e8 | ||
|
|
b9a00b15f5 | ||
|
|
e87f5af465 | ||
|
|
97d717b016 | ||
|
|
251fd3c67b | ||
|
|
7b0a569c64 | ||
|
|
7176bc8686 | ||
|
|
9c0b906ded | ||
|
|
0533916dad | ||
|
|
60b08586c2 | ||
|
|
28df2942e4 | ||
|
|
cd48d2ad37 | ||
|
|
9db2205241 | ||
|
|
045985cd5b | ||
|
|
809b371520 | ||
|
|
9bbc7342b8 | ||
|
|
973c435138 | ||
|
|
1c3dfd6491 | ||
|
|
9400b697eb | ||
|
|
49748a0374 | ||
|
|
163276a6c2 | ||
|
|
9b0c2f7324 | ||
|
|
e1807f598f | ||
|
|
02e63fff64 | ||
|
|
0864f05d9f | ||
|
|
1bbcd9656a | ||
|
|
34d8e52c22 | ||
|
|
ac7309e48c | ||
|
|
c8fade6013 | ||
|
|
ad052dd861 | ||
|
|
a5ea868609 | ||
|
|
0b03b3ef4d | ||
|
|
174cace20a | ||
|
|
e3f5dbfc80 | ||
|
|
40e24c25a2 | ||
|
|
c6e4466c53 | ||
|
|
6638eb4adc | ||
|
|
eb40968e34 |
@@ -228,7 +228,7 @@ 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 */
|
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
|
||||||
'uniqueness_rules' => array(
|
'uniqueness_rules' => array(
|
||||||
'no_duplicate' => array(
|
'no_duplicate' => array(
|
||||||
'attributes' => array(
|
'attributes' => array(
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ 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 */
|
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
|
||||||
);
|
);
|
||||||
MetaModel::Init_Params($aParams);
|
MetaModel::Init_Params($aParams);
|
||||||
//MetaModel::Init_InheritAttributes();
|
//MetaModel::Init_InheritAttributes();
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ 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 */
|
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
|
||||||
);
|
);
|
||||||
MetaModel::Init_Params($aParams);
|
MetaModel::Init_Params($aParams);
|
||||||
//MetaModel::Init_InheritAttributes();
|
//MetaModel::Init_InheritAttributes();
|
||||||
|
|||||||
@@ -2247,6 +2247,49 @@ interface iModuleExtension
|
|||||||
public function __construct();
|
public function __construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to provide messages to be displayed in the "Welcome Popup"
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
* @private
|
||||||
|
* @since 3.1.0
|
||||||
|
*/
|
||||||
|
interface iWelcomePopup
|
||||||
|
{
|
||||||
|
// Importance for ordering messages
|
||||||
|
// Just two levels since less important messages have nothing to do in the welcome popup
|
||||||
|
const IMPORTANCE_CRITICAL = 0;
|
||||||
|
const IMPORTANCE_HIGH = 1;
|
||||||
|
/**
|
||||||
|
* @return [['importance' => IMPORTANCE_CRITICAL|IMPORTANCE_HIGH, 'id' => '...', 'title' => '', 'html' => '', 'twig' => '']]
|
||||||
|
*/
|
||||||
|
public function GetMessages();
|
||||||
|
/**
|
||||||
|
* The message specified by the given Id has been acknowledged by the current user
|
||||||
|
* @param string $sMessageId
|
||||||
|
*/
|
||||||
|
public function AcknowledgeMessage(string $sMessageId): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from this class to provide messages to be displayed in the "Welcome Popup"
|
||||||
|
*
|
||||||
|
* @api
|
||||||
|
* @since 3.1.0
|
||||||
|
*/
|
||||||
|
abstract class AbstractWelcomePopup implements iWelcomePopup
|
||||||
|
{
|
||||||
|
public function GetMessages()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
public function AcknowledgeMessage(string $sMessageId): void
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KPI logging extensibility point
|
* KPI logging extensibility point
|
||||||
*
|
*
|
||||||
@@ -2254,19 +2297,19 @@ interface iModuleExtension
|
|||||||
*/
|
*/
|
||||||
interface iKPILoggerExtension
|
interface iKPILoggerExtension
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Init the statistics collected
|
* Init the statistics collected
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function InitStats();
|
public function InitStats();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new KPI to the stats
|
* Add a new KPI to the stats
|
||||||
*
|
*
|
||||||
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
|
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function LogOperation($oKpiLogData);
|
public function LogOperation($oKpiLogData);
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,36 @@
|
|||||||
<presentation/>
|
<presentation/>
|
||||||
<methods/>
|
<methods/>
|
||||||
</class>
|
</class>
|
||||||
|
<class id="WelcomePopupAcknowledge" _delta="define">
|
||||||
|
<parent>DBObject</parent>
|
||||||
|
<properties>
|
||||||
|
<comment>/* Acknowledge welcome popup messages */</comment>
|
||||||
|
<abstract>false</abstract>
|
||||||
|
<category></category>
|
||||||
|
<key_type>autoincrement</key_type>
|
||||||
|
<db_table>priv_welcome_popup_acknowledge</db_table>
|
||||||
|
</properties>
|
||||||
|
<fields>
|
||||||
|
<field id="message_uuid" xsi:type="AttributeString">
|
||||||
|
<sql>message_uuid</sql>
|
||||||
|
<default_value/>
|
||||||
|
<is_null_allowed>false</is_null_allowed>
|
||||||
|
</field>
|
||||||
|
<field id="user_id" xsi:type="AttributeExternalKey">
|
||||||
|
<sql>user_id</sql>
|
||||||
|
<target_class>User</target_class>
|
||||||
|
<is_null_allowed>false</is_null_allowed>
|
||||||
|
<on_target_delete>DEL_SILENT</on_target_delete>
|
||||||
|
</field>
|
||||||
|
<field id="acknowledge_date" xsi:type="AttributeDateTime">
|
||||||
|
<sql>acknowledge_date</sql>
|
||||||
|
<default_value/>
|
||||||
|
<is_null_allowed>false</is_null_allowed>
|
||||||
|
</field>
|
||||||
|
</fields>
|
||||||
|
<presentation/>
|
||||||
|
<methods/>
|
||||||
|
</class>
|
||||||
</classes>
|
</classes>
|
||||||
<portals>
|
<portals>
|
||||||
<portal id="backoffice" _delta="define">
|
<portal id="backoffice" _delta="define">
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ 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);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Combodo\iTop\Application\Helper\WebResourcesHelper;
|
|||||||
require_once(APPROOT.'/application/utils.inc.php');
|
require_once(APPROOT.'/application/utils.inc.php');
|
||||||
require_once(APPROOT.'/application/template.class.inc.php');
|
require_once(APPROOT.'/application/template.class.inc.php');
|
||||||
require_once(APPROOT."/application/user.dashboard.class.inc.php");
|
require_once(APPROOT."/application/user.dashboard.class.inc.php");
|
||||||
|
require_once(APPROOT."/setup/parentmenunodecompiler.class.inc.php");
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,12 +274,23 @@ class ApplicationMenu
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$aSubMenuNodes = static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams);
|
||||||
|
if (! ParentMenuNodeCompiler::$bUseLegacyMenuCompilation && !($oMenuNode instanceof ShortcutMenuNode)){
|
||||||
|
if (is_array($aSubMenuNodes) && 0 === sizeof($aSubMenuNodes)){
|
||||||
|
IssueLog::Debug('Empty menu node not displayed', LogChannels::CONSOLE, [
|
||||||
|
'menu_node_class' => get_class($oMenuNode),
|
||||||
|
'menu_node_label' => $oMenuNode->GetLabel(),
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$aMenuGroups[] = [
|
$aMenuGroups[] = [
|
||||||
'sId' => $oMenuNode->GetMenuID(),
|
'sId' => $oMenuNode->GetMenuID(),
|
||||||
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
|
'sIconCssClasses' => $oMenuNode->GetDecorationClasses(),
|
||||||
'sInitials' => $oMenuNode->GetInitials(),
|
'sInitials' => $oMenuNode->GetInitials(),
|
||||||
'sTitle' => $oMenuNode->GetTitle(),
|
'sTitle' => $oMenuNode->GetTitle(),
|
||||||
'aSubMenuNodes' => static::GetSubMenuNodes($sMenuGroupIdx, $aExtraParams),
|
'aSubMenuNodes' => $aSubMenuNodes,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,7 +1123,7 @@ class OQLMenuNode extends MenuNode
|
|||||||
*/
|
*/
|
||||||
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
public function RenderContent(WebPage $oPage, $aExtraParams = array())
|
||||||
{
|
{
|
||||||
ContextTag::AddContext(ContextTag::TAG_OBJECT_SEARCH);
|
$oTag = new ContextTag(ContextTag::TAG_OBJECT_SEARCH);
|
||||||
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
ApplicationMenu::CheckMenuIdEnabled($this->GetMenuId());
|
||||||
OQLMenuNode::RenderOQLSearch
|
OQLMenuNode::RenderOQLSearch
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<div style="width:100%;background: #fff url(../images/welcome.jpg) top left no-repeat;">
|
|
||||||
<style>
|
|
||||||
.welcome_popup_cell {
|
|
||||||
vertical-align:top;
|
|
||||||
width:50%;
|
|
||||||
border:0px solid #000;
|
|
||||||
-moz-border-radius:10px;
|
|
||||||
padding:5px;
|
|
||||||
text-align:left;
|
|
||||||
}
|
|
||||||
tr td.welcome_popup_cell, tr td.welcome_popup_cell ul {
|
|
||||||
font-size:10pt;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<p></p>
|
|
||||||
<p></p>
|
|
||||||
<p style="text-align:left; font-size:32px;padding-left:400px;padding-top:40px;margin-bottom:30px;margin-top:0;color:#FFFFFF;"><itopstring>UI:WelcomeMenu:Title</itopstring></p>
|
|
||||||
<p></p>
|
|
||||||
<table border="0" style="padding:10px;border-spacing: 10px;width:100%">
|
|
||||||
<tr>
|
|
||||||
<td class="welcome_popup_cell">
|
|
||||||
<itopstring>UI:WelcomeMenu:LeftBlock</itopstring>
|
|
||||||
</td>
|
|
||||||
<td class="welcome_popup_cell">
|
|
||||||
<itopstring>UI:WelcomeMenu:RightBlock</itopstring>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
@@ -1193,6 +1193,30 @@ 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).',
|
||||||
@@ -1353,6 +1377,14 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
],
|
],
|
||||||
|
'navigation_menu.sorted_popup_user_menu_items' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'description' => 'Sort user menu items after setup on page load',
|
||||||
|
'default' => [],
|
||||||
|
'value' => false,
|
||||||
|
'source_of_value' => '',
|
||||||
|
'show_in_conf_sample' => false,
|
||||||
|
],
|
||||||
'quick_create.enabled' => [
|
'quick_create.enabled' => [
|
||||||
'type' => 'bool',
|
'type' => 'bool',
|
||||||
'description' => 'Whether or not the quick create is enabled',
|
'description' => 'Whether or not the quick create is enabled',
|
||||||
@@ -1635,6 +1667,14 @@ class Config
|
|||||||
'source_of_value' => '',
|
'source_of_value' => '',
|
||||||
'show_in_conf_sample' => false,
|
'show_in_conf_sample' => false,
|
||||||
],
|
],
|
||||||
|
'security.single_profile_completion' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'description' => 'Non standalone profiles can be completed by other profiles via this configuration. default configuration is equivalent to [\'Portal power user\' => \'Portal user\'] configuration. unless you have specific portal customization.',
|
||||||
|
'default' => null,
|
||||||
|
'value' => false,
|
||||||
|
'source_of_value' => '',
|
||||||
|
'show_in_conf_sample' => false,
|
||||||
|
],
|
||||||
'behind_reverse_proxy' => [
|
'behind_reverse_proxy' => [
|
||||||
'type' => 'bool',
|
'type' => 'bool',
|
||||||
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
|
'description' => 'If true, then proxies custom header (X-Forwarded-*) are taken into account. Use only if the webserver is not publicly accessible (reachable only by the reverse proxy)',
|
||||||
|
|||||||
@@ -3169,6 +3169,10 @@ abstract class DBObject implements iDisplay
|
|||||||
// First query built upon on the root class, because the ID must be created first
|
// First query built upon on the root class, because the ID must be created first
|
||||||
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
|
||||||
|
|
||||||
|
//since N°5324: issue with test and db links events
|
||||||
|
$this->SetReadOnly('No modification allowed during transaction');
|
||||||
|
MetaModel::StartReentranceProtection($this);
|
||||||
|
|
||||||
// Then do the leaf class, if different from the root class
|
// Then do the leaf class, if different from the root class
|
||||||
if ($sClass != $sRootClass) {
|
if ($sClass != $sRootClass) {
|
||||||
$this->DBInsertSingleTable($sClass);
|
$this->DBInsertSingleTable($sClass);
|
||||||
@@ -3220,6 +3224,7 @@ abstract class DBObject implements iDisplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->SetReadWrite();
|
||||||
$this->m_bIsInDB = true;
|
$this->m_bIsInDB = true;
|
||||||
$this->m_bDirty = false;
|
$this->m_bDirty = false;
|
||||||
foreach ($this->m_aCurrValues as $sAttCode => $value) {
|
foreach ($this->m_aCurrValues as $sAttCode => $value) {
|
||||||
@@ -3230,7 +3235,7 @@ abstract class DBObject implements iDisplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prevent DBUpdate at this point (reentrance protection)
|
// Prevent DBUpdate at this point (reentrance protection)
|
||||||
MetaModel::StartReentranceProtection($this);
|
//MetaModel::StartReentranceProtection($this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->PostInsertActions();
|
$this->PostInsertActions();
|
||||||
@@ -3400,6 +3405,7 @@ abstract class DBObject implements iDisplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->SetReadOnly('No modification allowed during transaction');
|
||||||
$iTransactionRetry = 1;
|
$iTransactionRetry = 1;
|
||||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||||
if ($bIsTransactionEnabled) {
|
if ($bIsTransactionEnabled) {
|
||||||
@@ -3514,6 +3520,8 @@ abstract class DBObject implements iDisplay
|
|||||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||||
|
|
||||||
|
$this->SetReadWrite();
|
||||||
$this->m_bDirty = false;
|
$this->m_bDirty = false;
|
||||||
$this->m_aTouchedAtt = array();
|
$this->m_aTouchedAtt = array();
|
||||||
$this->m_aModifiedAtt = array();
|
$this->m_aModifiedAtt = array();
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ EOF;
|
|||||||
EOF;
|
EOF;
|
||||||
|
|
||||||
SetupUtils::builddir(dirname($sFilePath));
|
SetupUtils::builddir(dirname($sFilePath));
|
||||||
file_put_contents($sFilePath, $content);
|
file_put_contents($sFilePath, $content, LOCK_EX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Dict::SetUserLanguage($sUserLang);
|
Dict::SetUserLanguage($sUserLang);
|
||||||
|
|||||||
@@ -576,6 +576,11 @@ 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
|
||||||
|
|||||||
@@ -4219,40 +4219,77 @@ abstract class MetaModel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$aCurrentUser = array();
|
$aCurrentUser = [];
|
||||||
$aCurrentContact = array();
|
$aCurrentContact = [];
|
||||||
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) {
|
||||||
$oUser = UserRights::GetUserObject();
|
static::FillObjectPlaceholders($aPlaceholders, 'current_user', UserRights::GetUserObject(), $aCurrentUser);
|
||||||
$aPlaceholders['current_user->object()'] = $oUser;
|
|
||||||
foreach ($aCurrentUser as $sField) {
|
|
||||||
$aPlaceholders['current_user->'.$sField] = $oUser->Get($sField);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (count($aCurrentContact) > 0) {
|
if (count($aCurrentContact) > 0) {
|
||||||
$oPerson = UserRights::GetContactObject();
|
static::FillObjectPlaceholders($aPlaceholders, 'current_contact', UserRights::GetContactObject(), $aCurrentContact);
|
||||||
$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";
|
||||||
|
if (false === MetaModel::IsValidAttCode(get_class($oObject), $sField)){
|
||||||
|
$aContext = [
|
||||||
|
"current_user_id" => UserRights::GetUserId(),
|
||||||
|
"obj_class" => get_class($oObject),
|
||||||
|
"placeholder" => $sPlaceHolderKey,
|
||||||
|
"invalid_field" => $sField,
|
||||||
|
];
|
||||||
|
IssueLog::Warning("Unresolved placeholder due to invalid attribute", null,
|
||||||
|
$aContext);
|
||||||
|
$aPlaceholders[$sPlaceHolderKey] = \Dict::Format("Core:Placeholder:CannotBeResolved", $sPlaceHolderKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aPlaceholders[$sPlaceHolderKey] = $oObject->Get($sField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \DBSearch $oFilter
|
* @param \DBSearch $oFilter
|
||||||
*
|
*
|
||||||
@@ -7586,6 +7623,20 @@ abstract class MetaModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5324: to ease reentrance checks when using events on links (to avoid reentering if main link object ongoing operation)
|
||||||
|
*/
|
||||||
|
public static function GetReentranceObjectByChildClass(string $sParentClass, $sKey)
|
||||||
|
{
|
||||||
|
foreach (self::EnumChildClasses($sParentClass, ENUM_CHILD_CLASSES_ALL, false) as $sChildClass){
|
||||||
|
if (self::GetReentranceObject($sChildClass, $sKey)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \DBObject $oObject
|
* @param \DBObject $oObject
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
|
|||||||
|
|
||||||
#welcome_popup{
|
#welcome_popup{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
}
|
||||||
|
.ibo-welcome-popup--columns{
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
.ibo-welcome-popup--image{
|
.ibo-welcome-popup--image{
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -44,7 +46,39 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ibo-welcome-popup--text--options{
|
.ibo-welcome-popup--dialog {
|
||||||
position: absolute;
|
width: 60rem;
|
||||||
bottom: $ibo-welcome-popup--text--options--bottom;
|
}
|
||||||
|
.ibo-welcome-popup--content {
|
||||||
|
width: 100%;
|
||||||
|
.ibo-welcome-popup--message {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 12rem;
|
||||||
|
}
|
||||||
|
.ibo-welcome-popup--button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 1rem;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ibo-welcome-popup--indicators {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 0;
|
||||||
|
height: 3rem;
|
||||||
|
.ibo-welcome-popup--indicator {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: $ibo-color-secondary-600;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ibo-welcome-popup--active {
|
||||||
|
background-color: $ibo-color-information-600 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -34,7 +34,7 @@ class DBRestore extends DBBackup
|
|||||||
|
|
||||||
protected function LogInfo($sMsg)
|
protected function LogInfo($sMsg)
|
||||||
{
|
{
|
||||||
//IssueLog::Info('non juste info: '.$sMsg);
|
IssueLog::Info('non juste info: '.$sMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function LogError($sMsg)
|
protected function LogError($sMsg)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
|
<itop_design version="3.1">
|
||||||
<classes/>
|
<classes/>
|
||||||
<user_rights>
|
<user_rights>
|
||||||
<groups>
|
<groups>
|
||||||
@@ -546,5 +546,6 @@
|
|||||||
<groups/>
|
<groups/>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
<dictionaries/>
|
||||||
</user_rights>
|
</user_rights>
|
||||||
</itop_design>
|
</itop_design>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Localized data
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) Combodo SARL 2022
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
Dict::Add('EN US', 'English', 'English', array(
|
||||||
|
'Class:User/NonStandaloneProfileWarning' => 'Profile %1$s cannot be standalone. You should add other profiles to user %2$s otherwise you may encounter access issue with this user.',
|
||||||
|
'Class:User/NonStandaloneProfileWarning-ReparationMessage' => 'Profile %1$s cannot be standalone. User %2$s has been completed with another profile: %3$s.',
|
||||||
|
));
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Localized data
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) Combodo SARL 2022
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
Dict::Add('FR FR', 'French', 'Français', array(
|
||||||
|
'Class:User/NonStandaloneProfileWarning' => 'Le profil %1$s ne peut être seul. Sans le rajout d\'autres profiles, l\'utilisateur %2$s peut rencontrer des problèmes dans iTop.',
|
||||||
|
'Class:User/NonStandaloneProfileWarning-ReparationMessage' => 'Le profil %1$s ne peut être seul. Le user %2$s a été complété par le profil %3$s.',
|
||||||
|
));
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ SetupWebPage::AddModule(
|
|||||||
// Components
|
// Components
|
||||||
//
|
//
|
||||||
'datamodel' => array(
|
'datamodel' => array(
|
||||||
|
'src/UserProfilesEventListener.php'
|
||||||
),
|
),
|
||||||
'webservice' => array(
|
'webservice' => array(
|
||||||
//'webservices.itop-profiles-itil.php',
|
//'webservices.itop-profiles-itil.php',
|
||||||
|
|||||||
@@ -0,0 +1,394 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\ItilProfiles;
|
||||||
|
|
||||||
|
use Combodo\iTop\Service\Events\EventData;
|
||||||
|
use Combodo\iTop\Service\Events\EventService;
|
||||||
|
use Combodo\iTop\Service\Events\iEventServiceSetup;
|
||||||
|
use Exception;
|
||||||
|
use IssueLog;
|
||||||
|
use LogChannels;
|
||||||
|
|
||||||
|
define('POWER_USER_PORTAL_PROFILE_NAME', 'Portal power user');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UserProfilesEventListener
|
||||||
|
*
|
||||||
|
* @package Combodo\iTop\Core\EventListener
|
||||||
|
* @since 3.1 N°5324 - Avoid to have users with non-standalone power portal profile only
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UserProfilesEventListener implements iEventServiceSetup
|
||||||
|
{
|
||||||
|
const USERPROFILE_REPAIR_ITOP_PARAM_NAME = 'security.single_profile_completion';
|
||||||
|
private $bIsRepairmentEnabled = false;
|
||||||
|
|
||||||
|
//map: non standalone profile name => repairing profile id
|
||||||
|
private $aNonStandaloneProfilesMap = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function RegisterEventsAndListeners()
|
||||||
|
{
|
||||||
|
$this->Init();
|
||||||
|
|
||||||
|
if (false === $this->bIsRepairmentEnabled){
|
||||||
|
IssueLog::Debug('UserProfilesEventListener bIsRepairmentEnabled disabled', LogChannels::DM_CRUD);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aEventSource = [\User::class, \UserExternal::class, \UserInternal::class];
|
||||||
|
EventService::RegisterListener(
|
||||||
|
EVENT_DB_BEFORE_WRITE,
|
||||||
|
[$this, 'OnUserEdition'],
|
||||||
|
$aEventSource
|
||||||
|
);
|
||||||
|
|
||||||
|
EventService::RegisterListener(
|
||||||
|
EVENT_DB_BEFORE_WRITE,
|
||||||
|
[ $this, 'OnUserProfileEdition' ],
|
||||||
|
[ \URP_UserProfile::class ],
|
||||||
|
[],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
EventService::RegisterListener(
|
||||||
|
EVENT_DB_CHECK_TO_DELETE,
|
||||||
|
[ $this, 'OnUserProfileLinkDeletion' ],
|
||||||
|
[ \URP_UserProfile::class ],
|
||||||
|
[],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function IsRepairmentEnabled() : bool
|
||||||
|
{
|
||||||
|
return $this->bIsRepairmentEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function OnUserEdition(EventData $oEventData): void {
|
||||||
|
/** @var \User $oObject */
|
||||||
|
$oUser = $oEventData->Get('object');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->ValidateThenRepairOrWarn($oUser);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
IssueLog::Error('Exception occurred on RepairProfiles', LogChannels::DM_CRUD, [
|
||||||
|
'user_class' => get_class($oUser),
|
||||||
|
'user_id' => $oUser->GetKey(),
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_stacktrace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
if ($e instanceof \CoreCannotSaveObjectException){
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function OnUserProfileEdition(EventData $oEventData): void {
|
||||||
|
$oURP_UserProfile = $oEventData->Get('object');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$iUserId = $oURP_UserProfile->Get('userid');
|
||||||
|
$oUser = \MetaModel::GetReentranceObjectByChildClass(\User::class, $iUserId);
|
||||||
|
if (false !== $oUser){
|
||||||
|
IssueLog::Debug('OnUserProfileEdition user already being edited', LogChannels::DM_CRUD);
|
||||||
|
//user edition: handled by other event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oUser = \MetaModel::GetObject(\User::class, $iUserId);
|
||||||
|
$aChanges = $oURP_UserProfile->ListChanges();
|
||||||
|
if (array_key_exists('userid', $aChanges)) {
|
||||||
|
IssueLog::Debug('OnUserProfileEdition userid changed', LogChannels::DM_CRUD);
|
||||||
|
$iUserId = $oURP_UserProfile->GetOriginal('userid');
|
||||||
|
$oPreviousUser = \MetaModel::GetObject(\User::class, $iUserId);
|
||||||
|
|
||||||
|
$oProfileLinkSet = $oPreviousUser->Get('profile_list');
|
||||||
|
$oProfileLinkSet->Rewind();
|
||||||
|
$iCount = 0;
|
||||||
|
$sSingleProfileName = null;
|
||||||
|
while ($oCurrentURP_UserProfile = $oProfileLinkSet->Fetch()) {
|
||||||
|
$sNewUserId = $oCurrentURP_UserProfile->Get('userid');
|
||||||
|
$sOriginalUserId = $oCurrentURP_UserProfile->GetOriginal('userid');
|
||||||
|
if ($sNewUserId !== $sOriginalUserId) {
|
||||||
|
$sRemovedProfileId = $oCurrentURP_UserProfile->GetOriginal('profileid');
|
||||||
|
IssueLog::Debug('OnUserProfileEdition profile moved does not count', LogChannels::DM_CRUD, [
|
||||||
|
'URP_UserProfile' => $oURP_UserProfile->GetKey(),
|
||||||
|
'sRemovedProfileId' => $sRemovedProfileId,
|
||||||
|
'sNewUserId' => $sNewUserId,
|
||||||
|
'sOriginalUserId' => $sOriginalUserId,
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iCount++;
|
||||||
|
if ($iCount > 1){
|
||||||
|
IssueLog::Debug('OnUserProfileEdition more than one user', LogChannels::DM_CRUD);
|
||||||
|
//more than one profile: no repairment needed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$sSingleProfileName = $oCurrentURP_UserProfile->Get('profile');
|
||||||
|
}
|
||||||
|
$this->RepairProfileChangesOrWarn($oPreviousUser, $sSingleProfileName, $oURP_UserProfile, $sRemovedProfileId);
|
||||||
|
} else if (array_key_exists('profileid', $aChanges)){
|
||||||
|
IssueLog::Debug('OnUserProfileEdition profileid changed', LogChannels::DM_CRUD);
|
||||||
|
$oCurrentUserProfileSet = $oUser->Get('profile_list');
|
||||||
|
if ($oCurrentUserProfileSet->Count() === 1){
|
||||||
|
$oProfile = $oCurrentUserProfileSet->Fetch();
|
||||||
|
|
||||||
|
$this->RepairProfileChangesOrWarn($oUser, $oProfile->Get('profile'), $oURP_UserProfile, $oProfile->GetOriginal("profileid"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
IssueLog::Error('OnUserProfileEdition Exception', LogChannels::DM_CRUD, [
|
||||||
|
'user_id' => $iUserId,
|
||||||
|
'lnk_id' => $oURP_UserProfile->GetKey(),
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_stacktrace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
if ($e instanceof \CoreCannotSaveObjectException){
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function OnUserProfileLinkDeletion(EventData $oEventData): void {
|
||||||
|
$oURP_UserProfile = $oEventData->Get('object');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$iUserId = $oURP_UserProfile->Get('userid');
|
||||||
|
$oUser = \MetaModel::GetReentranceObjectByChildClass(\User::class, $iUserId);
|
||||||
|
if (false !== $oUser){
|
||||||
|
IssueLog::Debug('OnUserProfileLinkDeletion user being deleted already', LogChannels::DM_CRUD);
|
||||||
|
//user edition: handled by other event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oUser = \MetaModel::GetObject(\User::class, $iUserId);
|
||||||
|
|
||||||
|
/** @var \DeletionPlan $oDeletionPlan */
|
||||||
|
$oDeletionPlan = $oEventData->Get('deletion_plan');
|
||||||
|
$aDeletedURP_UserProfiles = [];
|
||||||
|
if (! is_null($oDeletionPlan)){
|
||||||
|
$aListDeletes = $oDeletionPlan->ListDeletes();
|
||||||
|
if (array_key_exists(\URP_UserProfile::class, $aListDeletes)) {
|
||||||
|
foreach ($aListDeletes[\URP_UserProfile::class] as $iId => $aDeletes) {
|
||||||
|
$aDeletedURP_UserProfiles []= $iId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$oProfileLinkSet = $oUser->Get('profile_list');
|
||||||
|
$oProfileLinkSet->Rewind();
|
||||||
|
$sSingleProfileName = null;
|
||||||
|
$iCount = 0;
|
||||||
|
while ($oCurrentURP_UserProfile = $oProfileLinkSet->Fetch()) {
|
||||||
|
if (in_array($oCurrentURP_UserProfile->GetKey(), $aDeletedURP_UserProfiles)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$iCount++;
|
||||||
|
if ($iCount > 1){
|
||||||
|
IssueLog::Debug('OnUserProfileLinkDeletion more than one profile', LogChannels::DM_CRUD);
|
||||||
|
//more than one profile: no repairment needed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$sSingleProfileName = $oCurrentURP_UserProfile->Get('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->RepairProfileChangesOrWarn($oUser, $sSingleProfileName, $oURP_UserProfile, $oURP_UserProfile->Get('profileid'), true);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
IssueLog::Error('OnUserProfileLinkDeletion Exception', LogChannels::DM_CRUD, [
|
||||||
|
'user_id' => $iUserId,
|
||||||
|
'profile_id' => $oURP_UserProfile->Get('profileid'),
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_stacktrace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $aPortalDispatcherData: passed only for testing purpose
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \ConfigException
|
||||||
|
* @throws \CoreException
|
||||||
|
*/
|
||||||
|
public function Init($aPortalDispatcherData=null) : void {
|
||||||
|
if (is_null($aPortalDispatcherData)){
|
||||||
|
$aPortalDispatcherData = \PortalDispatcherData::GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
$aNonStandaloneProfiles = \utils::GetConfig()->Get(self::USERPROFILE_REPAIR_ITOP_PARAM_NAME, null);
|
||||||
|
|
||||||
|
//When there are several customized portals on an itop, choosing a specific profile means choosing which portal user will access
|
||||||
|
//In that case, itop administrator has to specify it via itop configuration. we dont use default profiles repairment otherwise
|
||||||
|
if (is_null($aNonStandaloneProfiles)){
|
||||||
|
if (count($aPortalDispatcherData) > 2){
|
||||||
|
IssueLog::Debug('Init repairment disabled as there are more than 2 portals (extended customer should decide on their own)', LogChannels::DM_CRUD);
|
||||||
|
$this->bIsRepairmentEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aPortalNames = array_keys($aPortalDispatcherData);
|
||||||
|
sort($aPortalNames);
|
||||||
|
if ($aPortalNames !== ['backoffice', 'itop-portal']){
|
||||||
|
IssueLog::Debug('Init repairment disabled there is a custom portal', LogChannels::DM_CRUD, [$aPortalNames]);
|
||||||
|
$this->bIsRepairmentEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($aNonStandaloneProfiles)){
|
||||||
|
//default configuration in the case there are no customized portals
|
||||||
|
$aNonStandaloneProfiles = [ POWER_USER_PORTAL_PROFILE_NAME => PORTAL_PROFILE_NAME ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_array($aNonStandaloneProfiles)){
|
||||||
|
\IssueLog::Error(sprintf("%s is badly configured. it should be an array.", self::USERPROFILE_REPAIR_ITOP_PARAM_NAME),LogChannels::DM_CRUD, [self::USERPROFILE_REPAIR_ITOP_PARAM_NAME => $aNonStandaloneProfiles]);
|
||||||
|
$this->bIsRepairmentEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($aNonStandaloneProfiles)){
|
||||||
|
//Feature specifically disabled in itop configuration
|
||||||
|
IssueLog::Debug('Init repairment disabled by conf on purpose', LogChannels::DM_CRUD);
|
||||||
|
$this->bIsRepairmentEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$this->FetchRepairingProfileIds($aNonStandaloneProfiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function FetchRepairingProfileIds(array $aNonStandaloneProfiles) : void {
|
||||||
|
$aProfiles = [];
|
||||||
|
try {
|
||||||
|
$aProfilesToSearch = array_unique(array_values($aNonStandaloneProfiles));
|
||||||
|
if(($iIndex = array_search(null, $aProfilesToSearch)) !== false) {
|
||||||
|
unset($aProfilesToSearch[$iIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === count($aProfilesToSearch)){
|
||||||
|
$sInCondition = sprintf('"%s"', array_pop($aProfilesToSearch));
|
||||||
|
} else {
|
||||||
|
$sInCondition = sprintf('"%s"', implode('","', $aProfilesToSearch));
|
||||||
|
}
|
||||||
|
|
||||||
|
$sOql = "SELECT URP_Profiles WHERE name IN ($sInCondition)";
|
||||||
|
$oSearch = \DBSearch::FromOQL($sOql);
|
||||||
|
$oSearch->AllowAllData();
|
||||||
|
$oSet = new \DBObjectSet($oSearch);
|
||||||
|
while(($oProfile = $oSet->Fetch()) != null) {
|
||||||
|
$sProfileName = $oProfile->Get('name');
|
||||||
|
$aProfiles[$sProfileName] = $oProfile->GetKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aNonStandaloneProfilesMap = [];
|
||||||
|
foreach ($aNonStandaloneProfiles as $sNonStandaloneProfileName => $sRepairProfileName) {
|
||||||
|
if (is_null($sRepairProfileName)) {
|
||||||
|
$this->aNonStandaloneProfilesMap[$sNonStandaloneProfileName] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! array_key_exists($sRepairProfileName, $aProfiles)) {
|
||||||
|
throw new \Exception(sprintf("%s is badly configured. profile $sRepairProfileName does not exist.", self::USERPROFILE_REPAIR_ITOP_PARAM_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aNonStandaloneProfilesMap[$sNonStandaloneProfileName] = [ 'name' => $sRepairProfileName, 'id' => $aProfiles[$sRepairProfileName]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bIsRepairmentEnabled = true;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
IssueLog::Error('Exception when searching user portal profile', LogChannels::DM_CRUD, [
|
||||||
|
'exception_message' => $e->getMessage(),
|
||||||
|
'exception_stacktrace' => $e->getTraceAsString(),
|
||||||
|
'aProfiles' => $aProfiles,
|
||||||
|
'aNonStandaloneProfiles' => $aNonStandaloneProfiles,
|
||||||
|
]);
|
||||||
|
$this->bIsRepairmentEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ValidateThenRepairOrWarn(\User $oUser) : void
|
||||||
|
{
|
||||||
|
$oCurrentUserProfileSet = $oUser->Get('profile_list');
|
||||||
|
if ($oCurrentUserProfileSet->Count() === 1){
|
||||||
|
IssueLog::Debug('ValidateThenRepairOrWarn one profile found', LogChannels::DM_CRUD);
|
||||||
|
$oProfile = $oCurrentUserProfileSet->Fetch();
|
||||||
|
|
||||||
|
$this->RepairUserChangesOrWarn($oUser, $oProfile->Get('profile'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RepairUserChangesOrWarn(\User $oUser, string $sSingleProfileName) : void {
|
||||||
|
IssueLog::Debug('RepairUserChangesOrWarn', LogChannels::DM_CRUD,
|
||||||
|
[
|
||||||
|
'aNonStandaloneProfilesMap' => $this->aNonStandaloneProfilesMap,
|
||||||
|
'sSingleProfileName' => $sSingleProfileName
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (array_key_exists($sSingleProfileName, $this->aNonStandaloneProfilesMap)) {
|
||||||
|
$aRepairingProfileInfo = $this->aNonStandaloneProfilesMap[$sSingleProfileName];
|
||||||
|
if (is_null($aRepairingProfileInfo)){
|
||||||
|
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning", $sSingleProfileName, $oUser->Get('friendlyname'));
|
||||||
|
throw new \CoreCannotSaveObjectException(array('issues' => [$sMessage], 'class' => get_class($oUser), 'id' => $oUser->GetKey()));
|
||||||
|
} else {
|
||||||
|
//Completing profiles profiles by adding repairing one : by default portal user to a power portal user
|
||||||
|
$oUserProfile = new \URP_UserProfile();
|
||||||
|
$oUserProfile->Set('profileid', $aRepairingProfileInfo['id']);
|
||||||
|
$oCurrentUserProfileSet = $oUser->Get('profile_list');
|
||||||
|
$oCurrentUserProfileSet->AddItem($oUserProfile);
|
||||||
|
$oUser->Set('profile_list', $oCurrentUserProfileSet);
|
||||||
|
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning-ReparationMessage", $sSingleProfileName, $oUser->Get('friendlyname'), $aRepairingProfileInfo['name']);
|
||||||
|
$oUser::SetSessionMessage(get_class($oUser), $oUser->GetKey(), 1, $sMessage, 'WARNING', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RepairProfileChangesOrWarn(\User $oUser, ?string $sSingleProfileName, \URP_UserProfile $oURP_UserProfile, string $sRemovedProfileId, $bIsRemoval=false) : void {
|
||||||
|
IssueLog::Debug('RepairUserChangesOrWarn', LogChannels::DM_CRUD,
|
||||||
|
[
|
||||||
|
'aNonStandaloneProfilesMap' => $this->aNonStandaloneProfilesMap,
|
||||||
|
'sSingleProfileName' => $sSingleProfileName
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_null($sSingleProfileName)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($sSingleProfileName, $this->aNonStandaloneProfilesMap)) {
|
||||||
|
$aRepairingProfileInfo = $this->aNonStandaloneProfilesMap[$sSingleProfileName];
|
||||||
|
if (is_null($aRepairingProfileInfo)
|
||||||
|
|| ($aRepairingProfileInfo['id'] === $sRemovedProfileId) //cannot repair by readding same remove profile as it will raise uniqueness rule
|
||||||
|
){
|
||||||
|
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning", $sSingleProfileName, $oUser->Get('friendlyname'));
|
||||||
|
if ($bIsRemoval){
|
||||||
|
$oURP_UserProfile->AddDeleteIssue($sMessage);
|
||||||
|
} else {
|
||||||
|
throw new \CoreCannotSaveObjectException(array('issues' => [$sMessage], 'class' => get_class($oURP_UserProfile), 'id' => $oURP_UserProfile->GetKey()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Completing profiles profiles by adding repairing one : by default portal user to a power portal user
|
||||||
|
$oUserProfile = new \URP_UserProfile();
|
||||||
|
$oUserProfile->Set('profileid', $aRepairingProfileInfo['id']);
|
||||||
|
$oCurrentUserProfileSet = $oUser->Get('profile_list');
|
||||||
|
$oCurrentUserProfileSet->AddItem($oUserProfile);
|
||||||
|
$oUser->Set('profile_list', $oCurrentUserProfileSet);
|
||||||
|
$oUser->DBWrite();
|
||||||
|
|
||||||
|
$sMessage = \Dict::Format("Class:User/NonStandaloneProfileWarning-ReparationMessage", $sSingleProfileName, $oUser->Get('friendlyname'), $aRepairingProfileInfo['name']);
|
||||||
|
$oURP_UserProfile::SetSessionMessage(get_class($oURP_UserProfile), $oURP_UserProfile->GetKey(), 1, $sMessage, 'WARNING', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
|||||||
'Core:AttributeTagSet' => 'List of tags',
|
'Core:AttributeTagSet' => 'List of tags',
|
||||||
'Core:AttributeTagSet+' => '',
|
'Core:AttributeTagSet+' => '',
|
||||||
'Core:AttributeSet:placeholder' => 'click to add',
|
'Core:AttributeSet:placeholder' => 'click to add',
|
||||||
|
'Core:Placeholder:CannotBeResolved' => '(%1$s : cannot be resolved)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)',
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
|||||||
We hope you’ll enjoy this version as much as we enjoyed imagining and creating it.</div>
|
We hope you’ll enjoy this version as much as we enjoyed imagining and creating it.</div>
|
||||||
|
|
||||||
<div>Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.</div>',
|
<div>Customize your '.ITOP_APPLICATION.' preferences for a personalized experience.</div>',
|
||||||
|
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, discard this message',
|
||||||
'UI:WelcomeMenu:AllOpenRequests' => 'Open requests: %1$d',
|
'UI:WelcomeMenu:AllOpenRequests' => 'Open requests: %1$d',
|
||||||
'UI:WelcomeMenu:MyCalls' => 'My requests',
|
'UI:WelcomeMenu:MyCalls' => 'My requests',
|
||||||
'UI:WelcomeMenu:OpenIncidents' => 'Open incidents: %1$d',
|
'UI:WelcomeMenu:OpenIncidents' => 'Open incidents: %1$d',
|
||||||
|
|||||||
@@ -444,6 +444,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
|||||||
Esperamos distrute de esta versión tanto como nosotros la imaginamos y creamos.</div>
|
Esperamos distrute de esta versión tanto como nosotros la imaginamos y creamos.</div>
|
||||||
|
|
||||||
<div>Configure las preferencias de '.ITOP_APPLICATION.' para una experiencia personalizada.</div>',
|
<div>Configure las preferencias de '.ITOP_APPLICATION.' para una experiencia personalizada.</div>',
|
||||||
|
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, descartar este mensaje',
|
||||||
'UI:WelcomeMenu:AllOpenRequests' => 'Requerimientos Abiertos: %1$d',
|
'UI:WelcomeMenu:AllOpenRequests' => 'Requerimientos Abiertos: %1$d',
|
||||||
'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos',
|
'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos',
|
||||||
'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d',
|
'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d',
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
|||||||
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
|
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
|
||||||
'Core:AttributeTagSet+' => '',
|
'Core:AttributeTagSet+' => '',
|
||||||
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
|
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
|
||||||
|
'Core:Placeholder:CannotBeResolved' => '(%1$s : non remplacé)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de la classe %3$s)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de la classe %3$s)',
|
||||||
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s d\'une sous-classe)',
|
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s d\'une sous-classe)',
|
||||||
|
|||||||
@@ -433,6 +433,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
|||||||
Nous espérons que vous aimerez cette version autant que nous avons eu du plaisir à l\'imaginer et à la créer.</div>
|
Nous espérons que vous aimerez cette version autant que nous avons eu du plaisir à l\'imaginer et à la créer.</div>
|
||||||
|
|
||||||
<div>Configurez vos préférences '.ITOP_APPLICATION.' pour une expérience personnalisée.</div>',
|
<div>Configurez vos préférences '.ITOP_APPLICATION.' pour une expérience personnalisée.</div>',
|
||||||
|
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, supprimer ce message',
|
||||||
'UI:WelcomeMenu:AllOpenRequests' => 'Requêtes en cours: %1$d',
|
'UI:WelcomeMenu:AllOpenRequests' => 'Requêtes en cours: %1$d',
|
||||||
'UI:WelcomeMenu:MyCalls' => 'Mes appels support',
|
'UI:WelcomeMenu:MyCalls' => 'Mes appels support',
|
||||||
'UI:WelcomeMenu:OpenIncidents' => 'Incidents en cours: %1$d',
|
'UI:WelcomeMenu:OpenIncidents' => 'Incidents en cours: %1$d',
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -14,6 +14,7 @@ return array(
|
|||||||
'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php',
|
'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
|
'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
'AbstractWeeklyScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
|
'AbstractWeeklyScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
|
||||||
|
'AbstractWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
'Action' => $baseDir . '/core/action.class.inc.php',
|
'Action' => $baseDir . '/core/action.class.inc.php',
|
||||||
'ActionChecker' => $baseDir . '/core/userrights.class.inc.php',
|
'ActionChecker' => $baseDir . '/core/userrights.class.inc.php',
|
||||||
'ActionEmail' => $baseDir . '/core/action.class.inc.php',
|
'ActionEmail' => $baseDir . '/core/action.class.inc.php',
|
||||||
@@ -363,6 +364,8 @@ return array(
|
|||||||
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => $baseDir . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
|
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => $baseDir . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
|
||||||
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
|
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => $baseDir . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
|
||||||
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
|
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => $baseDir . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
|
||||||
|
'Combodo\\iTop\\Application\\WelcomePopup\\DefaultWelcomePopup' => $baseDir . '/sources/Application/WelcomePopup/DefaultWelcomePopup.php',
|
||||||
|
'Combodo\\iTop\\Application\\WelcomePopup\\WelcomePopupService' => $baseDir . '/sources/Application/WelcomePopup/WelcomePopupService.php',
|
||||||
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
|
'Combodo\\iTop\\Composer\\iTopComposer' => $baseDir . '/sources/Composer/iTopComposer.php',
|
||||||
'Combodo\\iTop\\Controller\\AbstractController' => $baseDir . '/sources/Controller/AbstractController.php',
|
'Combodo\\iTop\\Controller\\AbstractController' => $baseDir . '/sources/Controller/AbstractController.php',
|
||||||
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
|
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
|
||||||
@@ -372,6 +375,7 @@ return array(
|
|||||||
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php',
|
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php',
|
||||||
'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php',
|
'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php',
|
||||||
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => $baseDir . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
|
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => $baseDir . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
|
||||||
|
'Combodo\\iTop\\Controller\\WelcomePopupController' => $baseDir . '/sources/Controller/WelcomePopupController.php',
|
||||||
'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php',
|
'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php',
|
||||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
|
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
|
||||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
|
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
|
||||||
@@ -472,6 +476,8 @@ return array(
|
|||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
|
||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
|
||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
|
||||||
|
'Combodo\\iTop\\SessionTracker\\SessionGC' => $baseDir . '/sources/SessionTracker/SessionGC.php',
|
||||||
|
'Combodo\\iTop\\SessionTracker\\SessionHandler' => $baseDir . '/sources/SessionTracker/SessionHandler.php',
|
||||||
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
|
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
'Config' => $baseDir . '/core/config.class.inc.php',
|
'Config' => $baseDir . '/core/config.class.inc.php',
|
||||||
@@ -2985,6 +2991,7 @@ return array(
|
|||||||
'iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php',
|
'iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php',
|
||||||
'iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php',
|
'iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php',
|
||||||
'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php',
|
'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php',
|
||||||
|
'iWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
|
||||||
'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php',
|
'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php',
|
||||||
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
|
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
|
||||||
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',
|
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
'AbstractWeeklyScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
|
'AbstractWeeklyScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
|
||||||
|
'AbstractWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php',
|
'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php',
|
||||||
'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
|
'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
|
||||||
'ActionEmail' => __DIR__ . '/../..' . '/core/action.class.inc.php',
|
'ActionEmail' => __DIR__ . '/../..' . '/core/action.class.inc.php',
|
||||||
@@ -727,6 +728,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
|
'Combodo\\iTop\\Application\\UI\\Links\\Set\\LinkSetUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php',
|
||||||
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
|
'Combodo\\iTop\\Application\\UI\\Preferences\\BlockShortcuts\\BlockShortcuts' => __DIR__ . '/../..' . '/sources/Application/UI/Preferences/BlockShortcuts/BlockShortcuts.php',
|
||||||
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
|
'Combodo\\iTop\\Application\\UI\\Printable\\BlockPrintHeader\\BlockPrintHeader' => __DIR__ . '/../..' . '/sources/Application/UI/Printable/BlockPrintHeader/BlockPrintHeader.php',
|
||||||
|
'Combodo\\iTop\\Application\\WelcomePopup\\DefaultWelcomePopup' => __DIR__ . '/../..' . '/sources/Application/WelcomePopup/DefaultWelcomePopup.php',
|
||||||
|
'Combodo\\iTop\\Application\\WelcomePopup\\WelcomePopupService' => __DIR__ . '/../..' . '/sources/Application/WelcomePopup/WelcomePopupService.php',
|
||||||
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
|
'Combodo\\iTop\\Composer\\iTopComposer' => __DIR__ . '/../..' . '/sources/Composer/iTopComposer.php',
|
||||||
'Combodo\\iTop\\Controller\\AbstractController' => __DIR__ . '/../..' . '/sources/Controller/AbstractController.php',
|
'Combodo\\iTop\\Controller\\AbstractController' => __DIR__ . '/../..' . '/sources/Controller/AbstractController.php',
|
||||||
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
|
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
|
||||||
@@ -736,6 +739,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php',
|
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php',
|
||||||
'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php',
|
'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php',
|
||||||
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => __DIR__ . '/../..' . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
|
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => __DIR__ . '/../..' . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
|
||||||
|
'Combodo\\iTop\\Controller\\WelcomePopupController' => __DIR__ . '/../..' . '/sources/Controller/WelcomePopupController.php',
|
||||||
'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php',
|
'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php',
|
||||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
|
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
|
||||||
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
|
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
|
||||||
@@ -836,6 +840,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
|
||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
|
||||||
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
|
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
|
||||||
|
'Combodo\\iTop\\SessionTracker\\SessionGC' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionGC.php',
|
||||||
|
'Combodo\\iTop\\SessionTracker\\SessionHandler' => __DIR__ . '/../..' . '/sources/SessionTracker/SessionHandler.php',
|
||||||
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
|
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
|
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
|
||||||
@@ -3349,6 +3355,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
|||||||
'iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php',
|
'iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php',
|
||||||
'iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php',
|
'iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php',
|
||||||
'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php',
|
'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php',
|
||||||
|
'iWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
|
||||||
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
|
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
|
||||||
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
|
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
|
||||||
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
|
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
|
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
|
||||||
'name' => 'combodo/itop',
|
'name' => 'combodo/itop',
|
||||||
'dev' => true,
|
'dev' => true,
|
||||||
),
|
),
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
|
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'combodo/tcpdf' => array(
|
'combodo/tcpdf' => array(
|
||||||
|
|||||||
14
pages/UI.php
14
pages/UI.php
@@ -20,6 +20,7 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
|||||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||||
use Combodo\iTop\Controller\Base\Layout\ObjectController;
|
use Combodo\iTop\Controller\Base\Layout\ObjectController;
|
||||||
use Combodo\iTop\Service\Router\Router;
|
use Combodo\iTop\Service\Router\Router;
|
||||||
|
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a popup welcome message, once per session at maximum
|
* Displays a popup welcome message, once per session at maximum
|
||||||
@@ -29,18 +30,17 @@ use Combodo\iTop\Service\Router\Router;
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function DisplayWelcomePopup(WebPage $oP)
|
function DisplayWelcomePopup(WebPage $oP): void
|
||||||
{
|
{
|
||||||
if (!Session::IsSet('welcome'))
|
if (!Session::IsSet('welcome'))
|
||||||
{
|
{
|
||||||
// Check, only once per session, if the popup should be displayed...
|
$oWelcomePopupService = new WelcomePopupService();
|
||||||
// If the user did not already ask for hiding it forever
|
$aMessages = $oWelcomePopupService->GetMessages();
|
||||||
$bPopup = appUserPreferences::GetPref('welcome_popup', true);
|
if (count($aMessages) > 0)
|
||||||
if ($bPopup)
|
|
||||||
{
|
{
|
||||||
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup');
|
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup', ['messages' => $aMessages]);
|
||||||
Session::Set('welcome', 'ok');
|
|
||||||
}
|
}
|
||||||
|
Session::Set('welcome', 'ok'); // Try just once per session
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
|||||||
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
|
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
|
||||||
use Combodo\iTop\Service\Router\Router;
|
use Combodo\iTop\Service\Router\Router;
|
||||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
||||||
|
use Combodo\iTop\Controller\WelcomePopupController;
|
||||||
|
|
||||||
require_once('../approot.inc.php');
|
require_once('../approot.inc.php');
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ try
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ContextTag::AddContext(ContextTag::TAG_CONSOLE);
|
$oTag = new ContextTag(ContextTag::TAG_CONSOLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// First check if we can redirect the route to a dedicated controller
|
// First check if we can redirect the route to a dedicated controller
|
||||||
@@ -201,10 +202,10 @@ try
|
|||||||
$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
|
$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$aPrefillFormParam = array(
|
$aPrefillFormParam = array(
|
||||||
'user' => Session::Get("auth_user"),
|
'user' => Session::Get("auth_user"),
|
||||||
'context' => $oAppContext->GetAsHash(),
|
'context' => $oAppContext->GetAsHash(),
|
||||||
'att_code' => $sAttCode,
|
'att_code' => $sAttCode,
|
||||||
'origin' => 'console',
|
'origin' => 'console',
|
||||||
'source_obj' => $oObj
|
'source_obj' => $oObj
|
||||||
);
|
);
|
||||||
$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
|
$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
|
||||||
@@ -276,10 +277,10 @@ try
|
|||||||
$oPage->SetContentType('text/html');
|
$oPage->SetContentType('text/html');
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$aPrefillFormParam = array(
|
$aPrefillFormParam = array(
|
||||||
'user' => Session::Get('auth_user'),
|
'user' => Session::Get('auth_user'),
|
||||||
'context' => $oAppContext->GetAsHash(),
|
'context' => $oAppContext->GetAsHash(),
|
||||||
'att_code' => $sAttCode,
|
'att_code' => $sAttCode,
|
||||||
'origin' => 'console',
|
'origin' => 'console',
|
||||||
'source_obj' => $oObj,
|
'source_obj' => $oObj,
|
||||||
);
|
);
|
||||||
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
|
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
|
||||||
@@ -303,10 +304,10 @@ try
|
|||||||
}
|
}
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$aPrefillFormParam = array(
|
$aPrefillFormParam = array(
|
||||||
'user' => Session::Get('auth_user'),
|
'user' => Session::Get('auth_user'),
|
||||||
'context' => $oAppContext->GetAsHash(),
|
'context' => $oAppContext->GetAsHash(),
|
||||||
'att_code' => $sAttCode,
|
'att_code' => $sAttCode,
|
||||||
'origin' => 'console',
|
'origin' => 'console',
|
||||||
'source_obj' => $oObj,
|
'source_obj' => $oObj,
|
||||||
);
|
);
|
||||||
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
|
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
|
||||||
@@ -418,10 +419,10 @@ try
|
|||||||
$iInputId = utils::ReadParam('iInputId', '');
|
$iInputId = utils::ReadParam('iInputId', '');
|
||||||
$sAttCode = utils::ReadParam('sAttCode', '');
|
$sAttCode = utils::ReadParam('sAttCode', '');
|
||||||
$sJson = utils::ReadParam('json', '', false, 'raw_data');
|
$sJson = utils::ReadParam('json', '', false, 'raw_data');
|
||||||
$bTargetClassSelected = utils::ReadParam('bTargetClassSelected', '', false, 'raw_data');
|
$bTargetClassSelected = utils::ReadParam('bTargetClassSelected', '', false, 'raw_data');
|
||||||
// Building form, if target class has child classes we ask the user for the desired leaf class, unless we've already done just that
|
// Building form, if target class has child classes we ask the user for the desired leaf class, unless we've already done just that
|
||||||
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
|
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
|
||||||
if(!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)){
|
if (!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)) {
|
||||||
$oWidget->GetClassSelectionForm($oPage);
|
$oWidget->GetClassSelectionForm($oPage);
|
||||||
} else {
|
} else {
|
||||||
$aPrefillFormParam = array();
|
$aPrefillFormParam = array();
|
||||||
@@ -430,11 +431,11 @@ try
|
|||||||
$oObj = $oWizardHelper->GetTargetObject();
|
$oObj = $oWizardHelper->GetTargetObject();
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$aPrefillFormParam = array(
|
$aPrefillFormParam = array(
|
||||||
'user' => Session::Get('auth_user'),
|
'user' => Session::Get('auth_user'),
|
||||||
'context' => $oAppContext->GetAsHash(),
|
'context' => $oAppContext->GetAsHash(),
|
||||||
'att_code' => $sAttCode,
|
'att_code' => $sAttCode,
|
||||||
'source_obj' => $oObj,
|
'source_obj' => $oObj,
|
||||||
'origin' => 'console'
|
'origin' => 'console'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Search form: no current object
|
// Search form: no current object
|
||||||
@@ -527,7 +528,8 @@ try
|
|||||||
$oObj->Set($sAttCode, $defaultValue);
|
$oObj->Set($sAttCode, $defaultValue);
|
||||||
}
|
}
|
||||||
$sFormPrefix = $oWizardHelper->GetFormPrefix();
|
$sFormPrefix = $oWizardHelper->GetFormPrefix();
|
||||||
$aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(), $oWizardHelper->GetInitialState());
|
$aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(),
|
||||||
|
$oWizardHelper->GetInitialState());
|
||||||
foreach ($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) {
|
foreach ($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) {
|
||||||
$sId = $oWizardHelper->GetIdForField($sAttCode);
|
$sId = $oWizardHelper->GetIdForField($sAttCode);
|
||||||
if ($sId != '') {
|
if ($sId != '') {
|
||||||
@@ -573,7 +575,8 @@ try
|
|||||||
$sTargetState = utils::ReadParam('target_state', '');
|
$sTargetState = utils::ReadParam('target_state', '');
|
||||||
$iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
|
$iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
|
||||||
$oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState);
|
$oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState);
|
||||||
cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId));
|
cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(),
|
||||||
|
array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// DisplayBlock
|
// DisplayBlock
|
||||||
@@ -600,8 +603,7 @@ try
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$oFilter = DBSearch::unserialize($sFilter);
|
$oFilter = DBSearch::unserialize($sFilter);
|
||||||
}
|
} catch (CoreException $e) {
|
||||||
catch (CoreException $e) {
|
|
||||||
$sFilter = utils::HtmlEntities($sFilter);
|
$sFilter = utils::HtmlEntities($sFilter);
|
||||||
$oPage->p("Invalid query (invalid filter) : <code>$sFilter</code>");
|
$oPage->p("Invalid query (invalid filter) : <code>$sFilter</code>");
|
||||||
IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter");
|
IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter");
|
||||||
@@ -666,7 +668,8 @@ try
|
|||||||
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
|
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
|
||||||
$aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson).
|
$aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson).
|
||||||
',keys: { x: \'label\', value: [\'value\']'.
|
',keys: { x: \'label\', value: [\'value\']'.
|
||||||
'},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
|
'},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'',
|
||||||
|
$oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'pie':
|
case 'pie':
|
||||||
@@ -677,7 +680,8 @@ try
|
|||||||
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
|
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
|
||||||
$aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns).
|
$aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns).
|
||||||
',names: '.str_replace('"', '\'', $oBlock->sJSNames).
|
',names: '.str_replace('"', '\'', $oBlock->sJSNames).
|
||||||
',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
|
',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'',
|
||||||
|
$oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -777,14 +781,14 @@ try
|
|||||||
$oFilter = new DBObjectSearch($sClass);
|
$oFilter = new DBObjectSearch($sClass);
|
||||||
$oSet = new CMDBObjectSet($oFilter);
|
$oSet = new CMDBObjectSet($oFilter);
|
||||||
$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array(
|
$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array(
|
||||||
'currentId' => $currentId,
|
'currentId' => $currentId,
|
||||||
'baseClass' => $sRootClass,
|
'baseClass' => $sRootClass,
|
||||||
'action' => $sAction,
|
'action' => $sAction,
|
||||||
'table_id' => $sTableId,
|
'table_id' => $sTableId,
|
||||||
'selection_mode' => $sSelectionMode,
|
'selection_mode' => $sSelectionMode,
|
||||||
'result_list_outer_selector' => $sResultListOuterSelector,
|
'result_list_outer_selector' => $sResultListOuterSelector,
|
||||||
'cssCount' => $scssCount,
|
'cssCount' => $scssCount,
|
||||||
'table_inner_id' => $sTableInnerId
|
'table_inner_id' => $sTableInnerId
|
||||||
));
|
));
|
||||||
$oPage->add($sHtml);
|
$oPage->add($sHtml);
|
||||||
break;
|
break;
|
||||||
@@ -821,13 +825,13 @@ try
|
|||||||
TemporaryObjectManager::GetInstance()->CancelAllTemporaryObjects($iTransactionId);
|
TemporaryObjectManager::GetInstance()->CancelAllTemporaryObjects($iTransactionId);
|
||||||
|
|
||||||
IssueLog::Trace('on_form_cancel', $sObjClass, array(
|
IssueLog::Trace('on_form_cancel', $sObjClass, array(
|
||||||
'$iObjKey' => $iObjKey,
|
'$iObjKey' => $iObjKey,
|
||||||
'$sTransactionId' => $iTransactionId,
|
'$sTransactionId' => $iTransactionId,
|
||||||
'$sTempId' => $sTempId,
|
'$sTempId' => $sTempId,
|
||||||
'$sToken' => $sToken,
|
'$sToken' => $sToken,
|
||||||
'$sUser' => UserRights::GetUser(),
|
'$sUser' => UserRights::GetUser(),
|
||||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||||
));
|
));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -879,11 +883,9 @@ try
|
|||||||
$oDoc = utils::ReadPostedDocument('dashboard_upload_file');
|
$oDoc = utils::ReadPostedDocument('dashboard_upload_file');
|
||||||
$oDashboard->FromXml($oDoc->GetData());
|
$oDashboard->FromXml($oDoc->GetData());
|
||||||
$oDashboard->Save();
|
$oDashboard->Save();
|
||||||
}
|
} catch (DOMException $e) {
|
||||||
catch (DOMException $e) {
|
|
||||||
$aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile'));
|
$aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile'));
|
||||||
}
|
} catch (Exception $e) {
|
||||||
catch (Exception $e) {
|
|
||||||
$aResult = array('error' => $e->getMessage());
|
$aResult = array('error' => $e->getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1054,7 +1056,8 @@ EOF
|
|||||||
$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script
|
$oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script
|
||||||
// but is executed BEFORE all 'ready_scripts'
|
// but is executed BEFORE all 'ready_scripts'
|
||||||
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
||||||
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property'));
|
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
|
||||||
|
array('operation' => 'update_dashlet_property'));
|
||||||
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard'));
|
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard'));
|
||||||
$sHtml = str_replace("\n", '', $sHtml);
|
$sHtml = str_replace("\n", '', $sHtml);
|
||||||
$sHtml = str_replace("\r", '', $sHtml);
|
$sHtml = str_replace("\r", '', $sHtml);
|
||||||
@@ -1113,7 +1116,8 @@ EOF
|
|||||||
}
|
}
|
||||||
if ($oDashlet->IsFormRedrawNeeded()) {
|
if ($oDashlet->IsFormRedrawNeeded()) {
|
||||||
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
$oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed
|
||||||
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
|
$oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
|
||||||
|
array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams));
|
||||||
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard'));
|
$sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard'));
|
||||||
$sHtml = str_replace("\n", '', $sHtml);
|
$sHtml = str_replace("\n", '', $sHtml);
|
||||||
$sHtml = str_replace("\r", '', $sHtml);
|
$sHtml = str_replace("\r", '', $sHtml);
|
||||||
@@ -1359,7 +1363,8 @@ JS
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sFullTextJS = addslashes($sFullText);
|
$sFullTextJS = addslashes($sFullText);
|
||||||
$bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]);
|
$bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query',
|
||||||
|
$aAccelerators[$sClassName]);
|
||||||
if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) {
|
if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) {
|
||||||
$bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
|
$bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
|
||||||
}
|
}
|
||||||
@@ -1393,9 +1398,11 @@ EOF;
|
|||||||
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
|
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
|
||||||
$oPage->add("<div class=\"page_header\">\n");
|
$oPage->add("<div class=\"page_header\">\n");
|
||||||
if (array_key_exists($sClassName, $aAccelerators)) {
|
if (array_key_exists($sClassName, $aAccelerators)) {
|
||||||
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
|
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
|
||||||
|
count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
|
||||||
} else {
|
} else {
|
||||||
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."</h2>\n");
|
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
|
||||||
|
count($aLeafs), Metamodel::GetName($sClassName))."</h2>\n");
|
||||||
}
|
}
|
||||||
$oPage->add("</div>\n");
|
$oPage->add("</div>\n");
|
||||||
$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
|
$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
|
||||||
@@ -1411,7 +1418,8 @@ EOF;
|
|||||||
if (array_key_exists($sClassName, $aAccelerators)) {
|
if (array_key_exists($sClassName, $aAccelerators)) {
|
||||||
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
|
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
|
||||||
$oPage->add("<div class=\"page_header\">\n");
|
$oPage->add("<div class=\"page_header\">\n");
|
||||||
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', 0, Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
|
$oPage->add('<h2 class="ibo-global-search--result--title">'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
|
||||||
|
0, Metamodel::GetName($sClassName)).$sEnlargeButton."</h2>\n");
|
||||||
$oPage->add("</div>\n");
|
$oPage->add("</div>\n");
|
||||||
$oPage->add("</div>\n");
|
$oPage->add("</div>\n");
|
||||||
$oPage->p(' '); // Some space ?
|
$oPage->p(' '); // Some space ?
|
||||||
@@ -1491,7 +1499,8 @@ EOF
|
|||||||
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||||
$oSet = new DBObjectSet($oFilter);
|
$oSet = new DBObjectSet($oFilter);
|
||||||
$oPage->add("<div class=\"page_header\">\n");
|
$oPage->add("<div class=\"page_header\">\n");
|
||||||
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)." <span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
|
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)." <span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
|
||||||
|
$oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
|
||||||
$oPage->add("</div>\n");
|
$oPage->add("</div>\n");
|
||||||
if ($oSet->Count() > 0) {
|
if ($oSet->Count() > 0) {
|
||||||
$aLeafs = array();
|
$aLeafs = array();
|
||||||
@@ -1579,10 +1588,11 @@ EOF
|
|||||||
$oPage->add('<div class="statistics"><div class="stats-toggle closed">'.Dict::S('ExcelExport:Statistics').'<div class="stats-data"></div></div></div>');
|
$oPage->add('<div class="statistics"><div class="stats-toggle closed">'.Dict::S('ExcelExport:Statistics').'<div class="stats-data"></div></div></div>');
|
||||||
$oPage->add('</div>');
|
$oPage->add('</div>');
|
||||||
$aLabels = array(
|
$aLabels = array(
|
||||||
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
|
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
|
||||||
'cancel_button' => Dict::S('UI:Button:Cancel'),
|
'cancel_button' => Dict::S('UI:Button:Cancel'),
|
||||||
'export_button' => Dict::S('ExcelExporter:ExportButton'),
|
'export_button' => Dict::S('ExcelExporter:ExportButton'),
|
||||||
'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??)
|
'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'),
|
||||||
|
//TODO: better name for the file (based on the class of the filter??)
|
||||||
);
|
);
|
||||||
$sJSLabels = json_encode($aLabels);
|
$sJSLabels = json_encode($aLabels);
|
||||||
$sFilter = addslashes($sFilter);
|
$sFilter = addslashes($sFilter);
|
||||||
@@ -1694,7 +1704,8 @@ EOF
|
|||||||
if ($sDirection == 'up') {
|
if ($sDirection == 'up') {
|
||||||
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
|
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
|
||||||
} else {
|
} else {
|
||||||
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
|
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects,
|
||||||
|
$aContexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove excluded classes from the graph
|
// Remove excluded classes from the graph
|
||||||
@@ -1754,9 +1765,11 @@ EOF
|
|||||||
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
|
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
|
||||||
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
|
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
|
||||||
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));*/
|
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));*/
|
||||||
$oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));
|
$oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(),
|
||||||
|
Metamodel::GetName($sListClass)));
|
||||||
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
|
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
|
||||||
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)));
|
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet,
|
||||||
|
array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then the content of the groups (one table per group)
|
// Then the content of the groups (one table per group)
|
||||||
@@ -1768,8 +1781,10 @@ EOF
|
|||||||
$sListClass = get_class(current($aObjects));
|
$sListClass = get_class(current($aObjects));
|
||||||
$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
|
$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
|
||||||
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
|
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
|
||||||
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl);
|
$sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/',
|
||||||
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass));
|
$sIconUrl);
|
||||||
|
$oTitle = new Html("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".Dict::Format('UI:RelationGroupNumber_N',
|
||||||
|
(1 + $idx)), Metamodel::GetName($sListClass));
|
||||||
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
|
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
|
||||||
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet));
|
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet));
|
||||||
|
|
||||||
@@ -1843,7 +1858,8 @@ EOF
|
|||||||
if ($sDirection == 'up') {
|
if ($sDirection == 'up') {
|
||||||
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
|
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
|
||||||
} else {
|
} else {
|
||||||
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts);
|
$oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects,
|
||||||
|
$aContexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove excluded classes from the graph
|
// Remove excluded classes from the graph
|
||||||
@@ -1874,13 +1890,15 @@ EOF
|
|||||||
$oSearch = new DBObjectSearch($sListClass);
|
$oSearch = new DBObjectSearch($sListClass);
|
||||||
$oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
|
$oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
|
||||||
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), 1, "relation_group_$idx"));
|
$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), 1,
|
||||||
|
"relation_group_$idx"));
|
||||||
$oBlock = new DisplayBlock($oSearch, 'list');
|
$oBlock = new DisplayBlock($oSearch, 'list');
|
||||||
$oBlock->Display($oPage, 'group_'.$iBlock++, array(
|
$oBlock->Display($oPage, 'group_'.$iBlock++, array(
|
||||||
'surround_with_panel' => true,
|
'surround_with_panel' => true,
|
||||||
'panel_class' => $sListClass,
|
'panel_class' => $sListClass,
|
||||||
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass)),
|
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']),
|
||||||
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
|
Metamodel::GetName($sListClass)),
|
||||||
|
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1894,11 +1912,12 @@ EOF
|
|||||||
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
|
||||||
$oBlock = new DisplayBlock($oSearch, 'list');
|
$oBlock = new DisplayBlock($oSearch, 'list');
|
||||||
$oBlock->Display($oPage, 'list_'.$iBlock++, array(
|
$oBlock->Display($oPage, 'list_'.$iBlock++, array(
|
||||||
'table_id' => 'ImpactAnalysis_'.$sListClass,
|
'table_id' => 'ImpactAnalysis_'.$sListClass,
|
||||||
'surround_with_panel' => true,
|
'surround_with_panel' => true,
|
||||||
'panel_class' => $sListClass,
|
'panel_class' => $sListClass,
|
||||||
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass)),
|
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys),
|
||||||
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
|
Metamodel::GetName($sListClass)),
|
||||||
|
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1950,7 +1969,8 @@ EOF
|
|||||||
|
|
||||||
$sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
|
$sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
|
||||||
$oAppContext = new ApplicationContext();
|
$oAppContext = new ApplicationContext();
|
||||||
$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket));
|
$oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey,
|
||||||
|
array('this' => $oTicket));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'export_build':
|
case 'export_build':
|
||||||
@@ -2000,7 +2020,7 @@ EOF
|
|||||||
$aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey);
|
$aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey);
|
||||||
|
|
||||||
$aResult = [
|
$aResult = [
|
||||||
'locked' => $aLockData['locked'],
|
'locked' => $aLockData['locked'],
|
||||||
'message' => '',
|
'message' => '',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -2087,11 +2107,11 @@ EOF
|
|||||||
$aResult = array(
|
$aResult = array(
|
||||||
'uploaded' => 0,
|
'uploaded' => 0,
|
||||||
'fileName' => '',
|
'fileName' => '',
|
||||||
'url' => '',
|
'url' => '',
|
||||||
'icon' => '',
|
'icon' => '',
|
||||||
'msg' => '',
|
'msg' => '',
|
||||||
'att_id' => 0,
|
'att_id' => 0,
|
||||||
'preview' => 'false',
|
'preview' => 'false',
|
||||||
);
|
);
|
||||||
|
|
||||||
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
|
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
|
||||||
@@ -2126,20 +2146,19 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
|
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
|
||||||
'$operation' => $operation,
|
'$operation' => $operation,
|
||||||
'$aResult' => $aResult,
|
'$aResult' => $aResult,
|
||||||
'secret' => $oAttachment->Get('secret'),
|
'secret' => $oAttachment->Get('secret'),
|
||||||
'temp_id' => $sTempId,
|
'temp_id' => $sTempId,
|
||||||
'item_class' => $sObjClass,
|
'item_class' => $sObjClass,
|
||||||
'user' => UserRights::GetUser(),
|
'user' => UserRights::GetUser(),
|
||||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
|
$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
|
||||||
}
|
}
|
||||||
}
|
} catch (FileUploadException $e) {
|
||||||
catch (FileUploadException $e) {
|
|
||||||
$aResult['error'] = $e->GetMessage();
|
$aResult['error'] = $e->GetMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2156,8 +2175,8 @@ EOF
|
|||||||
if (!InlineImage::IsImage($sDocMimeType)) {
|
if (!InlineImage::IsImage($sDocMimeType)) {
|
||||||
LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image',
|
LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image',
|
||||||
array(
|
array(
|
||||||
'operation' => 'cke_upload_and_browse',
|
'operation' => 'cke_upload_and_browse',
|
||||||
'class' => $sObjClass,
|
'class' => $sObjClass,
|
||||||
'ImgMimeType' => $sDocMimeType,
|
'ImgMimeType' => $sDocMimeType,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@@ -2174,22 +2193,21 @@ EOF
|
|||||||
$iAttId = $oAttachment->DBInsert();
|
$iAttId = $oAttachment->DBInsert();
|
||||||
|
|
||||||
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
|
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
|
||||||
'$operation' => $operation,
|
'$operation' => $operation,
|
||||||
'secret' => $oAttachment->Get('secret'),
|
'secret' => $oAttachment->Get('secret'),
|
||||||
'temp_id' => $sTempId,
|
'temp_id' => $sTempId,
|
||||||
'item_class' => $sObjClass,
|
'item_class' => $sObjClass,
|
||||||
'user' => UserRights::GetUser(),
|
'user' => UserRights::GetUser(),
|
||||||
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
|
||||||
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} catch (FileUploadException $e) {
|
||||||
catch (FileUploadException $e) {
|
|
||||||
LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured',
|
LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured',
|
||||||
array(
|
array(
|
||||||
'operation' => 'cke_upload_and_browse',
|
'operation' => 'cke_upload_and_browse',
|
||||||
'class' => $sObjClass,
|
'class' => $sObjClass,
|
||||||
'exceptionMsg' => $e,
|
'exceptionMsg' => $e,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -2303,7 +2321,8 @@ $('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true });
|
|||||||
EOF
|
EOF
|
||||||
);
|
);
|
||||||
$sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
|
$sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))";
|
||||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
|
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(),
|
||||||
|
array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId));
|
||||||
$oPage->add("<div><fieldset><legend>$sAvailableImagesLegend</legend>");
|
$oPage->add("<div><fieldset><legend>$sAvailableImagesLegend</legend>");
|
||||||
|
|
||||||
if ($oSet->Count() == 0) {
|
if ($oSet->Count() == 0) {
|
||||||
@@ -2360,7 +2379,8 @@ EOF
|
|||||||
$aTriggerMentionedSearches = [];
|
$aTriggerMentionedSearches = [];
|
||||||
|
|
||||||
$aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL));
|
$aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL));
|
||||||
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aTriggerSetParams);
|
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"),
|
||||||
|
array(), $aTriggerSetParams);
|
||||||
/** @var \TriggerOnObjectMention $oTrigger */
|
/** @var \TriggerOnObjectMention $oTrigger */
|
||||||
while ($oTrigger = $oTriggerSet->Fetch()) {
|
while ($oTrigger = $oTriggerSet->Fetch()) {
|
||||||
$sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter');
|
$sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter');
|
||||||
@@ -2394,7 +2414,8 @@ EOF
|
|||||||
|
|
||||||
// Add condition to filter on the friendlyname
|
// Add condition to filter on the friendlyname
|
||||||
$oSearch->AddConditionExpression(
|
$oSearch->AddConditionExpression(
|
||||||
new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE', new VariableExpression('needle'))
|
new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE',
|
||||||
|
new VariableExpression('needle'))
|
||||||
);
|
);
|
||||||
|
|
||||||
$oSet = new DBObjectSet($oSearch, [], $aSearchParams);
|
$oSet = new DBObjectSet($oSearch, [], $aSearchParams);
|
||||||
@@ -2413,8 +2434,8 @@ EOF
|
|||||||
$sObjectClass = get_class($oObject);
|
$sObjectClass = get_class($oObject);
|
||||||
$iObjectId = $oObject->GetKey();
|
$iObjectId = $oObject->GetKey();
|
||||||
$aMatch = [
|
$aMatch = [
|
||||||
'class' => $sObjectClass,
|
'class' => $sObjectClass,
|
||||||
'id' => $iObjectId,
|
'id' => $iObjectId,
|
||||||
'friendlyname' => $oObject->Get('friendlyname'),
|
'friendlyname' => $oObject->Get('friendlyname'),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -2423,7 +2444,8 @@ EOF
|
|||||||
/** @var \ormDocument $oImage */
|
/** @var \ormDocument $oImage */
|
||||||
$oImage = $oObject->Get($sObjectImageAttCode);
|
$oImage = $oObject->Get($sObjectImageAttCode);
|
||||||
if (!$oImage->IsEmpty()) {
|
if (!$oImage->IsEmpty()) {
|
||||||
$aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId, $sObjectImageAttCode)."')";
|
$aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId,
|
||||||
|
$sObjectImageAttCode)."')";
|
||||||
$aMatch['initials'] = '';
|
$aMatch['initials'] = '';
|
||||||
} else {
|
} else {
|
||||||
// If no image found, fallback on initials
|
// If no image found, fallback on initials
|
||||||
@@ -2460,8 +2482,7 @@ EOF
|
|||||||
$aRenderRes = $oRenderer->Render($aRequestedFields);
|
$aRenderRes = $oRenderer->Render($aRequestedFields);
|
||||||
|
|
||||||
$aResult['form']['updated_fields'] = $aRenderRes;
|
$aResult['form']['updated_fields'] = $aRenderRes;
|
||||||
}
|
} catch (Exception $e) {
|
||||||
catch (Exception $e) {
|
|
||||||
$aResult['error'] = $e->getMessage();
|
$aResult['error'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
$oPage->SetData($aResult);
|
$oPage->SetData($aResult);
|
||||||
@@ -2478,10 +2499,9 @@ EOF
|
|||||||
$oController = new PreferencesController();
|
$oController = new PreferencesController();
|
||||||
$aResult = $oController->SetUserPicture();
|
$aResult = $oController->SetUserPicture();
|
||||||
$aResult['success'] = true;
|
$aResult['success'] = true;
|
||||||
}
|
} catch (Exception $oException) {
|
||||||
catch (Exception $oException) {
|
|
||||||
$aResult = [
|
$aResult = [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error_message' => $oException->getMessage(),
|
'error_message' => $oException->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -2500,10 +2520,9 @@ EOF
|
|||||||
$aResult = [
|
$aResult = [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
];
|
];
|
||||||
}
|
} catch (Exception $oException) {
|
||||||
catch (Exception $oException) {
|
|
||||||
$aResult = [
|
$aResult = [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error_message' => $oException->getMessage(),
|
'error_message' => $oException->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -2516,10 +2535,9 @@ EOF
|
|||||||
try {
|
try {
|
||||||
$oController = new ActivityPanelController();
|
$oController = new ActivityPanelController();
|
||||||
$aResult = $oController->AddCaseLogsEntries();
|
$aResult = $oController->AddCaseLogsEntries();
|
||||||
}
|
} catch (Exception $oException) {
|
||||||
catch (Exception $oException) {
|
|
||||||
$aResult = [
|
$aResult = [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error_message' => $oException->getMessage(),
|
'error_message' => $oException->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -2532,10 +2550,9 @@ EOF
|
|||||||
try {
|
try {
|
||||||
$oController = new ActivityPanelController();
|
$oController = new ActivityPanelController();
|
||||||
$aResult = $oController->LoadMoreEntries();
|
$aResult = $oController->LoadMoreEntries();
|
||||||
}
|
} catch (Exception $oException) {
|
||||||
catch (Exception $oException) {
|
|
||||||
$aResult = [
|
$aResult = [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error_message' => $oException->getMessage(),
|
'error_message' => $oException->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -2551,6 +2568,24 @@ EOF
|
|||||||
$oAjaxRenderController->GetMenusCount($oPage);
|
$oAjaxRenderController->GetMenusCount($oPage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// WelcomePopupMenu
|
||||||
|
//--------------------------------
|
||||||
|
case 'welcome_popup_acknowledge_message':
|
||||||
|
$oPage = new JsonPage();
|
||||||
|
try {
|
||||||
|
$oController = new WelcomePopupController();
|
||||||
|
$oController->AcknowledgeMessage();
|
||||||
|
$aResult = ['success' => true];
|
||||||
|
} catch (Exception $oException) {
|
||||||
|
$aResult = [
|
||||||
|
'success' => false,
|
||||||
|
'error_message' => $oException->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$oPage->SetData($aResult);
|
||||||
|
break;
|
||||||
|
|
||||||
//--------------------------------
|
//--------------------------------
|
||||||
// Object
|
// Object
|
||||||
//--------------------------------
|
//--------------------------------
|
||||||
|
|||||||
@@ -141,55 +141,58 @@ JS
|
|||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
|
$bIsSiloSelectionEnabled = MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
|
||||||
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
|
if ($bIsSiloSelectionEnabled)
|
||||||
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
|
{
|
||||||
$oFavoriteOrganizationsForm = new Form();
|
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
|
||||||
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
|
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
|
||||||
// Favorite organizations: the organizations listed in the drop-down menu
|
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
|
||||||
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
|
$oFavoriteOrganizationsForm = new Form();
|
||||||
$oFilter = DBObjectSearch::FromOQL($sOQL);
|
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
|
||||||
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
// Favorite organizations: the organizations listed in the drop-down menu
|
||||||
|
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
|
||||||
|
$oFilter = DBObjectSearch::FromOQL($sOQL);
|
||||||
|
$oBlock = new DisplayBlock($oFilter, 'list', false);
|
||||||
|
|
||||||
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
|
$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
|
||||||
|
|
||||||
$sIdFavoriteOrganizations = 1;
|
$sIdFavoriteOrganizations = 1;
|
||||||
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
|
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
|
||||||
'menu' => false,
|
'menu' => false,
|
||||||
'selection_mode' => true,
|
'selection_mode' => true,
|
||||||
'selection_type' => 'multiple',
|
'selection_type' => 'multiple',
|
||||||
'table_id' => 'user_prefs',
|
'table_id' => 'user_prefs',
|
||||||
'surround_with_panel' => false,
|
'surround_with_panel' => false,
|
||||||
'selected_rows' => $aFavoriteOrgs,
|
'selected_rows' => $aFavoriteOrgs,
|
||||||
]));
|
]));
|
||||||
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
|
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
|
||||||
|
|
||||||
// Button toolbar
|
// Button toolbar
|
||||||
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
|
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
|
||||||
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
|
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
|
||||||
|
|
||||||
// - Cancel button
|
// - Cancel button
|
||||||
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
|
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
|
||||||
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
|
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
|
||||||
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
|
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
|
||||||
// - Submit button
|
// - Submit button
|
||||||
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
|
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
|
||||||
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
|
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
|
||||||
|
|
||||||
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
|
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
|
||||||
// if ($aFavoriteOrgs == null) {
|
// if ($aFavoriteOrgs == null) {
|
||||||
// // All checked
|
// // All checked
|
||||||
// $oP->add_ready_script(
|
// $oP->add_ready_script(
|
||||||
// <<<JS
|
// <<<JS
|
||||||
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
|
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
|
||||||
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
|
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
|
||||||
//JS
|
//JS
|
||||||
// );
|
// );
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
|
|
||||||
|
|
||||||
|
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
|
||||||
|
}
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Shortcuts
|
// Shortcuts
|
||||||
|
|||||||
@@ -146,18 +146,23 @@ class DBBackup
|
|||||||
/**
|
/**
|
||||||
* Create a normalized backup name, depending on the current date/time and Database
|
* Create a normalized backup name, depending on the current date/time and Database
|
||||||
*
|
*
|
||||||
* @param string $sNameSpec Name and path, eventually containing itop placeholders + time formatting following the strftime() format {@link https://www.php.net/manual/fr/function.strftime.php}
|
* @param string|null $sNameSpec Name and path, eventually containing itop placeholders + time formatting following the strftime() format {@link https://www.php.net/manual/fr/function.strftime.php}
|
||||||
* @param \DateTime|null $oDateTime Date time to use for the name
|
* @param \DateTime|null $oDateTime Date time to use for the name
|
||||||
*
|
*
|
||||||
* @return string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
|
* @return ?string Name of the backup file WITHOUT the file extension (eg. `.tar.gz`)
|
||||||
* @since 3.1.0 N°5279 Add $oDateTime parameter
|
* @since 3.1.0 N°5279 Add $oDateTime parameter
|
||||||
*/
|
*/
|
||||||
public function MakeName(string $sNameSpec = "__DB__-%Y-%m-%d", DateTime $oDateTime = null)
|
public function MakeName(?string $sNameSpec = null, ?DateTime $oDateTime = null)
|
||||||
{
|
{
|
||||||
if ($oDateTime === null) {
|
if ($oDateTime === null) {
|
||||||
$oDateTime = new DateTime();
|
$oDateTime = new DateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//N°6640
|
||||||
|
if ($sNameSpec === null) {
|
||||||
|
$sNameSpec = "__DB__-%Y-%m-%d";
|
||||||
|
}
|
||||||
|
|
||||||
$sFileName = $sNameSpec;
|
$sFileName = $sNameSpec;
|
||||||
$sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
|
$sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
|
||||||
$sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
|
$sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ use Combodo\iTop\DesignElement;
|
|||||||
|
|
||||||
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
require_once(APPROOT.'setup/setuputils.class.inc.php');
|
||||||
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
require_once(APPROOT.'setup/modelfactory.class.inc.php');
|
||||||
|
require_once(APPROOT.'setup/parentmenunodecompiler.class.inc.php');
|
||||||
require_once(APPROOT.'core/moduledesign.class.inc.php');
|
require_once(APPROOT.'core/moduledesign.class.inc.php');
|
||||||
|
|
||||||
|
|
||||||
class DOMFormatException extends Exception
|
class DOMFormatException extends Exception
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -368,21 +368,16 @@ class MFCompiler
|
|||||||
*/
|
*/
|
||||||
protected function DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
|
protected function DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
|
||||||
{
|
{
|
||||||
$aAllClasses = array(); // flat list of classes
|
$aAllClasses = []; // flat list of classes
|
||||||
$aModulesInfo = array(); // Hash array of module_name => array('version' => string, 'root_dir' => string)
|
$aModulesInfo = []; // Hash array of module_name => array('version' => string, 'root_dir' => string)
|
||||||
|
|
||||||
// Determine the target modules for the MENUS
|
// Determine the target modules for the MENUS
|
||||||
//
|
|
||||||
$aMenuNodes = array();
|
|
||||||
$aMenusByModule = array();
|
|
||||||
foreach ($this->oFactory->GetNodes('menus/menu') as $oMenuNode)
|
|
||||||
{
|
|
||||||
$sMenuId = $oMenuNode->getAttribute('id');
|
|
||||||
$aMenuNodes[$sMenuId] = $oMenuNode;
|
|
||||||
|
|
||||||
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
|
/**
|
||||||
$aMenusByModule[$sModuleMenu][] = $sMenuId;
|
* @since 3.1 N°4762
|
||||||
}
|
*/
|
||||||
|
$oParentMenuNodeCompiler = new ParentMenuNodeCompiler($this);
|
||||||
|
$oParentMenuNodeCompiler->LoadXmlMenus($this->oFactory);
|
||||||
|
|
||||||
// Determine the target module (exactly one!) for USER RIGHTS
|
// Determine the target module (exactly one!) for USER RIGHTS
|
||||||
// This used to be based solely on the module which created the user_rights node first
|
// This used to be based solely on the module which created the user_rights node first
|
||||||
@@ -429,6 +424,7 @@ class MFCompiler
|
|||||||
|
|
||||||
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
|
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
|
||||||
|
|
||||||
|
$oParentMenuNodeCompiler->LoadModuleMenuInfo($aModules);
|
||||||
foreach ($aModules as $foo => $oModule) {
|
foreach ($aModules as $foo => $oModule) {
|
||||||
$sModuleName = $oModule->GetName();
|
$sModuleName = $oModule->GetName();
|
||||||
$sModuleVersion = $oModule->GetVersion();
|
$sModuleVersion = $oModule->GetVersion();
|
||||||
@@ -513,7 +509,7 @@ class MFCompiler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!array_key_exists($sModuleName, $aMenusByModule))
|
if (is_null($oParentMenuNodeCompiler->GetMenusByModule($sModuleName)))
|
||||||
{
|
{
|
||||||
$this->Log("Found module without menus declared: $sModuleName");
|
$this->Log("Found module without menus declared: $sModuleName");
|
||||||
}
|
}
|
||||||
@@ -533,79 +529,19 @@ class $sMenuCreationClass extends ModuleHandlerAPI
|
|||||||
global \$__comp_menus__; // ensure that the global variable is indeed global !
|
global \$__comp_menus__; // ensure that the global variable is indeed global !
|
||||||
|
|
||||||
EOF;
|
EOF;
|
||||||
// Preliminary: determine parent menus not defined within the current module
|
|
||||||
$aMenusToLoad = array();
|
$oParentMenuNodeCompiler->CompileModuleMenus($oModule, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||||
$aParentMenus = array();
|
|
||||||
foreach($aMenusByModule[$sModuleName] as $sMenuId)
|
|
||||||
{
|
|
||||||
$oMenuNode = $aMenuNodes[$sMenuId];
|
|
||||||
// compute parent hierarchy
|
|
||||||
$aParentIdHierarchy = [];
|
|
||||||
while ($sParent = $oMenuNode->GetChildText('parent', null)) {
|
|
||||||
array_unshift($aParentIdHierarchy, $sParent);
|
|
||||||
$oMenuNode = $aMenuNodes[$sParent];
|
|
||||||
}
|
|
||||||
$aMenusToLoad = array_merge($aMenusToLoad, $aParentIdHierarchy);
|
|
||||||
$aParentMenus = array_merge($aParentMenus, $aParentIdHierarchy);
|
|
||||||
// Note: the order matters: the parents must be defined BEFORE
|
|
||||||
$aMenusToLoad[] = $sMenuId;
|
|
||||||
}
|
|
||||||
$aMenusToLoad = array_unique($aMenusToLoad);
|
|
||||||
$aMenuLinesForAll = array();
|
|
||||||
$aMenuLinesForAdmins = array();
|
|
||||||
$aAdminMenus = array();
|
|
||||||
foreach($aMenusToLoad as $sMenuId)
|
|
||||||
{
|
|
||||||
$oMenuNode = $aMenuNodes[$sMenuId];
|
|
||||||
if (is_null($oMenuNode))
|
|
||||||
{
|
|
||||||
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
|
|
||||||
}
|
|
||||||
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup')
|
|
||||||
{
|
|
||||||
// Note: this algorithm is wrong
|
|
||||||
// 1 - the module may appear empty in the current module, while children are defined in other modules
|
|
||||||
// 2 - check recursively that child nodes are not empty themselves
|
|
||||||
// Future algorithm:
|
|
||||||
// a- browse the modules and build the menu tree
|
|
||||||
// b- browse the tree and blacklist empty menus
|
|
||||||
// c- before compiling, discard if blacklisted
|
|
||||||
if (!in_array($oMenuNode->getAttribute("id"), $aParentMenus))
|
|
||||||
{
|
|
||||||
// Discard empty menu groups
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/** @var \iTopWebPage $oP */
|
|
||||||
$aMenuLines = $this->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
|
||||||
}
|
|
||||||
catch (DOMFormatException $e)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
|
|
||||||
}
|
|
||||||
$sParent = $oMenuNode->GetChildText('parent', null);
|
|
||||||
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]))
|
|
||||||
{
|
|
||||||
$aMenuLinesForAdmins = array_merge($aMenuLinesForAdmins, $aMenuLines);
|
|
||||||
$aAdminMenus[$oMenuNode->getAttribute("id")] = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aMenuLinesForAll = array_merge($aMenuLinesForAll, $aMenuLines);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sIndent = "\t\t";
|
$sIndent = "\t\t";
|
||||||
foreach ($aMenuLinesForAll as $sPHPLine)
|
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAll() as $sPHPLine)
|
||||||
{
|
{
|
||||||
$sCompiledCode .= $sIndent.$sPHPLine."\n";
|
$sCompiledCode .= $sIndent.$sPHPLine."\n";
|
||||||
}
|
}
|
||||||
if (count($aMenuLinesForAdmins) > 0)
|
if (count($oParentMenuNodeCompiler->GetMenuLinesForAdmins()) > 0)
|
||||||
{
|
{
|
||||||
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
|
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
|
||||||
$sCompiledCode .= $sIndent."{\n";
|
$sCompiledCode .= $sIndent."{\n";
|
||||||
foreach ($aMenuLinesForAdmins as $sPHPLine)
|
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAdmins() as $sPHPLine)
|
||||||
{
|
{
|
||||||
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
|
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
|
||||||
}
|
}
|
||||||
@@ -2717,7 +2653,7 @@ CSS;
|
|||||||
* @throws \DOMException
|
* @throws \DOMException
|
||||||
* @throws \DOMFormatException
|
* @throws \DOMFormatException
|
||||||
*/
|
*/
|
||||||
protected function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
|
public function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
|
||||||
{
|
{
|
||||||
$this->CompileFiles($oMenu, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
|
$this->CompileFiles($oMenu, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
|
||||||
|
|
||||||
|
|||||||
287
setup/parentmenunodecompiler.class.inc.php
Normal file
287
setup/parentmenunodecompiler.class.inc.php
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1 N°4762
|
||||||
|
*/
|
||||||
|
class ParentMenuNodeCompiler
|
||||||
|
{
|
||||||
|
const COMPILED = 1;
|
||||||
|
const COMPILING = 2;
|
||||||
|
|
||||||
|
public static $bUseLegacyMenuCompilation = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MFCompiler
|
||||||
|
*/
|
||||||
|
private $oMFCompiler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* admin menus declaration lines: result of module menu compilation
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenuLinesForAdmins = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non-admin menus declaration lines: result of module menu compilation
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenuLinesForAll = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use to handle menu group compilation recurring algorithm
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenuProcessStatus = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenuNodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenusByModule = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aMenusToLoadByModule = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aParentMenusByModule = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used by overall algo
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aParentMenuNodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used by new algo
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aParentAdminMenus = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* used by overall algo
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $aParentModuleRootDirs = [];
|
||||||
|
|
||||||
|
public function __construct(MFCompiler $oMFCompiler) {
|
||||||
|
$this->oMFCompiler = $oMFCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function UseLegacyMenuCompilation(){
|
||||||
|
self::$bUseLegacyMenuCompilation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \ModelFactory $oFactory
|
||||||
|
* Initialize menu nodes arrays
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function LoadXmlMenus(\ModelFactory $oFactory) : void {
|
||||||
|
foreach ($oFactory->GetNodes('menus/menu') as $oMenuNode) {
|
||||||
|
$sMenuId = $oMenuNode->getAttribute('id');
|
||||||
|
$this->aMenuNodes[$sMenuId] = $oMenuNode;
|
||||||
|
|
||||||
|
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
|
||||||
|
$this->aMenusByModule[$sModuleMenu][] = $sMenuId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $aModules
|
||||||
|
* Initialize arrays related to parent/child menus
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function LoadModuleMenuInfo($aModules) : void
|
||||||
|
{
|
||||||
|
foreach ($aModules as $foo => $oModule) {
|
||||||
|
$sModuleRootDir = $oModule->GetRootDir();
|
||||||
|
$sModuleName = $oModule->GetName();
|
||||||
|
|
||||||
|
if (array_key_exists($sModuleName, $this->aMenusByModule)) {
|
||||||
|
$aMenusToLoad = [];
|
||||||
|
$aParentMenus = [];
|
||||||
|
|
||||||
|
foreach ($this->aMenusByModule[$sModuleName] as $sMenuId) {
|
||||||
|
$oMenuNode = $this->aMenuNodes[$sMenuId];
|
||||||
|
|
||||||
|
if (self::$bUseLegacyMenuCompilation){
|
||||||
|
if ($sParent = $oMenuNode->GetChildText('parent', null)) {
|
||||||
|
$aMenusToLoad[] = $sParent;
|
||||||
|
$aParentMenus[] = $sParent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup') {
|
||||||
|
$this->aParentModuleRootDirs[$sMenuId] = $sModuleRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sParent = $oMenuNode->GetChildText('parent', null)) {
|
||||||
|
$aMenusToLoad[] = $sParent;
|
||||||
|
$aParentMenus[] = $sParent;
|
||||||
|
|
||||||
|
$this->aParentModuleRootDirs[$sParent] = $sModuleRootDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($sMenuId, $this->aParentModuleRootDirs)){
|
||||||
|
$this->aParentMenuNodes[$sMenuId] = $oMenuNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: the order matters: the parents must be defined BEFORE
|
||||||
|
$aMenusToLoad[] = $sMenuId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aMenusToLoadByModule[$sModuleName] = array_unique($aMenusToLoad);
|
||||||
|
$this->aParentMenusByModule[$sModuleName] = array_unique($aParentMenus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the actual "Compilation" for one module at a time
|
||||||
|
* @param \MFModule $oModule
|
||||||
|
* @param string $sTempTargetDir
|
||||||
|
* @param string $sFinalTargetDir
|
||||||
|
* @param string $sRelativeDir
|
||||||
|
* @param Page $oP
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function CompileModuleMenus(MFModule $oModule, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP = null) : void
|
||||||
|
{
|
||||||
|
$this->aMenuLinesForAdmins = [];
|
||||||
|
$this->aMenuLinesForAll = [];
|
||||||
|
$aAdminMenus = [];
|
||||||
|
|
||||||
|
$sModuleRootDir = $oModule->GetRootDir();
|
||||||
|
$sModuleName = $oModule->GetName();
|
||||||
|
|
||||||
|
$aParentMenus = $this->aParentMenusByModule[$sModuleName];
|
||||||
|
foreach($this->aMenusToLoadByModule[$sModuleName] as $sMenuId)
|
||||||
|
{
|
||||||
|
$oMenuNode = $this->aMenuNodes[$sMenuId];
|
||||||
|
if (is_null($oMenuNode))
|
||||||
|
{
|
||||||
|
throw new Exception("Module '{$oModule->GetId()}' (location : '$sModuleRootDir') contains an unknown menuId : '$sMenuId'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$bUseLegacyMenuCompilation) {
|
||||||
|
if ($oMenuNode->getAttribute("xsi:type") == 'MenuGroup') {
|
||||||
|
// Note: this algorithm is wrong
|
||||||
|
// 1 - the module may appear empty in the current module, while children are defined in other modules
|
||||||
|
// 2 - check recursively that child nodes are not empty themselves
|
||||||
|
// Future algorithm:
|
||||||
|
// a- browse the modules and build the menu tree
|
||||||
|
// b- browse the tree and blacklist empty menus
|
||||||
|
// c- before compiling, discard if blacklisted
|
||||||
|
if (! in_array($oMenuNode->getAttribute("id"), $aParentMenus)) {
|
||||||
|
// Discard empty menu groups
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (array_key_exists($sMenuId, $this->aParentMenuNodes)) {
|
||||||
|
// compile parent menus recursively
|
||||||
|
$this->CompileParentMenuNode($sMenuId, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//both new/legacy algo: compile leaf menu
|
||||||
|
$aMenuLines = $this->oMFCompiler->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||||
|
}
|
||||||
|
catch (DOMFormatException $e)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||||
|
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($aAdminMenus[$sParent]) || isset($this->aParentAdminMenus[$sParent]))
|
||||||
|
{
|
||||||
|
$this->aMenuLinesForAdmins = array_merge($this->aMenuLinesForAdmins, $aMenuLines);
|
||||||
|
$aAdminMenus[$oMenuNode->getAttribute("id")] = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->aMenuLinesForAll = array_merge($this->aMenuLinesForAll, $aMenuLines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform parent menu compilation including its ancestrors (recursively)
|
||||||
|
* @param string $sMenuId
|
||||||
|
* @param string $sTempTargetDir
|
||||||
|
* @param string $sFinalTargetDir
|
||||||
|
* @param string $sRelativeDir
|
||||||
|
* @param Page $oP
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function CompileParentMenuNode(string $sMenuId, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP = null) : void
|
||||||
|
{
|
||||||
|
$oMenuNode = $this->aParentMenuNodes[$sMenuId];
|
||||||
|
$sStatus = array_key_exists($sMenuId, $this->aMenuProcessStatus) ? $this->aMenuProcessStatus[$sMenuId] : null;
|
||||||
|
if ($sStatus === self::COMPILED){
|
||||||
|
//node already processed before
|
||||||
|
return;
|
||||||
|
} else if ($sStatus === self::COMPILING){
|
||||||
|
throw new \Exception("Cyclic dependency between parent menus ($sMenuId)");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aMenuProcessStatus[$sMenuId] = self::COMPILING;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||||
|
if (! empty($sParent)){
|
||||||
|
//compile parents before (even parent of parents ... recursively)
|
||||||
|
$this->CompileParentMenuNode($sParent, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! array_key_exists($sMenuId, $this->aParentModuleRootDirs)){
|
||||||
|
throw new Exception("Failed to process parent menu '$sMenuId' that is referenced by a child but not defined");
|
||||||
|
}
|
||||||
|
$sModuleRootDir = $this->aParentModuleRootDirs[$sMenuId];
|
||||||
|
$aMenuLines = $this->oMFCompiler->CompileMenu($oMenuNode, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
|
||||||
|
} catch (DOMFormatException $e) {
|
||||||
|
throw new Exception("Failed to process menu '$sMenuId', from '$sModuleRootDir': ".$e->getMessage());
|
||||||
|
}
|
||||||
|
$sParent = $oMenuNode->GetChildText('parent', null);
|
||||||
|
if (($oMenuNode->GetChildText('enable_admin_only') == '1') || isset($this->aParentAdminMenus[$sParent])) {
|
||||||
|
$this->aMenuLinesForAdmins = array_merge($this->aMenuLinesForAdmins, $aMenuLines);
|
||||||
|
$this->aParentAdminMenus[$oMenuNode->getAttribute("id")] = true;
|
||||||
|
} else {
|
||||||
|
$this->aMenuLinesForAll = array_merge($this->aMenuLinesForAll, $aMenuLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->aMenuProcessStatus[$sMenuId] = self::COMPILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetMenusByModule(string $sModuleName) : ?array
|
||||||
|
{
|
||||||
|
if (array_key_exists($sModuleName, $this->aMenusByModule)) {
|
||||||
|
return $this->aMenusByModule[$sModuleName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetMenuLinesForAdmins(): array {
|
||||||
|
return $this->aMenuLinesForAdmins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetMenuLinesForAll(): array {
|
||||||
|
return $this->aMenuLinesForAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
namespace Combodo\iTop\Application\Helper;
|
namespace Combodo\iTop\Application\Helper;
|
||||||
|
|
||||||
|
use Combodo\iTop\SessionTracker\SessionHandler;
|
||||||
use utils;
|
use utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +35,7 @@ class Session
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!self::$bIsInitialized) {
|
if (!self::$bIsInitialized) {
|
||||||
|
SessionHandler::session_set_save_handler();
|
||||||
session_name('itop-'.md5(APPROOT));
|
session_name('itop-'.md5(APPROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ class NewsroomMenuFactory
|
|||||||
return $oMenu;
|
return $oMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is any Newsroom provider configured
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function HasProviders()
|
||||||
|
{
|
||||||
|
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
|
||||||
|
return count($aProviders) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare parameters for the newsroom JS widget
|
* Prepare parameters for the newsroom JS widget
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\PopoverMenu;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItem;
|
||||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
|
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem\PopoverMenuItemFactory;
|
||||||
use Dict;
|
use Dict;
|
||||||
use JSPopupMenuItem;
|
use JSPopupMenuItem;
|
||||||
@@ -56,31 +57,69 @@ class PopoverMenuFactory
|
|||||||
->SetHorizontalPosition(PopoverMenu::ENUM_HORIZONTAL_POSITION_ALIGN_OUTER_RIGHT)
|
->SetHorizontalPosition(PopoverMenu::ENUM_HORIZONTAL_POSITION_ALIGN_OUTER_RIGHT)
|
||||||
->SetVerticalPosition(PopoverMenu::ENUM_VERTICAL_POSITION_ABOVE);
|
->SetVerticalPosition(PopoverMenu::ENUM_VERTICAL_POSITION_ABOVE);
|
||||||
|
|
||||||
|
$aUserMenuItems = [];
|
||||||
|
|
||||||
// Allowed portals
|
// Allowed portals
|
||||||
$aAllowedPortalsItems = static::PrepareAllowedPortalsItemsForUserMenu();
|
$aAllowedPortalsItems = static::PrepareAllowedPortalsItemsForUserMenu();
|
||||||
if (!empty($aAllowedPortalsItems)) {
|
self::AddPopoverMenuItems($aAllowedPortalsItems, $aUserMenuItems);
|
||||||
$oMenu->AddSection('allowed_portals')
|
|
||||||
->SetItems('allowed_portals', $aAllowedPortalsItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
// User related pages
|
// User related pages
|
||||||
$oMenu->AddSection('user_related')
|
self::AddPopoverMenuItems(static::PrepareUserRelatedItemsForUserMenu(), $aUserMenuItems);
|
||||||
->SetItems('user_related', static::PrepareUserRelatedItemsForUserMenu());
|
|
||||||
|
|
||||||
// API: iPopupMenuExtension::MENU_USER_ACTIONS
|
// API: iPopupMenuExtension::MENU_USER_ACTIONS
|
||||||
$aAPIItems = static::PrepareAPIItemsForUserMenu($oMenu);
|
$aAPIItems = static::PrepareAPIItemsForUserMenu($oMenu);
|
||||||
if (count($aAPIItems) > 0) {
|
self::AddPopoverMenuItems($aAPIItems, $aUserMenuItems);
|
||||||
$oMenu->AddSection('popup_menu_extension-menu_user_actions')
|
|
||||||
->SetItems('popup_menu_extension-menu_user_actions', $aAPIItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Misc links
|
// Misc links
|
||||||
$oMenu->AddSection('misc')
|
/*$oMenu->AddSection('misc')
|
||||||
->SetItems('misc', static::PrepareMiscItemsForUserMenu());
|
->SetItems('misc', static::PrepareMiscItemsForUserMenu());*/
|
||||||
|
self::AddPopoverMenuItems(static::PrepareMiscItemsForUserMenu(), $aUserMenuItems);
|
||||||
|
|
||||||
|
self::SortPopoverMenuItems($aUserMenuItems);
|
||||||
|
|
||||||
|
$oMenu->AddSection('misc')
|
||||||
|
->AddItems('misc', $aUserMenuItems);
|
||||||
return $oMenu;
|
return $oMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PopoverMenuItem[] $aPopoverMenuItem
|
||||||
|
* @param PopoverMenuItem[] $aUserMenuItems
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function AddPopoverMenuItems(array $aPopoverMenuItem, array &$aUserMenuItems) : void {
|
||||||
|
foreach ($aPopoverMenuItem as $oPopoverMenuItem){
|
||||||
|
$aUserMenuItems[$oPopoverMenuItem->GetUID()] = $oPopoverMenuItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PopoverMenuItem[] $aPopoverMenuItem
|
||||||
|
* @param PopoverMenuItem[] $aUserMenuItems
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private static function SortPopoverMenuItems(array &$aUserMenuItems) : void {
|
||||||
|
$aSortedMenusFromConfig = MetaModel::GetConfig()->Get('navigation_menu.sorted_popup_user_menu_items');
|
||||||
|
if (!is_array($aSortedMenusFromConfig) || empty($aSortedMenusFromConfig)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aSortedMenus = [];
|
||||||
|
foreach ($aSortedMenusFromConfig as $sMenuUID){
|
||||||
|
if (array_key_exists($sMenuUID, $aUserMenuItems)){
|
||||||
|
$aSortedMenus[]=$aUserMenuItems[$sMenuUID];
|
||||||
|
unset($aUserMenuItems[$sMenuUID]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($aUserMenuItems as $oMenu){
|
||||||
|
$aSortedMenus[]=$oMenu;
|
||||||
|
}
|
||||||
|
$aUserMenuItems = $aSortedMenus;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the allowed portals items for the current user
|
* Return the allowed portals items for the current user
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ use MetaModel;
|
|||||||
use UIExtKeyWidget;
|
use UIExtKeyWidget;
|
||||||
use UserRights;
|
use UserRights;
|
||||||
use utils;
|
use utils;
|
||||||
|
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\NewsroomMenu\NewsroomMenuFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class NavigationMenu
|
* Class NavigationMenu
|
||||||
@@ -274,7 +275,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
|||||||
*/
|
*/
|
||||||
public function IsNewsroomEnabled(): bool
|
public function IsNewsroomEnabled(): bool
|
||||||
{
|
{
|
||||||
return MetaModel::GetConfig()->Get('newsroom_enabled');
|
return (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class NavigationMenuFactory
|
|||||||
{
|
{
|
||||||
|
|
||||||
$oNewsroomMenu = null;
|
$oNewsroomMenu = null;
|
||||||
if (MetaModel::GetConfig()->Get('newsroom_enabled'))
|
if (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders())
|
||||||
{
|
{
|
||||||
$oNewsroomMenu = NewsroomMenuFactory::MakeNewsroomMenuForNavigationMenu();
|
$oNewsroomMenu = NewsroomMenuFactory::MakeNewsroomMenuForNavigationMenu();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,13 +242,6 @@ JS
|
|||||||
// TODO 3.0.0: Reuse theming mechanism for Full Moon
|
// TODO 3.0.0: Reuse theming mechanism for Full Moon
|
||||||
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
|
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
|
||||||
$this->add_linked_stylesheet($sCssThemeUrl);
|
$this->add_linked_stylesheet($sCssThemeUrl);
|
||||||
|
|
||||||
$sCssRelPath = utils::GetCSSFromSASS(
|
|
||||||
'css/backoffice/main.scss',
|
|
||||||
array(
|
|
||||||
APPROOT.'css/backoffice/',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function GetReadyScriptsStartedTrigger(): ?string
|
protected function GetReadyScriptsStartedTrigger(): ?string
|
||||||
|
|||||||
27
sources/Application/WelcomePopup/DefaultWelcomePopup.php
Normal file
27
sources/Application/WelcomePopup/DefaultWelcomePopup.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
namespace Combodo\iTop\Application\WelcomePopup;
|
||||||
|
|
||||||
|
use Dict;
|
||||||
|
use AbstractWelcomePopup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the "default" Welcome Popup message
|
||||||
|
* @since 3.1.0
|
||||||
|
*/
|
||||||
|
class DefaultWelcomePopup extends AbstractWelcomePopup
|
||||||
|
{
|
||||||
|
public function GetMessages()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
// Replacement of the welcome popup message which
|
||||||
|
// was hard-coded in the pages/UI.php
|
||||||
|
'id' => '0001',
|
||||||
|
'title' => Dict::S('UI:WelcomeMenu:Title'),
|
||||||
|
'twig' => '/templates/pages/backoffice/welcome_popup/default_welcome_popup',
|
||||||
|
'importance' => \iWelcomePopup::IMPORTANCE_HIGH,
|
||||||
|
'parameters' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
234
sources/Application/WelcomePopup/WelcomePopupService.php
Normal file
234
sources/Application/WelcomePopup/WelcomePopupService.php
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
namespace Combodo\iTop\Application\WelcomePopup;
|
||||||
|
use AttributeDateTime;
|
||||||
|
use DBObjectSearch;
|
||||||
|
use DBObjectSet;
|
||||||
|
use Exception;
|
||||||
|
use IssueLog;
|
||||||
|
use LogChannels;
|
||||||
|
use MetaModel;
|
||||||
|
use UserRights;
|
||||||
|
use WelcomePopupAcknowledge;
|
||||||
|
use iWelcomePopup;
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling of the messages displayed in the "Welcome Popup"
|
||||||
|
* @since 3.1.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WelcomePopupService
|
||||||
|
{
|
||||||
|
private const PROVIDER_KEY_LENGTH = 128;
|
||||||
|
/**
|
||||||
|
* Array of acknowledged messages for the current user
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
static $aAcknowledgedMessage = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of "providers" of welcome popup messages
|
||||||
|
* @var iWelcomePopup[]
|
||||||
|
*/
|
||||||
|
protected $aMessagesProviders = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of messages to display in the Welcome popup dialog
|
||||||
|
* @return string[][]
|
||||||
|
*/
|
||||||
|
public function GetMessages()
|
||||||
|
{
|
||||||
|
$this->LoadProviders();
|
||||||
|
return $this->ProcessMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the messages to display from a list of iWelcomePopup instances
|
||||||
|
* The messages are ordered by importance (CRITICAL first) then by ID
|
||||||
|
* Invalid messages or acknowledged messages are removed from the list
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function ProcessMessages(): array
|
||||||
|
{
|
||||||
|
$this->LoadProviders();
|
||||||
|
$aMessages = [];
|
||||||
|
foreach($this->aMessagesProviders as $oProvider) {
|
||||||
|
$aProviderMessages = $oProvider->GetMessages();
|
||||||
|
if (count($aProviderMessages) === 0) {
|
||||||
|
IssueLog::Debug('Empty list of messages for '.get_class($oProvider), LogChannels::CONSOLE);
|
||||||
|
}
|
||||||
|
foreach($aProviderMessages as $aMessage) {
|
||||||
|
$aReasons = [];
|
||||||
|
if (!$this->IsMessageValid($aMessage, $aReasons)) {
|
||||||
|
IssueLog::Error('Invalid structure returned by '.get_class($oProvider).'::GetMessages()', LogChannels::CONSOLE, $aReasons);
|
||||||
|
continue; // Fail silently
|
||||||
|
}
|
||||||
|
$sUUID = $this->MakeStringFitIn(get_class($oProvider), static::PROVIDER_KEY_LENGTH).'::'.$aMessage['id'];
|
||||||
|
$aMessage['uuid'] = $sUUID;
|
||||||
|
$aMessages[] = $aMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Filter the acknowledged messages AFTER getting all messages
|
||||||
|
// This allows for "replacing" a message (from another provider for example)
|
||||||
|
// by automatically acknowledging it when called in GetMessages()
|
||||||
|
foreach($aMessages as $key => $aMessage) {
|
||||||
|
if ($this->IsMessageAcknowledged($aMessage['uuid'])) {
|
||||||
|
IssueLog::Debug('Ignoring already acknowledged message '.$aMessage['uuid'], LogChannels::CONSOLE);
|
||||||
|
unset($aMessages[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usort($aMessages, array(get_class($this), 'SortOnImportance'));
|
||||||
|
return $aMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for usort to compare two items based on their 'importance' field
|
||||||
|
* @param string[] $aItem1
|
||||||
|
* @param string[] $aItem2
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function SortOnImportance($aItem1, $aItem2): int
|
||||||
|
{
|
||||||
|
if ($aItem1['importance'] === $aItem2['importance']) {
|
||||||
|
return strcmp($aItem1['id'], $aItem2['id']);
|
||||||
|
}
|
||||||
|
return ($aItem1['importance'] < $aItem2['importance']) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AcknowledgeMessage(string $sMessageUUID): void
|
||||||
|
{
|
||||||
|
$this->LoadProviders();
|
||||||
|
$oAcknowledge = MetaModel::NewObject(WelcomePopupAcknowledge::class, [
|
||||||
|
'message_uuid' => $sMessageUUID,
|
||||||
|
'acknowledge_date' => date(AttributeDateTime::GetSQLFormat()),
|
||||||
|
'user_id' => UserRights::GetConnectedUserId(),
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$oAcknowledge->DBInsert();
|
||||||
|
$oProvider = $this->GetProviderByUUID($sMessageUUID);
|
||||||
|
if (static::$aAcknowledgedMessage !== null) {
|
||||||
|
static::$aAcknowledgedMessage[] = $sMessageUUID; // Update the cache
|
||||||
|
}
|
||||||
|
// Notify the provider of the message
|
||||||
|
$sMessageId = substr($sMessageUUID, strpos($sMessageUUID, '::')+2);
|
||||||
|
if ($oProvider !== null) {
|
||||||
|
$oProvider->AcknowledgeMessage($sMessageId);
|
||||||
|
}
|
||||||
|
} catch(Exception $e) {
|
||||||
|
IssueLog::Error("Failed to acknowledge the message $sMessageUUID for user ".UserRights::GetConnectedUserId().". Reason: ".$e->getMessage(), LogChannels::CONSOLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the provider of messages, decoupled from the constructor for testability
|
||||||
|
*/
|
||||||
|
protected function LoadProviders(): void
|
||||||
|
{
|
||||||
|
if ($this->aMessagesProviders !== null) return;
|
||||||
|
|
||||||
|
$aProviders = [];
|
||||||
|
$aProviderClasses = utils::GetClassesForInterface(iWelcomePopup::class, '', array('[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]', '[\\\\/]tests[\\\\/]'));
|
||||||
|
foreach($aProviderClasses as $sProviderClass) {
|
||||||
|
$aProviders[] = new $sProviderClass();
|
||||||
|
}
|
||||||
|
$this->SetMessagesProviders($aProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given message was acknowledged by the current user
|
||||||
|
* @param string $sMessageId
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function IsMessageAcknowledged(string $sMessageUUID): bool
|
||||||
|
{
|
||||||
|
$iUserId = UserRights::GetConnectedUserId();
|
||||||
|
if (static::$aAcknowledgedMessage === null) {
|
||||||
|
|
||||||
|
$oSearch = new DBObjectSearch(WelcomePopupAcknowledge::class);
|
||||||
|
$oSearch->AddCondition('user_id', $iUserId);
|
||||||
|
$oSet = new DBObjectSet($oSearch);
|
||||||
|
$aAcknowledgedMessages = $oSet->GetColumnAsArray('message_uuid');
|
||||||
|
$this->SetAcknowledgedMessagesCache($aAcknowledgedMessages);
|
||||||
|
}
|
||||||
|
return in_array($sMessageUUID, static::$aAcknowledgedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cache of acknowledged messages (useful for testing)
|
||||||
|
* @param array $aAcknowledgedMessages
|
||||||
|
*/
|
||||||
|
protected function SetAcknowledgedMessagesCache(array $aAcknowledgedMessages): void
|
||||||
|
{
|
||||||
|
static::$aAcknowledgedMessage = $aAcknowledgedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cache of welcome popup message providers (useful for testing)
|
||||||
|
* @param iWelcomePopup[] $aMessagesProviders
|
||||||
|
*/
|
||||||
|
protected function SetMessagesProviders(array $aMessagesProviders): void
|
||||||
|
{
|
||||||
|
$this->aMessagesProviders = $aMessagesProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the provider associated with a message
|
||||||
|
* @param string $sMessageUUID
|
||||||
|
* @return iWelcomePopup|NULL
|
||||||
|
*/
|
||||||
|
protected function GetProviderByUUID(string $sMessageUUID): ?iWelcomePopup
|
||||||
|
{
|
||||||
|
$this->LoadProviders();
|
||||||
|
$sProviderKey = substr($sMessageUUID, 0, strpos($sMessageUUID, '::'));
|
||||||
|
foreach($this->aMessagesProviders as $oProvider) {
|
||||||
|
if ($this->MakeStringFitIn(get_class($oProvider), static::PROVIDER_KEY_LENGTH) === $sProviderKey) {
|
||||||
|
return $oProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the structure of a given message is valid by checking
|
||||||
|
* all its mandatory elements
|
||||||
|
* @param string[] $aMessage
|
||||||
|
* @param string[] $aReasons
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function IsMessageValid($aMessage, array &$aReasons): bool
|
||||||
|
{
|
||||||
|
if (!is_array($aMessage)) {
|
||||||
|
$aReasons[] = 'GetMessage() must return an array of arrays.';
|
||||||
|
return false; // Stop checking immediately
|
||||||
|
}
|
||||||
|
$bRet = true;
|
||||||
|
foreach(['id', 'importance', 'title'] as $sKey) {
|
||||||
|
if (!array_key_exists($sKey, $aMessage)) {
|
||||||
|
$aReasons[] = "Field '$sKey' missing from the message structure.";
|
||||||
|
$bRet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!array_key_exists('html', $aMessage) && !array_key_exists('twig', $aMessage)) {
|
||||||
|
$aReasons[] = "Message structure must contain either a field 'html' or a field 'twig'.";
|
||||||
|
$bRet = false;
|
||||||
|
}
|
||||||
|
return $bRet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorten the given string (if needed) but preserving its uniqueness
|
||||||
|
* @param string $sProviderClass
|
||||||
|
* @param int $iLengthLimit
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function MakeStringFitIn(string $sProviderClass, int $iLengthLimit): string
|
||||||
|
{
|
||||||
|
if(mb_strlen($sProviderClass) <= $iLengthLimit) {
|
||||||
|
return $sProviderClass;
|
||||||
|
}
|
||||||
|
// Truncate the string to $iLimitLength and replace the first carahcters with the MD5 of the complete string
|
||||||
|
$sMD5 = md5($sProviderClass, false);
|
||||||
|
return $sMD5.'-'.mb_substr($sProviderClass, -($iLengthLimit - strlen($sMD5) - 1)); // strlen is OK on the MD5 string, and '-' is not allowed in a class name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
24
sources/Controller/WelcomePopupController.php
Normal file
24
sources/Controller/WelcomePopupController.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
namespace Combodo\iTop\Controller;
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple controller to acknowledge (via Ajax) welcome popup messages
|
||||||
|
* @since 3.1.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class WelcomePopupController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Operation: welcome_popup.acknowledge_message
|
||||||
|
*/
|
||||||
|
public function AcknowledgeMessage(): void
|
||||||
|
{
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
$sMessageUUID = utils::ReadPostedParam('message_uuid', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
||||||
|
$oService->AcknowledgeMessage($sMessageUUID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -145,8 +145,8 @@ final class EventService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$oLastException = null;
|
$oFirstException = null;
|
||||||
$sLastExceptionMessage = null;
|
$sFirstExceptionMessage = null;
|
||||||
$bEventFired = false;
|
$bEventFired = false;
|
||||||
foreach (self::GetListeners($sEvent, $eventSource) as $aEventCallback) {
|
foreach (self::GetListeners($sEvent, $eventSource) as $aEventCallback) {
|
||||||
if (!self::MatchContext($aEventCallback['context'])) {
|
if (!self::MatchContext($aEventCallback['context'])) {
|
||||||
@@ -164,9 +164,12 @@ final class EventService
|
|||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
catch (Exception $e) {
|
catch (Exception $e) {
|
||||||
$sLastExceptionMessage = "Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with non-blocking error: ".$e->getMessage();
|
$sExceptionMessage = "Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with non-blocking error: ".$e->getMessage();
|
||||||
EventServiceLog::Error($sLastExceptionMessage);
|
EventServiceLog::Error($sExceptionMessage);
|
||||||
$oLastException = $e;
|
if (is_null($oFirstException)){
|
||||||
|
$oFirstException = $e;
|
||||||
|
$sFirstExceptionMessage = $sExceptionMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($bEventFired) {
|
if ($bEventFired) {
|
||||||
@@ -174,9 +177,9 @@ final class EventService
|
|||||||
}
|
}
|
||||||
$oKPI->ComputeStats('FireEvent', $sEvent);
|
$oKPI->ComputeStats('FireEvent', $sEvent);
|
||||||
|
|
||||||
if (!is_null($oLastException)) {
|
if (!is_null($oFirstException)) {
|
||||||
EventServiceLog::Error("Throwing the last exception caught: $sLastExceptionMessage");
|
EventServiceLog::Error("Throwing the last exception caught: $sFirstExceptionMessage");
|
||||||
throw $oLastException;
|
throw $oFirstException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
sources/SessionTracker/SessionGC.php
Normal file
32
sources/SessionTracker/SessionGC.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\SessionTracker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SessionGC
|
||||||
|
*
|
||||||
|
* @author Olivier Dain <olivier.dain@combodo.com>
|
||||||
|
* @package Combodo\iTop\SessionTracker
|
||||||
|
* @since 3.1.1 3.2.0 N°6901
|
||||||
|
*/
|
||||||
|
class SessionGC implements \iBackgroundProcess
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function GetPeriodicity()
|
||||||
|
{
|
||||||
|
return 60 * 1; // seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function Process($iTimeLimit)
|
||||||
|
{
|
||||||
|
$iMaxLifetime = ini_get('session.gc_maxlifetime') ?? 1440;
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
$iProcessed = $oSessionHandler->gc_with_time_limit($iMaxLifetime, $iTimeLimit);
|
||||||
|
return "processed $iProcessed tasks";
|
||||||
|
}
|
||||||
|
}
|
||||||
240
sources/SessionTracker/SessionHandler.php
Normal file
240
sources/SessionTracker/SessionHandler.php
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\SessionTracker;
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\Helper\Session;
|
||||||
|
use ContextTag;
|
||||||
|
use Exception;
|
||||||
|
use IssueLog;
|
||||||
|
use ReturnTypeWillChange;
|
||||||
|
use UserRights;
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SessionHandler:
|
||||||
|
* defaut PHP SessionHandler already relies on files that are accessible by iTop.
|
||||||
|
* this new iTop SessionHandler creates additional session files that are located under iTop folders.
|
||||||
|
* these new session files are meant to monitor the application and contain additional data:
|
||||||
|
* - current user id
|
||||||
|
* - context
|
||||||
|
* - login_mode
|
||||||
|
* - session creation timestamp
|
||||||
|
*
|
||||||
|
* @author Olivier Dain <olivier.dain@combodo.com>
|
||||||
|
* @package Combodo\iTop\SessionTracker
|
||||||
|
* @since 3.1.1 3.2.0 N°6901
|
||||||
|
*/
|
||||||
|
class SessionHandler extends \SessionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $session_id
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
#[ReturnTypeWillChange]
|
||||||
|
public function destroy($session_id) : bool
|
||||||
|
{
|
||||||
|
IssueLog::Debug("Destroy PHP session", \LogChannels::SESSIONTRACKER, [
|
||||||
|
'session_id' => $session_id,
|
||||||
|
]);
|
||||||
|
$bRes = parent::destroy($session_id);
|
||||||
|
|
||||||
|
if ($bRes) {
|
||||||
|
$this->unlink_session_file($session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $max_lifetime
|
||||||
|
*/
|
||||||
|
#[ReturnTypeWillChange]
|
||||||
|
public function gc($max_lifetime) : bool
|
||||||
|
{
|
||||||
|
IssueLog::Debug("Run PHP sessions garbage collector", \LogChannels::SESSIONTRACKER, [
|
||||||
|
'max_lifetime' => $max_lifetime,
|
||||||
|
]);
|
||||||
|
$iRes = parent::gc($max_lifetime);
|
||||||
|
$this->gc_with_time_limit($max_lifetime);
|
||||||
|
return $iRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $save_path
|
||||||
|
* @param string $session_name
|
||||||
|
*/
|
||||||
|
#[ReturnTypeWillChange]
|
||||||
|
public function open($save_path, $session_name) : bool
|
||||||
|
{
|
||||||
|
$bRes = parent::open($save_path, $session_name);
|
||||||
|
|
||||||
|
$session_id = session_id();
|
||||||
|
IssueLog::Debug("Open PHP session", \LogChannels::SESSIONTRACKER, [
|
||||||
|
'session_id' => $session_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($bRes) {
|
||||||
|
$this->touch_session_file($session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $session_id
|
||||||
|
* @param string $data
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
#[ReturnTypeWillChange]
|
||||||
|
public function write($session_id, $data) : bool
|
||||||
|
{
|
||||||
|
$bRes = parent::write($session_id, $data);
|
||||||
|
|
||||||
|
IssueLog::Debug("Write PHP session", \LogChannels::SESSIONTRACKER, [
|
||||||
|
'session_id' => $session_id,
|
||||||
|
'data' => $data,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($bRes) {
|
||||||
|
$this->touch_session_file($session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function session_set_save_handler() : void
|
||||||
|
{
|
||||||
|
if (false === utils::GetConfig()->Get('sessions_tracking.enabled')){
|
||||||
|
//feature disabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionhandler = new SessionHandler();
|
||||||
|
session_set_save_handler($sessionhandler, true);
|
||||||
|
|
||||||
|
$iThreshold = utils::GetConfig()->Get('sessions_tracking.gc_threshold');
|
||||||
|
$iThreshold = min(100, $iThreshold);
|
||||||
|
$iThreshold = max(1, $iThreshold);
|
||||||
|
if ((100 != $iThreshold) && (rand(1, 100) > $iThreshold)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iMaxLifetime = ini_get('session.gc_maxlifetime') ?? 60;
|
||||||
|
$iMaxDurationInSeconds = utils::GetConfig()->Get('sessions_tracking.gc_duration_in_seconds');
|
||||||
|
$sessionhandler->gc_with_time_limit($iMaxLifetime, time() + $iMaxDurationInSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generate_session_content(?string $sPreviousFileVersionContent) : ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$sUserId = UserRights::GetUserId();
|
||||||
|
if (null === $sUserId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default value in case of
|
||||||
|
// - First time file creation
|
||||||
|
// - Data corruption (not a json / not an array / no previous creation_time key)
|
||||||
|
$iCreationTime = time();
|
||||||
|
|
||||||
|
if (! is_null($sPreviousFileVersionContent)) {
|
||||||
|
$aJson = json_decode($sPreviousFileVersionContent, true);
|
||||||
|
if (is_array($aJson) && array_key_exists('creation_time', $aJson)) {
|
||||||
|
$iCreationTime = $aJson['creation_time'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode (
|
||||||
|
[
|
||||||
|
'login_mode' => Session::Get('login_mode'),
|
||||||
|
'user_id' => $sUserId,
|
||||||
|
'creation_time' => $iCreationTime,
|
||||||
|
'context' => implode('|', ContextTag::GetStack())
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} catch(Exception $e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_file_path($session_id) : string
|
||||||
|
{
|
||||||
|
return utils::GetDataPath() . "sessions/session_$session_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function touch_session_file($session_id) : ?string
|
||||||
|
{
|
||||||
|
if (strlen($session_id) == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearstatcache();
|
||||||
|
if (! is_dir(utils::GetDataPath() . "sessions")) {
|
||||||
|
@mkdir(utils::GetDataPath() . "sessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sFilePath = $this->get_file_path($session_id);
|
||||||
|
|
||||||
|
$sPreviousFileVersionContent = null;
|
||||||
|
if (is_file($sFilePath)) {
|
||||||
|
$sPreviousFileVersionContent = file_get_contents($sFilePath);
|
||||||
|
}
|
||||||
|
$sNewContent = $this->generate_session_content($sPreviousFileVersionContent);
|
||||||
|
if (is_null($sNewContent) || ($sPreviousFileVersionContent === $sNewContent)) {
|
||||||
|
@touch($sFilePath);
|
||||||
|
} else {
|
||||||
|
file_put_contents($sFilePath, $sNewContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function unlink_session_file($session_id)
|
||||||
|
{
|
||||||
|
$sFilePath = $this->get_file_path($session_id);
|
||||||
|
if (is_file($sFilePath)) {
|
||||||
|
@unlink($sFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $max_lifetime
|
||||||
|
* @param int $iTimeLimit Unix timestamp of time limit not to exceed. -1 for no limit.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function gc_with_time_limit(int $max_lifetime, int $iTimeLimit = -1) : int
|
||||||
|
{
|
||||||
|
$aFiles = $this->list_session_files();
|
||||||
|
$iProcessed = 0;
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
foreach ($aFiles as $sFile) {
|
||||||
|
if ($now - filemtime($sFile) > $max_lifetime) {
|
||||||
|
@unlink($sFile);
|
||||||
|
$iProcessed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-1 !== $iTimeLimit && time() > $iTimeLimit) {
|
||||||
|
//cleanup processing has to stop after $iTimeLimit
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $iProcessed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list_session_files() : array
|
||||||
|
{
|
||||||
|
clearstatcache();
|
||||||
|
if (! is_dir(utils::GetDataPath() . "sessions")) {
|
||||||
|
@mkdir(utils::GetDataPath() . "sessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
return glob(utils::GetDataPath() . "sessions/session_*");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="ibo-welcome-popup--columns">
|
||||||
|
<div class="ibo-welcome-popup--image ibo-svg-illustration--container">
|
||||||
|
{{ source("images/illustrations/undraw_relaunch_day.svg") }}
|
||||||
|
</div>
|
||||||
|
<div class="ibo-welcome-popup--text">
|
||||||
|
<div>
|
||||||
|
{{ 'UI:WelcomeMenu:Text'| dict_s|raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,14 +1,25 @@
|
|||||||
<div id="welcome_popup">
|
<div id="welcome_popup_dialog" class="ibo-welcome-popup--dialog ibo-is-hidden">
|
||||||
<div class="ibo-welcome-popup--image ibo-svg-illustration--container">
|
<div class="ibo-welcome-popup--content">
|
||||||
{{ source("images/illustrations/undraw_relaunch_day.svg") }}
|
{% for message in messages %}
|
||||||
</div>
|
<div class="ibo-welcome-popup--message {% if not loop.first %}ibo-is-hidden{% endif %}" data-message-uuid="{{ message.uuid }}" data_role="welcome-popup-title" data-title="{{ message.title }}">
|
||||||
<div class="ibo-welcome-popup--text">
|
{% if message.twig is defined %}
|
||||||
<div>
|
{{ include([message.twig ~ '.html.twig', message.twig ~ '.twig', message.twig], message.parameters ?? {}, sandboxed = true) }}
|
||||||
{{ 'UI:WelcomeMenu:Text'| dict_s|raw }}
|
{% else %}
|
||||||
</div>
|
{{ message.html|raw }}
|
||||||
<div class="ibo-welcome-popup--text--options">
|
{% endif %}
|
||||||
<input type="checkbox" checked id="display_welcome_popup"/><label for="display_welcome_popup">{{'UI:DisplayThisMessageAtStartup'| dict_s}}</label>
|
<div class="ibo-welcome-popup--button" data-message-uuid="{{ message.uuid }}">
|
||||||
</div>
|
{% UIButton ForPrimaryAction{'sLabel':'UI:WelcomePopup:Button:Acknowledge'|dict_s, 'bIsSubmit': false } %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ibo-welcome-popup--indicators">
|
||||||
|
{% if messages|length > 1 %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<span class="ibo-welcome-popup--indicator {% if loop.first %}ibo-welcome-popup--active{% endif %}" data-message-uuid="{{ message.uuid }}"></span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,38 @@
|
|||||||
$('#welcome_popup').dialog( { width:'60%', height: 'auto', title: '{{ 'UI:WelcomeMenu:Title'|dict_s }}', autoOpen: true, modal:true,
|
$('#welcome_popup_dialog').removeClass('ibo-is-hidden');
|
||||||
close: function() {
|
$('#welcome_popup_dialog').dialog({
|
||||||
var bDisplay = $('#display_welcome_popup:checked').length;
|
modal: true,
|
||||||
SetUserPreference('welcome_popup', bDisplay, true);
|
width: '60%',
|
||||||
},
|
autoOpen: true,
|
||||||
buttons: [{
|
title: $('div[data_role=welcome-popup-title]').first().attr('data-title'),
|
||||||
text: "{{ 'UI:Button:Ok'|dict_s }}", click: function() {
|
close: function() { $('#welcome_popup_dialog').remove(); }
|
||||||
$(this).dialog( "close" ); $(this).remove();
|
});
|
||||||
}}],
|
$('.ui-widget-overlay').click(function() { $('#welcome_popup_dialog').dialog('close'); } );
|
||||||
|
$('.ibo-welcome-popup--indicator').click(function() {
|
||||||
|
const id = $(this).attr('data-message-uuid');
|
||||||
|
const escaped_id = id.replace(/\\/g, '\\\\'); // All backslashes must be doubled in a jQuery selector
|
||||||
|
const new_title = $('.ibo-welcome-popup--message[data-message-uuid="'+escaped_id+'"]').attr('data-title');
|
||||||
|
$('.ibo-welcome-popup--message').addClass('ibo-is-hidden');
|
||||||
|
$('.ibo-welcome-popup--indicator').removeClass('ibo-welcome-popup--active');
|
||||||
|
$('.ibo-welcome-popup--message[data-message-uuid="'+escaped_id+'"]').removeClass('ibo-is-hidden');
|
||||||
|
$('.ibo-welcome-popup--indicator[data-message-uuid="'+escaped_id+'"]').addClass('ibo-welcome-popup--active');
|
||||||
|
$('#welcome_popup_dialog').dialog('option', 'title', new_title);
|
||||||
|
$('.ibo-welcome-popup--message[data-message-uuid="'+escaped_id+'"] button').focus();
|
||||||
|
});
|
||||||
|
$('.ibo-welcome-popup--button').click('button', function() {
|
||||||
|
const id = $(this).attr('data-message-uuid');
|
||||||
|
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'welcome_popup_acknowledge_message', message_uuid: id});
|
||||||
|
const escaped_id = id.replace(/\\/g, '\\\\');; // All backslashes must be doubled in a jQuery selector
|
||||||
|
$('.ibo-welcome-popup--message[data-message-uuid="'+escaped_id+'"]').remove();
|
||||||
|
if($('.ibo-welcome-popup--message').length == 0) {
|
||||||
|
// Last message, close the dialog
|
||||||
|
$('#welcome_popup_dialog').dialog('close');
|
||||||
|
} else {
|
||||||
|
// Move the active state to the next message
|
||||||
|
$('.ibo-welcome-popup--indicator[data-message-uuid="'+escaped_id+'"]').siblings().first().trigger('click');
|
||||||
|
$('.ibo-welcome-popup--indicator[data-message-uuid="'+escaped_id+'"]').remove();
|
||||||
|
if ($('.ibo-welcome-popup--indicator').length == 1) {
|
||||||
|
// Last indicator, remove it
|
||||||
|
$('.ibo-welcome-popup--indicator').remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if ($('#welcome_popup').height() > ($(window).height()-70))
|
|
||||||
{
|
|
||||||
$('#welcome_popup').height($(window).height()-70);
|
|
||||||
}
|
|
||||||
|
|||||||
49
tests/php-unit-tests/module_integration.xml.dist
Normal file
49
tests/php-unit-tests/module_integration.xml.dist
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
backupGlobals="true"
|
||||||
|
colors="true"
|
||||||
|
columns="120"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnError="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
stopOnIncomplete="false"
|
||||||
|
stopOnRisky="false"
|
||||||
|
stopOnSkipped="false"
|
||||||
|
verbose="true"
|
||||||
|
printerClass="\Sempro\PHPUnitPrettyPrinter\PrettyPrinterForPhpUnit9"
|
||||||
|
>
|
||||||
|
|
||||||
|
<extensions>
|
||||||
|
<extension class="Combodo\iTop\Test\UnitTest\Hook\TestsRunStartHook" />
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<php>
|
||||||
|
<ini name="error_reporting" value="E_ALL"/>
|
||||||
|
<ini name="display_errors" value="On"/>
|
||||||
|
<ini name="log_errors" value="On"/>
|
||||||
|
<ini name="html_errors" value="Off"/>
|
||||||
|
<env name="PHPUNIT_PRETTY_PRINT_PROGRESS" value="true"/>
|
||||||
|
</php>
|
||||||
|
<!-- N°6949 - file dedicated to validate modules/extensions -->
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="ModuleIntegration">
|
||||||
|
<file>integration-tests/DictionariesConsistencyAfterSetupTest.php</file>
|
||||||
|
<file>integration-tests/DictionariesConsistencyTest.php</file>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<!-- Code coverage white list -->
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<file>../../core/apc-emulation.php</file>
|
||||||
|
<file>../../core/ormlinkset.class.inc.php</file>
|
||||||
|
<file>../../datamodels/2.x/itop-tickets/main.itop-tickets.php</file>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace UI\Base\Component\PopoverMenu;
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuFactory;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
class PopoverMenuFactoryTest extends ItopDataTestCase {
|
||||||
|
|
||||||
|
public function MakeUserMenuForNavigationMenuProvider(){
|
||||||
|
$aNotSortedMenuUIDs = [
|
||||||
|
'portal_itop_portal',
|
||||||
|
'UI_Preferences',
|
||||||
|
'UI_Help',
|
||||||
|
'UI_AboutBox'
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'no conf' => [
|
||||||
|
'aConf' => null,
|
||||||
|
'aExpectedMenuUIDs' => $aNotSortedMenuUIDs
|
||||||
|
],
|
||||||
|
'not an array conf' => [
|
||||||
|
'aConf' => "wrong conf",
|
||||||
|
'aExpectedMenuUIDs' => $aNotSortedMenuUIDs
|
||||||
|
],
|
||||||
|
'default conf' => [
|
||||||
|
'aConf' => [],
|
||||||
|
'aExpectedMenuUIDs' => $aNotSortedMenuUIDs
|
||||||
|
],
|
||||||
|
'same order in conf' => [
|
||||||
|
'aConf' => [
|
||||||
|
'portal:itop-portal',
|
||||||
|
'UI:Preferences',
|
||||||
|
'UI:Help',
|
||||||
|
'UI:AboutBox',
|
||||||
|
],
|
||||||
|
'aExpectedMenuUIDs' => $aNotSortedMenuUIDs
|
||||||
|
],
|
||||||
|
'first menus sorted and last one missing in conf' => [
|
||||||
|
'aConf' => [
|
||||||
|
"portal:itop-portal",
|
||||||
|
"UI:Preferences",
|
||||||
|
],
|
||||||
|
'aExpectedMenuUIDs' => $aNotSortedMenuUIDs
|
||||||
|
],
|
||||||
|
'some menus but not all sorted' => [
|
||||||
|
'aConf' => [
|
||||||
|
'UI:Preferences',
|
||||||
|
'UI:AboutBox',
|
||||||
|
],
|
||||||
|
'aExpectedMenuUIDs' => [
|
||||||
|
'UI_Preferences',
|
||||||
|
'UI_AboutBox',
|
||||||
|
'portal_itop_portal',
|
||||||
|
'UI_Help',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'all user menu sorted' => [
|
||||||
|
'aConf' => [
|
||||||
|
'UI:Preferences',
|
||||||
|
'UI:AboutBox',
|
||||||
|
'portal:itop-portal',
|
||||||
|
'UI:Help',
|
||||||
|
],
|
||||||
|
'aExpectedMenuUIDs' => [
|
||||||
|
'UI_Preferences',
|
||||||
|
'UI_AboutBox',
|
||||||
|
'portal_itop_portal',
|
||||||
|
'UI_Help',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dataProvider MakeUserMenuForNavigationMenuProvider
|
||||||
|
*/
|
||||||
|
public function testMakeUserMenuForNavigationMenu($aConf, $aExpectedMenuUIDs){
|
||||||
|
if (! is_null($aConf)){
|
||||||
|
\MetaModel::GetConfig()->Set('navigation_menu.sorted_popup_user_menu_items', $aConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aRes = PopoverMenuFactory::MakeUserMenuForNavigationMenu()->GetSections();
|
||||||
|
$this->assertTrue(array_key_exists('misc', $aRes));
|
||||||
|
$aUIDsWithDummyRandoString = array_keys($aRes['misc']['aItems']);
|
||||||
|
//replace ibo-popover-menu--item-6464cdca5ecf4214716943--UI_AboutBox by UI_AboutBox (for ex)
|
||||||
|
$aUIDs = preg_replace('/ibo-popover-menu--item-([^\-]+)--/', '', $aUIDsWithDummyRandoString);
|
||||||
|
$this->assertEquals($aExpectedMenuUIDs, $aUIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
class WelcomePopupTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider sortOnImportanceDataProvider
|
||||||
|
*/
|
||||||
|
public function testSortOnImportance($aToSort, $aExpected)
|
||||||
|
{
|
||||||
|
$bResult = usort($aToSort, [WelcomePopupService::class, 'SortOnImportance']);
|
||||||
|
$this->assertTrue($bResult);
|
||||||
|
$this->assertEquals($aExpected, $aToSort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data provider for testSortOnImportance
|
||||||
|
* @return array[][]|string[][][][]|number[][][][]
|
||||||
|
*/
|
||||||
|
public function sortOnImportanceDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'empty array' => [
|
||||||
|
'to-sort' => [],
|
||||||
|
'expected' => [],
|
||||||
|
],
|
||||||
|
'3-item array' => [
|
||||||
|
'to-sort' => [
|
||||||
|
['id' => 'aa1', 'title' => 'AA1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa2', 'title' => 'AA2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
['id' => 'aa3', 'title' => 'AA3', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
],
|
||||||
|
'expected' => [
|
||||||
|
['id' => 'aa1', 'title' => 'AA1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa3', 'title' => 'AA3', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa2', 'title' => 'AA2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'5-item array' => [
|
||||||
|
'to-sort' => [
|
||||||
|
['id' => 'aa1', 'title' => 'AA1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa2', 'title' => 'AA2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
['id' => 'aa3', 'title' => 'AA3', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'zz1', 'title' => 'ZZ1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'zz2', 'title' => 'ZZ2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
],
|
||||||
|
'expected' => [
|
||||||
|
['id' => 'aa1', 'title' => 'AA1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa3', 'title' => 'AA3', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'zz1', 'title' => 'ZZ1', 'importance' => 0 /*iWelcomePopup::IMPORTANCE_CRITICAL*/],
|
||||||
|
['id' => 'aa2', 'title' => 'AA2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
['id' => 'zz2', 'title' => 'ZZ2', 'importance' => 1 /*iWelcomePopup::IMPORTANCE_HIGH*/],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider isMessageAcknowledgedDataProvider
|
||||||
|
*/
|
||||||
|
public function testIsMessageAcknowledged($sMessageId, $aCache, $bExpected)
|
||||||
|
{
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
$this->InvokeNonPublicMethod(WelcomePopupService::class, 'SetAcknowledgedMessagesCache', $oService, [$aCache]);
|
||||||
|
|
||||||
|
$this->assertEquals($bExpected, $this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageId]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMessageAcknowledgedDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'empty-cache' => [
|
||||||
|
'123', [], false,
|
||||||
|
],
|
||||||
|
'acknowledged' => [
|
||||||
|
'123', ['123'], true,
|
||||||
|
],
|
||||||
|
'non-acknowledged' => [
|
||||||
|
'456', ['123'], false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider isMessageValidDataProvider
|
||||||
|
*/
|
||||||
|
public function testIsMessageValid($aMessage, $bExpected)
|
||||||
|
{
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
$aReasons = [];
|
||||||
|
$bResult = $this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageValid', $oService, [$aMessage, &$aReasons]);
|
||||||
|
if ($bResult !== $bExpected) {
|
||||||
|
print_r($aReasons);
|
||||||
|
}
|
||||||
|
$this->assertEquals($bExpected, $bResult);
|
||||||
|
if ($bResult) {
|
||||||
|
$this->assertEquals(0, count($aReasons));
|
||||||
|
} else {
|
||||||
|
$this->assertNotEquals(0, count($aReasons));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isMessageValidDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'not an array' => [
|
||||||
|
'123', false,
|
||||||
|
],
|
||||||
|
'empty array' => [
|
||||||
|
[], false,
|
||||||
|
],
|
||||||
|
'missing id' => [
|
||||||
|
['title' => 'foo', 'importance' => 0, 'html' => '<p>Hello</p>'], false,
|
||||||
|
],
|
||||||
|
'message Ok (html)' => [
|
||||||
|
['id' => '123', 'title' => 'foo', 'importance' => 0, 'html' => '<p>Hello</p>'], true,
|
||||||
|
],
|
||||||
|
'message Ok (twig)' => [
|
||||||
|
['id' => '123', 'title' => 'foo', 'importance' => 0, 'twig' => '/some/path'], true,
|
||||||
|
],
|
||||||
|
'missing html and twig' => [
|
||||||
|
['id' => '123', 'title' => 'foo', 'importance' => 0], false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessMessages()
|
||||||
|
{
|
||||||
|
// Mock a WelcomePopup message provider, with a fixed class name
|
||||||
|
$oProvider1 = $this->getMockBuilder(iWelcomePopup::class)->setMockClassName('Provider1')->getMock();
|
||||||
|
$oProvider1->expects($this->once())->method('GetMessages')->willReturn([
|
||||||
|
['id' => '123', 'title' => 'foo', 'importance' => 0, 'html' => '<p>Hello Foo</p>'],
|
||||||
|
['id' => '456', 'title' => 'bar', 'importance' => 1, 'html' => '<p>Hello Bar</p>'], // Already acknowledged will be skipped
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Mock another WelcomePopup message provider, with a different class name
|
||||||
|
$oProvider2 = $this->getMockBuilder(iWelcomePopup::class)->setMockClassName('Provider2')->getMock();
|
||||||
|
$oProvider2->expects($this->once())->method('GetMessages')->willReturn([
|
||||||
|
['id' => '789', 'title' => 'Ga', 'importance' => 1, 'html' => '<p>Hello Ga</p>'],
|
||||||
|
['id' => '012', 'title' => 'Bu', 'importance' => 0, 'twig' => 'ga/bu/zo'],
|
||||||
|
['id' => '000', 'title' => 'Bu', 'importance' => 0], // Invalid, will be ignored
|
||||||
|
]);
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
$this->InvokeNonPublicMethod(WelcomePopupService::class, 'SetAcknowledgedMessagesCache', $oService, [[get_class($oProvider1).'::456']]);
|
||||||
|
$this->InvokeNonPublicMethod(WelcomePopupService::class, 'SetMessagesProviders', $oService, [[$oProvider1, $oProvider2]]);
|
||||||
|
|
||||||
|
$aMessages = $this->InvokeNonPublicMethod(WelcomePopupService::class, 'ProcessMessages', $oService, []);
|
||||||
|
$this->assertEquals(
|
||||||
|
[
|
||||||
|
['id' => '012', 'title' => 'Bu', 'importance' => 0, 'twig' => 'ga/bu/zo', 'uuid' => 'Provider2::012'],
|
||||||
|
['id' => '123', 'title' => 'foo', 'importance' => 0, 'html' => '<p>Hello Foo</p>', 'uuid' => 'Provider1::123'],
|
||||||
|
['id' => '789', 'title' => 'Ga', 'importance' => 1, 'html' => '<p>Hello Ga</p>', 'uuid' => 'Provider2::789'],
|
||||||
|
],
|
||||||
|
$aMessages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testAcknowledgeMessage()
|
||||||
|
{
|
||||||
|
self::CreateUser('admin-testAcknowledgeMessage', 1, '-Passw0rd!Complex-');
|
||||||
|
UserRights::Login('admin-testAcknowledgeMessage');
|
||||||
|
|
||||||
|
// Mock a WelcomePopup message provider, with a fixed class name
|
||||||
|
$oProvider1 = $this->getMockBuilder(iWelcomePopup::class)->setMockClassName('Provider1')->getMock();
|
||||||
|
$oProvider1->expects($this->exactly(2))->method('AcknowledgeMessage');
|
||||||
|
|
||||||
|
// Mock another WelcomePopup message provider, with a different class name
|
||||||
|
$oProvider2 = $this->getMockBuilder(iWelcomePopup::class)->setMockClassName('Provider2')->getMock();
|
||||||
|
$oProvider2->expects($this->exactly(1))->method('AcknowledgeMessage');
|
||||||
|
|
||||||
|
$sMessageUUID1 = get_class($oProvider1).'::0123456';
|
||||||
|
$sMessageUUID2 = get_class($oProvider1).'::456789';
|
||||||
|
$sMessageUUID3 = get_class($oProvider2).'::456789'; // Same message id but different provider / UUID
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
|
||||||
|
$this->InvokeNonPublicMethod(WelcomePopupService::class, 'SetMessagesProviders', $oService, [[$oProvider1, $oProvider2]]);
|
||||||
|
|
||||||
|
$oService->AcknowledgeMessage($sMessageUUID1);
|
||||||
|
$this->assertTrue($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID1]));
|
||||||
|
$this->assertFalse($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, ['-This-Message-Id-Is-Not-Ack0ledg3dged!']));
|
||||||
|
$this->assertFalse($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID3]));
|
||||||
|
|
||||||
|
$oService->AcknowledgeMessage($sMessageUUID2);
|
||||||
|
$this->assertTrue($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID1]));
|
||||||
|
$this->assertTrue($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID2]));
|
||||||
|
$this->assertFalse($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, ['-This-Message-Id-Is-Not-Ack0ledg3dged!']));
|
||||||
|
$this->assertFalse($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID3]));
|
||||||
|
|
||||||
|
$oService->AcknowledgeMessage($sMessageUUID3);
|
||||||
|
$this->assertTrue($this->InvokeNonPublicMethod(WelcomePopupService::class, 'IsMessageAcknowledged', $oService, [$sMessageUUID3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider makeStringFitInProvider
|
||||||
|
*/
|
||||||
|
public function testMakeStringFitIn($sInput, $iLimit, $sExpected)
|
||||||
|
{
|
||||||
|
$oService = new WelcomePopupService();
|
||||||
|
$sFitted = $this->InvokeNonPublicMethod(WelcomePopupService::class, 'MakeStringFitIn', $oService, [$sInput, $iLimit]);
|
||||||
|
$this->assertTrue(mb_strlen($sFitted) <= $iLimit);
|
||||||
|
$this->assertEquals($sExpected, $sFitted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function makeStringFitInProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Simple (no truncation)' => ['/Some/Short/EnoughName', 50, '/Some/Short/EnoughName'],
|
||||||
|
'Very long (truncated)' => ['/Some/Very/Loooooooooooooooooooooooooooong/Naaaaaaaaaaaaaaaaaaaaaaaaaame', 50, '4769a98d57a0f2e9b99483f780833faf-aaaaaaaaaaaaaaame'],
|
||||||
|
'Long More aggressive truncation' => ['/Some/Very/Loooooooooooooooooooooooooooong/Naaaaaaaaaaaaaaaaaaaaaaaaaame', 45, '4769a98d57a0f2e9b99483f780833faf-aaaaaaaaaame'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||||
|
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use MetaModel;
|
||||||
|
use PHPUnit\Framework\ExpectationFailedException;
|
||||||
|
use UserRights;
|
||||||
|
use VariableExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MetaModelMagicPlaceholderTest
|
||||||
|
* @since 3.1.1 N°6824
|
||||||
|
* @covers MetaModel::AddMagicPlaceholders()
|
||||||
|
* @package Combodo\iTop\Test\UnitTest\Core
|
||||||
|
*/
|
||||||
|
class MetaModelMagicPlaceholderTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Asserts that two array with DBObjects are equal (the important is to check the {class,id} couple
|
||||||
|
*
|
||||||
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||||
|
* @throws ExpectationFailedException
|
||||||
|
*/
|
||||||
|
public static function assertEqualsShallow($expected, $actual, string $message = ''): void
|
||||||
|
{
|
||||||
|
if (is_array($expected)) {
|
||||||
|
foreach ($expected as $key => $value) {
|
||||||
|
if ($value instanceof \DBObject) {
|
||||||
|
$expected[$key] = get_class($value).'::'.$value->GetKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($actual as $key => $value) {
|
||||||
|
if ($value instanceof \DBObject) {
|
||||||
|
$actual[$key] = get_class($value).'::'.$value->GetKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parent::assertEquals($expected, $actual, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddMagicPlaceholdersWhenLoggedInUserHasAContact()
|
||||||
|
{
|
||||||
|
// Create data fixture => User + Person
|
||||||
|
$iNum = uniqid();
|
||||||
|
$sLogin = "AddMagicPlaceholders".$iNum;
|
||||||
|
$this->CreateTestOrganization();
|
||||||
|
$oPerson = $this->CreatePerson($iNum);
|
||||||
|
$sContactId = $oPerson->GetKey();
|
||||||
|
$oUser = $this->CreateUser($sLogin, 1, "Abcdef@12345678", $oPerson->GetKey());
|
||||||
|
UserRights::Login($sLogin);
|
||||||
|
|
||||||
|
// Test legacy behavior (no expected args)
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"]);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => $sContactId,
|
||||||
|
'current_user->object()' => $oUser,
|
||||||
|
'current_contact->object()' => $oPerson,
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders without second parameter (legacy) should add "curent_contact_id/current_user->object()/current_contact->object()"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// With expected arguments explicitly given as "none"
|
||||||
|
$aExpectedArgs = [];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add only expected arguments'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test new behavior (with expected args)
|
||||||
|
$aExpectedArgs = [
|
||||||
|
new VariableExpression('current_user->login'),
|
||||||
|
new VariableExpression('current_user->not_existing_attribute'),
|
||||||
|
new VariableExpression('current_contact_id'),
|
||||||
|
new VariableExpression('current_contact->org_id'),
|
||||||
|
new VariableExpression('current_contact->not_existing_attribute'),
|
||||||
|
];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => $sContactId,
|
||||||
|
'current_user->object()' => $oUser,
|
||||||
|
'current_user->not_existing_attribute' => '(current_user->not_existing_attribute : cannot be resolved)',
|
||||||
|
'current_user->login' => $sLogin,
|
||||||
|
'current_contact->object()' => $oPerson,
|
||||||
|
'current_contact->org_id' => $oPerson->Get('org_id'),
|
||||||
|
'current_contact->not_existing_attribute' => '(current_contact->not_existing_attribute : cannot be resolved)',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add expected arguments and render them with an explicit error when the information could not be known'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddMagicPlaceholdersWhenLoggedInUserHasNoContact()
|
||||||
|
{
|
||||||
|
// Create data fixture => User without contact
|
||||||
|
$iNum = uniqid();
|
||||||
|
$sLogin = "AddMagicPlaceholders".$iNum;
|
||||||
|
$oUser = $this->CreateContactlessUser($sLogin, 1, "Abcdef@12345678");
|
||||||
|
UserRights::Login($sLogin);
|
||||||
|
|
||||||
|
// Test legacy behavior (no expected args)
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"]);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => 0,
|
||||||
|
'current_user->object()' => $oUser,
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders without second parameter (legacy) should add "current_contact_id=0/current_user->object()"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with expected arguments explicitly given as "none"
|
||||||
|
$aExpectedArgs = [];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add only expected arguments'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with a few expected arguments, some of which being invalid attributes
|
||||||
|
$aExpectedArgs = [
|
||||||
|
new VariableExpression('current_user->login'),
|
||||||
|
new VariableExpression('current_user->not_existing_attribute'),
|
||||||
|
new VariableExpression('current_contact_id'),
|
||||||
|
new VariableExpression('current_contact->org_id'),
|
||||||
|
new VariableExpression('current_contact->not_existing_attribute'),
|
||||||
|
];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => 0,
|
||||||
|
'current_user->object()' => $oUser,
|
||||||
|
'current_user->not_existing_attribute' => '(current_user->not_existing_attribute : cannot be resolved)',
|
||||||
|
'current_user->login' => $sLogin,
|
||||||
|
'current_contact->object()' => '(current_contact->object() : cannot be resolved)',
|
||||||
|
'current_contact->org_id' => '(current_contact->org_id : cannot be resolved)',
|
||||||
|
'current_contact->not_existing_attribute' => '(current_contact->not_existing_attribute : cannot be resolved)',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add expected arguments and render them with an explicit error when the information could not be known'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAddMagicPlaceholdersWhenThereIsNoLoggedInUser()
|
||||||
|
{
|
||||||
|
// Test legacy behavior (no expected args)
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"]);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => '',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders without second parameter (legacy) should add "curent_contact_id"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with expected arguments explicitly given as "none"
|
||||||
|
$aExpectedArgs = [];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add only expected arguments'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test with a few expected arguments, some of which being invalid attributes
|
||||||
|
$aExpectedArgs = [
|
||||||
|
new VariableExpression('current_user->login'),
|
||||||
|
new VariableExpression('current_user->not_existing_attribute'),
|
||||||
|
new VariableExpression('current_contact_id'),
|
||||||
|
new VariableExpression('current_contact->org_id'),
|
||||||
|
new VariableExpression('current_contact->not_existing_attribute'),
|
||||||
|
];
|
||||||
|
$aPlaceholders = MetaModel::AddMagicPlaceholders(['gabu' => "zomeu"], $aExpectedArgs);
|
||||||
|
$this->assertEqualsShallow(
|
||||||
|
[
|
||||||
|
'gabu' => 'zomeu',
|
||||||
|
'current_contact_id' => '',
|
||||||
|
'current_user->object()' => '(current_user->object() : cannot be resolved)',
|
||||||
|
'current_user->not_existing_attribute' => '(current_user->not_existing_attribute : cannot be resolved)',
|
||||||
|
'current_user->login' => '(current_user->login : cannot be resolved)',
|
||||||
|
'current_contact->object()' => '(current_contact->object() : cannot be resolved)',
|
||||||
|
'current_contact->org_id' => '(current_contact->org_id : cannot be resolved)',
|
||||||
|
'current_contact->not_existing_attribute' => '(current_contact->not_existing_attribute : cannot be resolved)',
|
||||||
|
],
|
||||||
|
$aPlaceholders,
|
||||||
|
'AddMagicPlaceholders should add expected arguments and render them with an explicit error when the information could not be known'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,6 @@ use utils;
|
|||||||
/**
|
/**
|
||||||
* @group itopRequestMgmt
|
* @group itopRequestMgmt
|
||||||
* @group userRights
|
* @group userRights
|
||||||
* @group defaultProfiles
|
|
||||||
*/
|
*/
|
||||||
class UserRightsTest extends ItopDataTestCase
|
class UserRightsTest extends ItopDataTestCase
|
||||||
{
|
{
|
||||||
@@ -50,7 +49,7 @@ class UserRightsTest extends ItopDataTestCase
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
utils::GetConfig()->SetModuleSetting('authent-local', 'password_validation.pattern', '');
|
utils::GetConfig()->SetModuleSetting('authent-local', 'password_validation.pattern', '');
|
||||||
self::CreateUser('admin', 1);
|
self::CreateContactlessUser('admin', 1);
|
||||||
}
|
}
|
||||||
catch (CoreCannotSaveObjectException $e) {
|
catch (CoreCannotSaveObjectException $e) {
|
||||||
}
|
}
|
||||||
@@ -78,7 +77,7 @@ class UserRightsTest extends ItopDataTestCase
|
|||||||
$sLogin = $sLoginPrefix.$iCount;
|
$sLogin = $sLoginPrefix.$iCount;
|
||||||
$iCount++;
|
$iCount++;
|
||||||
|
|
||||||
$oUser = self::CreateUser($sLogin, $iProfileId);
|
$oUser = self::CreateContactlessUser($sLogin, $iProfileId);
|
||||||
$_SESSION = array();
|
$_SESSION = array();
|
||||||
UserRights::Login($sLogin);
|
UserRights::Login($sLogin);
|
||||||
return $oUser;
|
return $oUser;
|
||||||
@@ -494,7 +493,7 @@ class UserRightsTest extends ItopDataTestCase
|
|||||||
{
|
{
|
||||||
utils::GetConfig()->Set('security.hide_administrators', $bHideAdministrators);
|
utils::GetConfig()->Set('security.hide_administrators', $bHideAdministrators);
|
||||||
|
|
||||||
$oUserAdmin = $this->CreateUser('admin1', 1);
|
$oUserAdmin = $this->CreateContactlessUser('admin1', 1);
|
||||||
$this->CreateUniqueUserAndLogin('test1', 2); // portal user
|
$this->CreateUniqueUserAndLogin('test1', 2); // portal user
|
||||||
|
|
||||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||||
|
|||||||
@@ -0,0 +1,657 @@
|
|||||||
|
<?php
|
||||||
|
// Copyright (c) 2010-2023 Combodo SARL
|
||||||
|
//
|
||||||
|
// This file is part of iTop.
|
||||||
|
//
|
||||||
|
// iTop is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// iTop is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: Eric
|
||||||
|
* Date: 25/01/2018
|
||||||
|
* Time: 11:12
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Module\iTopProfilesItil;
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\Helper\Session;
|
||||||
|
use Combodo\iTop\Application\UI\Base\Layout\NavigationMenu\NavigationMenuFactory;
|
||||||
|
use Combodo\iTop\ItilProfiles\UserProfilesEventListener;
|
||||||
|
use Combodo\iTop\Service\Events\EventService;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use DBObjectSet;
|
||||||
|
use URP_UserProfile;
|
||||||
|
use UserRights;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5324
|
||||||
|
* @group itopRequestMgmt
|
||||||
|
* @group userRights
|
||||||
|
* @group defaultProfiles
|
||||||
|
*
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
class UserProfilesEventListenerTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
//reset conf to have nominal behaviour
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function PortaPowerUserProvider(){
|
||||||
|
return [
|
||||||
|
'Portal power user only => user should be repaired by adding User portal profile' => [
|
||||||
|
'aAssociatedProfilesBeforeUserCreation' => [
|
||||||
|
'Portal power user'
|
||||||
|
],
|
||||||
|
'aExpectedAssociatedProfilesAfterUserCreation'=> [
|
||||||
|
'Portal power user',
|
||||||
|
'Portal user',
|
||||||
|
],
|
||||||
|
'bCheckSessionMessage' => true
|
||||||
|
],
|
||||||
|
'Portal power user + Configuration Manager => profiles untouched' => [
|
||||||
|
'aAssociatedProfilesBeforeUserCreation' => [
|
||||||
|
'Portal power user',
|
||||||
|
'Configuration Manager',
|
||||||
|
],
|
||||||
|
'aExpectedAssociatedProfilesAfterUserCreation'=> [
|
||||||
|
'Portal power user',
|
||||||
|
'Configuration Manager',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserLocalCreation($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bCheckSessionMessage=false)
|
||||||
|
{
|
||||||
|
/*if ($bCheckSessionMessage){
|
||||||
|
$sLogin = "Admin-" . uniqid();
|
||||||
|
$oConnectedUser = $this->CreateContactlessUser($sLogin, 1, "Iuytrez9876543ç_è-(");
|
||||||
|
$_SESSION = [];
|
||||||
|
\UserRights::Login($oConnectedUser->Get('login'));
|
||||||
|
}*/
|
||||||
|
|
||||||
|
$oUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser->Set('language', 'EN US');
|
||||||
|
$this->commonUserCreationTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
|
||||||
|
/*if ($bCheckSessionMessage){
|
||||||
|
$aObjMessages = Session::Get('obj_messages');
|
||||||
|
$this->assertNotEmpty($aObjMessages);
|
||||||
|
$sKey = sprintf("%s::%s", get_class($oUser), $oUser->GetKey());
|
||||||
|
$this->assertTrue(array_key_exists($sKey, $aObjMessages));
|
||||||
|
|
||||||
|
$sMsg = <<<TXT
|
||||||
|
User profile Portal power user cannot be standalone. User has been completed with profile Portal power user.
|
||||||
|
TXT;
|
||||||
|
$aExpectedMessages = [
|
||||||
|
[
|
||||||
|
'rank' => 1,
|
||||||
|
'severity' => 'WARNING',
|
||||||
|
'message' => $sMsg
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$this->assertEquals($aExpectedMessages, array_values($aObjMessages[$sKey]), var_export($aObjMessages[$sKey], true));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserLocalUpdate($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bCheckSessionMessage=false)
|
||||||
|
{
|
||||||
|
$oUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserLocalUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser->Set('language', 'EN US');
|
||||||
|
$this->commonUserUpdateTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserLDAPCreation($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bCheckSessionMessage=false)
|
||||||
|
{
|
||||||
|
$oUser = new \UserLDAP();
|
||||||
|
$sLogin = 'testUserLDAPCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$this->commonUserCreationTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserLDAPUpdate($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation)
|
||||||
|
{
|
||||||
|
$oUser = new \UserLDAP();
|
||||||
|
$sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$this->commonUserUpdateTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserExternalCreation($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bCheckSessionMessage=false)
|
||||||
|
{
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin = 'testUserLDAPCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$this->commonUserCreationTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider PortaPowerUserProvider
|
||||||
|
*/
|
||||||
|
public function testUserExternalUpdate($aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bCheckSessionMessage=false)
|
||||||
|
{
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$this->commonUserUpdateTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CreateUserForProfileTesting(\User $oUserToCreate, array $aAssociatedProfilesBeforeUserCreation, $bDbInsert=true) : array
|
||||||
|
{
|
||||||
|
$aProfiles = [];
|
||||||
|
$oSearch = \DBSearch::FromOQL("SELECT URP_Profiles");
|
||||||
|
$oProfileSet = new DBObjectSet($oSearch);
|
||||||
|
while (($oProfile = $oProfileSet->Fetch()) != null){
|
||||||
|
$aProfiles[$oProfile->Get('name')] = $oProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->CreateTestOrganization();
|
||||||
|
$oContact = $this->CreatePerson("1");
|
||||||
|
$iContactid = $oContact->GetKey();
|
||||||
|
|
||||||
|
$oUserToCreate->Set('contactid', $iContactid);
|
||||||
|
$sUserClass = get_class($oUserToCreate);
|
||||||
|
|
||||||
|
$oUserProfileList = $oUserToCreate->Get('profile_list');
|
||||||
|
foreach ($aAssociatedProfilesBeforeUserCreation as $sProfileName){
|
||||||
|
$oUserProfile = new URP_UserProfile();
|
||||||
|
$oProfile = $aProfiles[$sProfileName];
|
||||||
|
$oUserProfile->Set('profileid', $oProfile->GetKey());
|
||||||
|
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||||
|
$oUserProfileList->AddItem($oUserProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$oUserToCreate->Set('profile_list', $oUserProfileList);
|
||||||
|
if ($bDbInsert){
|
||||||
|
$sId = $oUserToCreate->DBInsert();
|
||||||
|
} else {
|
||||||
|
$sId = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ $sId, $aProfiles];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commonUserCreationTest($oUserToCreate, $aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation, $bTestUserItopAccess=true)
|
||||||
|
{
|
||||||
|
$sUserClass = get_class($oUserToCreate);
|
||||||
|
list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUserToCreate, $aAssociatedProfilesBeforeUserCreation);
|
||||||
|
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedAssociatedProfilesAfterUserCreation, $bTestUserItopAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedAssociatedProfilesAfterUserCreation, $bTestItopConnection=true){
|
||||||
|
$oUser = \MetaModel::GetObject($sUserClass, $sId);
|
||||||
|
$oUserProfileList = $oUser->Get('profile_list');
|
||||||
|
$aProfilesAfterCreation=[];
|
||||||
|
while (($oProfile = $oUserProfileList->Fetch()) != null){
|
||||||
|
$aProfilesAfterCreation[] = $oProfile->Get('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($aExpectedAssociatedProfilesAfterUserCreation as $sExpectedProfileName){
|
||||||
|
$this->assertTrue(in_array($sExpectedProfileName, $aProfilesAfterCreation),
|
||||||
|
"profile \'$sExpectedProfileName\' should be asociated to user after creation. " . var_export($aProfilesAfterCreation, true) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $bTestItopConnection){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
UserRights::Login($oUser->Get('login'));
|
||||||
|
|
||||||
|
if (! UserRights::IsPortalUser()) {
|
||||||
|
//calling this API triggers Fatal Error on below OQL used by \User->GetContactObject() for a user with only 'portal power user' profile
|
||||||
|
/**
|
||||||
|
* Error: No result for the single row query: 'SELECT DISTINCT `Contact`.`id` AS `Contactid`, `Contact`.`name` AS `Contactname`, `Contact`.`status` AS `Contactstatus`, `Contact`.`org_id` AS `Contactorg_id`, `Organization_org_id`.`name` AS `Contactorg_name`, `Contact`.`email` AS `Contactemail`, `Contact`.`phone` AS `Contactphone`, `Contact`.`notify` AS `Contactnotify`, `Contact`.`function` AS `Contactfunction`, `Contact`.`finalclass` AS `Contactfinalclass`, IF((`Contact`.`finalclass` IN ('Team', 'Contact')), CAST(CONCAT(COALESCE(`Contact`.`name`, '')) AS CHAR), CAST(CONCAT(COALESCE(`Contact_poly_Person`.`first_name`, ''), COALESCE(' ', ''), COALESCE(`Contact`.`name`, '')) AS CHAR)) AS `Contactfriendlyname`, COALESCE((`Contact`.`status` = 'inactive'), 0) AS `Contactobsolescence_flag`, `Contact`.`obsolescence_date` AS `Contactobsolescence_date`, CAST(CONCAT(COALESCE(`Organization_org_id`.`name`, '')) AS CHAR) AS `Contactorg_id_friendlyname`, COALESCE((`Organization_org_id`.`status` = 'inactive'), 0) AS `Contactorg_id_obsolescence_flag` FROM `contact` AS `Contact` INNER JOIN `organization` AS `Organization_org_id` ON `Contact`.`org_id` = `Organization_org_id`.`id` LEFT JOIN `person` AS `Contact_poly_Person` ON `Contact`.`id` = `Contact_poly_Person`.`id` WHERE ((`Contact`.`id` = 40) AND 0) '.
|
||||||
|
*/
|
||||||
|
NavigationMenuFactory::MakeStandard();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(true, 'after fix N°5324 no exception raised');
|
||||||
|
// logout
|
||||||
|
$_SESSION = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commonUserUpdateTest($oUserToCreate, $aAssociatedProfilesBeforeUserCreation,
|
||||||
|
$aExpectedAssociatedProfilesAfterUserCreation)
|
||||||
|
{
|
||||||
|
$sUserClass = get_class($oUserToCreate);
|
||||||
|
list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUserToCreate, ["Administrator"]);
|
||||||
|
|
||||||
|
$oUserToUpdate = \MetaModel::GetObject($sUserClass, $sId);
|
||||||
|
$oProfileList = $oUserToUpdate->Get('profile_list');
|
||||||
|
while($oObj = $oProfileList->Fetch()){
|
||||||
|
$oProfileList->RemoveItem($oObj->GetKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($aAssociatedProfilesBeforeUserCreation as $sProfileName){
|
||||||
|
$oAdminUrpProfile = new URP_UserProfile();
|
||||||
|
$oProfile = $aProfiles[$sProfileName];
|
||||||
|
$oAdminUrpProfile->Set('profileid', $oProfile->GetKey());
|
||||||
|
$oAdminUrpProfile->Set('reason', 'UNIT Tests');
|
||||||
|
$oProfileList->AddItem($oAdminUrpProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$oUserToUpdate->Set('profile_list', $oProfileList);
|
||||||
|
$oUserToUpdate->DBWrite();
|
||||||
|
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedAssociatedProfilesAfterUserCreation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ProfilesLinksProvider
|
||||||
|
*/
|
||||||
|
public function testProfilesLinksDBDelete(string $sProfileNameToRemove, $bRaiseException=false){
|
||||||
|
$aInitialProfiles = [ $sProfileNameToRemove, "Portal power user"];
|
||||||
|
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
|
||||||
|
$sUserClass = get_class($oUser);
|
||||||
|
list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, $aInitialProfiles);
|
||||||
|
|
||||||
|
if ($bRaiseException){
|
||||||
|
$this->expectException(\DeleteException::class);
|
||||||
|
$sMessage = <<<TXT
|
||||||
|
Profile Portal power user cannot be standalone. You should add other profiles to user $sLogin otherwise you may encounter access issue with this user.
|
||||||
|
TXT;
|
||||||
|
|
||||||
|
$this->expectExceptionMessage($sMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
|
||||||
|
if (array_key_exists($sProfileNameToRemove, $aURPUserProfileByUser)){
|
||||||
|
$oURPUserProfile = $aURPUserProfileByUser[$sProfileNameToRemove];
|
||||||
|
$oURPUserProfile->DBDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $bRaiseException) {
|
||||||
|
$aExpectedProfilesAfterUpdate = ["Portal power user", "Portal user"];
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedProfilesAfterUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ProfilesLinksProvider
|
||||||
|
*/
|
||||||
|
public function testProfilesLinksEdit_ChangeProfileId(string $sInitialProfile, $bRaiseException=false){
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
|
||||||
|
$sUserClass = get_class($oUser);
|
||||||
|
list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, [$sInitialProfile]);
|
||||||
|
|
||||||
|
$oURP_Profile = \MetaModel::GetObjectByColumn("URP_Profiles", "name", "Portal power user");
|
||||||
|
|
||||||
|
$aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
|
||||||
|
|
||||||
|
if ($bRaiseException){
|
||||||
|
$this->expectException(\CoreCannotSaveObjectException::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($sInitialProfile, $aURPUserProfileByUser)){
|
||||||
|
$oURPUserProfile = $aURPUserProfileByUser[$sInitialProfile];
|
||||||
|
$oURPUserProfile->Set('profileid', $oURP_Profile->GetKey());
|
||||||
|
$oURPUserProfile->DBWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$bRaiseException) {
|
||||||
|
$aExpectedProfilesAfterUpdate = ["Portal power user", "Portal user"];
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedProfilesAfterUpdate);
|
||||||
|
|
||||||
|
//check warning
|
||||||
|
/*$aObjMessages = Session::Get('obj_messages');
|
||||||
|
$this->assertNotEmpty($aObjMessages);
|
||||||
|
$sKey = sprintf("%s::%s", get_class($oURPUserProfile), $oURPUserProfile->GetKey());
|
||||||
|
$this->assertTrue(array_key_exists($sKey, $aObjMessages));
|
||||||
|
|
||||||
|
$sMsg = <<<TXT
|
||||||
|
User profile Portal power user cannot be standalone. User has been completed with profile Portal power user.
|
||||||
|
TXT;
|
||||||
|
$aExpectedMessages = [
|
||||||
|
[
|
||||||
|
'rank' => 1,
|
||||||
|
'severity' => 'WARNING',
|
||||||
|
'message' => $sMsg
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$this->assertEquals($aExpectedMessages, array_values($aObjMessages[$sKey]), var_export($aObjMessages[$sKey], true));*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ProfilesLinksProvider() {
|
||||||
|
return [
|
||||||
|
"Administrator" => [ "sProfileNameToMove" => "Administrator" ],
|
||||||
|
"Portal user" => [ "sProfileNameToMove" => "Portal user", "bRaiseException" => true ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ProfilesLinksProvider
|
||||||
|
*/
|
||||||
|
public function testProfilesLinksEdit_ChangeUserId($sProfileNameToMove, $bRaiseException=false){
|
||||||
|
$aInitialProfiles = [ $sProfileNameToMove, "Portal power user"];
|
||||||
|
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin1 = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin1);
|
||||||
|
|
||||||
|
$sUserClass = get_class($oUser);
|
||||||
|
list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, $aInitialProfiles);
|
||||||
|
|
||||||
|
$oUser = new \UserExternal();
|
||||||
|
$sLogin2 = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin2);
|
||||||
|
list ($sAnotherUserId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, ["Configuration Manager"]);
|
||||||
|
|
||||||
|
if ($bRaiseException){
|
||||||
|
$this->expectException(\CoreCannotSaveObjectException::class);
|
||||||
|
$sMessage = <<<TXT
|
||||||
|
Profile Portal power user cannot be standalone. You should add other profiles to user $sLogin1 otherwise you may encounter access issue with this user.
|
||||||
|
TXT;
|
||||||
|
|
||||||
|
$this->expectExceptionMessage($sMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
|
||||||
|
if (array_key_exists($sProfileNameToMove, $aURPUserProfileByUser)){
|
||||||
|
$oURPUserProfile = $aURPUserProfileByUser[$sProfileNameToMove];
|
||||||
|
$oURPUserProfile->Set('userid', $sAnotherUserId);
|
||||||
|
$oURPUserProfile->DBWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $bRaiseException) {
|
||||||
|
$aExpectedProfilesAfterUpdate = [$sProfileNameToMove, "Configuration Manager"];
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sAnotherUserId, $aExpectedProfilesAfterUpdate);
|
||||||
|
|
||||||
|
$aExpectedProfilesAfterUpdate = ["Portal power user", "Portal user"];
|
||||||
|
$this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedProfilesAfterUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetURPUserProfileByUser($iUserId) : array {
|
||||||
|
$aRes = [];
|
||||||
|
$oSearch = \DBSearch::FromOQL("SELECT URP_UserProfile WHERE userid=$iUserId");
|
||||||
|
$oSet = new DBObjectSet($oSearch);
|
||||||
|
while (($oURPUserProfile = $oSet->Fetch()) != null){
|
||||||
|
$aRes[$oURPUserProfile->Get('profile')] = $oURPUserProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserProfilesEventListenerInit_nominal(){
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserProfilesEventListenerInit_badlyconfigured(){
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME, "a string instead of an array");
|
||||||
|
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
|
||||||
|
$this->assertFalse($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserProfilesEventListenerInit_specifically_disabled(){
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME, []);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
|
||||||
|
$this->assertFalse($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CustomizedPortalsProvider(){
|
||||||
|
return [
|
||||||
|
'console + customized portal' => [
|
||||||
|
'aPortalDispatcherData' => [
|
||||||
|
'customer-portal',
|
||||||
|
'backoffice'
|
||||||
|
]],
|
||||||
|
'console + itop portal + customized portal' => [
|
||||||
|
'aPortalDispatcherData' => [
|
||||||
|
'itop-portal',
|
||||||
|
'customer-portal',
|
||||||
|
'backoffice'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider CustomizedPortalsProvider
|
||||||
|
*/
|
||||||
|
public function testUserProfilesEventListenerInit_furtherportals_norepairmentconfigured($aPortalDispatcherData){
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init($aPortalDispatcherData);
|
||||||
|
|
||||||
|
$this->assertFalse($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserProfilesEventListenerInit_furtherportals_repairmentconfigured(){
|
||||||
|
$aPortalDispatcherData = [
|
||||||
|
'itop-portal',
|
||||||
|
'customer-portal',
|
||||||
|
'backoffice'
|
||||||
|
];
|
||||||
|
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME, ['Portal power user' => 'Portal user']);
|
||||||
|
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init($aPortalDispatcherData);
|
||||||
|
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserProfilesEventListenerInit_with_unknownprofile(){
|
||||||
|
$aPortalDispatcherData = [
|
||||||
|
'itop-portal',
|
||||||
|
'customer-portal',
|
||||||
|
'backoffice'
|
||||||
|
];
|
||||||
|
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME, ['Portal power user' => 'Dummy Profile']);
|
||||||
|
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init($aPortalDispatcherData);
|
||||||
|
|
||||||
|
$this->assertFalse($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInit_ConfWithOneWarningProfile() {
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
['Configuration Manager' => 'Administrator', 'Portal power user' => null]
|
||||||
|
);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInit_ConfWithFurtherWarningProfiles() {
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
['Configuration Manager' => null, 'Portal power user' => null]
|
||||||
|
);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInit_ConfWithFurtherWarningProfilesAndOneRepairment() {
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
['Portal power user' => null, 'Configuration Manager' => null, 'Administrator' => "Configuration Manager"]
|
||||||
|
);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRepairProfiles_WithAnotherFallbackProfile()
|
||||||
|
{
|
||||||
|
$oUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser->Set('language', 'EN US');
|
||||||
|
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
['Portal power user' => 'Configuration Manager']
|
||||||
|
);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
|
||||||
|
$this->CreateUserForProfileTesting($oUser, ['Portal power user'], false);
|
||||||
|
$oUserProfilesEventListener->ValidateThenRepairOrWarn($oUser);
|
||||||
|
|
||||||
|
$oUserProfileList = $oUser->Get('profile_list');
|
||||||
|
$aProfilesAfterCreation=[];
|
||||||
|
while (($oProfile = $oUserProfileList->Fetch()) != null){
|
||||||
|
$aProfilesAfterCreation[] = $oProfile->Get('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertContains('Configuration Manager', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
$this->assertContains('Portal power user', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRepairProfiles_MultiRepairmentConf()
|
||||||
|
{
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
[
|
||||||
|
'Administrator' => 'Portal user',
|
||||||
|
'Portal power user' => 'Configuration Manager'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->Init();
|
||||||
|
$this->assertTrue($oUserProfilesEventListener->IsRepairmentEnabled());
|
||||||
|
|
||||||
|
$oUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser->Set('language', 'EN US');
|
||||||
|
$this->CreateUserForProfileTesting($oUser, ['Portal power user'], false);
|
||||||
|
$oUserProfilesEventListener->ValidateThenRepairOrWarn($oUser);
|
||||||
|
|
||||||
|
$oUserProfileList = $oUser->Get('profile_list');
|
||||||
|
$aProfilesAfterCreation=[];
|
||||||
|
while (($oProfile = $oUserProfileList->Fetch()) != null){
|
||||||
|
$aProfilesAfterCreation[] = $oProfile->Get('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertContains('Configuration Manager', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
$this->assertContains('Portal power user', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
|
||||||
|
$oUser2 = new \UserLocal();
|
||||||
|
$sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid();
|
||||||
|
$oUser2->Set('login', $sLogin);
|
||||||
|
$oUser2->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser2->Set('language', 'EN US');
|
||||||
|
|
||||||
|
$this->CreateUserForProfileTesting($oUser2, ['Administrator'], false);
|
||||||
|
$oUserProfilesEventListener->ValidateThenRepairOrWarn($oUser2);
|
||||||
|
|
||||||
|
$oUserProfileList = $oUser2->Get('profile_list');
|
||||||
|
$aProfilesAfterCreation=[];
|
||||||
|
while (($oProfile = $oUserProfileList->Fetch()) != null){
|
||||||
|
$aProfilesAfterCreation[] = $oProfile->Get('profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertContains('Administrator', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
$this->assertContains('Portal user', $aProfilesAfterCreation, var_export($aProfilesAfterCreation, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCreationWithWarningMessageConf()
|
||||||
|
{
|
||||||
|
$_SESSION = [];
|
||||||
|
$oAdminUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserCreationWithWarningMessageConf-Admin'.uniqid();
|
||||||
|
$oAdminUser->Set('login', $sLogin);
|
||||||
|
$oAdminUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oAdminUser->Set('language', 'EN US');
|
||||||
|
$aAssociatedProfilesBeforeUserCreation = ['Administrator'];
|
||||||
|
$this->commonUserCreationTest($oAdminUser, $aAssociatedProfilesBeforeUserCreation, $aAssociatedProfilesBeforeUserCreation, false);
|
||||||
|
UserRights::Login($oAdminUser->Get('login'));
|
||||||
|
|
||||||
|
$aAssociatedProfilesBeforeUserCreation = [
|
||||||
|
'Portal power user'
|
||||||
|
];
|
||||||
|
|
||||||
|
$oUser = new \UserLocal();
|
||||||
|
$sLogin = 'testUserCreationWithWarningMessageConf-'.uniqid();
|
||||||
|
$oUser->Set('login', $sLogin);
|
||||||
|
$oUser->Set('password', 'ABCD1234@gabuzomeu');
|
||||||
|
$oUser->Set('language', 'EN US');
|
||||||
|
|
||||||
|
\MetaModel::GetConfig()->Set(UserProfilesEventListener::USERPROFILE_REPAIR_ITOP_PARAM_NAME,
|
||||||
|
['Portal power user' => null ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->SetNonPublicStaticProperty(EventService::class, "aEventListeners", []);
|
||||||
|
$oUserProfilesEventListener = new UserProfilesEventListener();
|
||||||
|
$oUserProfilesEventListener->RegisterEventsAndListeners();
|
||||||
|
|
||||||
|
$this->expectException(\CoreCannotSaveObjectException::class);
|
||||||
|
$sMessage = <<<TXT
|
||||||
|
Profile Portal power user cannot be standalone. You should add other profiles to user $sLogin otherwise you may encounter access issue with this user.
|
||||||
|
TXT;
|
||||||
|
|
||||||
|
$this->expectExceptionMessage($sMessage);
|
||||||
|
|
||||||
|
$this->commonUserCreationTest($oUser, $aAssociatedProfilesBeforeUserCreation, $aAssociatedProfilesBeforeUserCreation, false);
|
||||||
|
|
||||||
|
$_SESSION = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,7 +140,7 @@ class DBBackupTest extends ItopTestCase
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function testMakeName(string $sInputFormat, DateTime $oBackupDateTime, string $sExpectedFilename): void
|
public function testMakeName(?string $sInputFormat, ?DateTime $oBackupDateTime, string $sExpectedFilename): void
|
||||||
{
|
{
|
||||||
$oConfig = utils::GetConfig();
|
$oConfig = utils::GetConfig();
|
||||||
|
|
||||||
@@ -160,6 +160,11 @@ class DBBackupTest extends ItopTestCase
|
|||||||
$oBackupDateTime = DateTime::createFromFormat('Y-m-d H:i:s', '1985-07-30 15:30:59');
|
$oBackupDateTime = DateTime::createFromFormat('Y-m-d H:i:s', '1985-07-30 15:30:59');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'Default format - no params' => [
|
||||||
|
'__DB__-%Y-%m-%d',
|
||||||
|
$oBackupDateTime,
|
||||||
|
static::DUMMY_DB_NAME.'-1985-07-30',
|
||||||
|
],
|
||||||
'Default format' => [
|
'Default format' => [
|
||||||
'__DB__-%Y-%m-%d',
|
'__DB__-%Y-%m-%d',
|
||||||
$oBackupDateTime,
|
$oBackupDateTime,
|
||||||
@@ -182,4 +187,28 @@ class DBBackupTest extends ItopTestCase
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* N°6640 - Broken unattended setup with XML backup configuration
|
||||||
|
*/
|
||||||
|
public function testMakeNameWithoutParams(): void
|
||||||
|
{
|
||||||
|
$oConfig = utils::GetConfig();
|
||||||
|
|
||||||
|
// See https://github.com/Combodo/iTop/commit/f7ee21f1d7d1c23910506e9e31b57f33311bd5e0#diff-d693fb790e3463d1aa960c2b8b293532b1bbd12c3b8f885d568d315c404f926aR131
|
||||||
|
$oConfig->Set('db_host', static::DUMMY_DB_HOST);
|
||||||
|
$oConfig->Set('db_name', static::DUMMY_DB_NAME);
|
||||||
|
$oConfig->Set('db_subname', static::DUMMY_DB_SUBNAME);
|
||||||
|
|
||||||
|
$oDateTime = new DateTime();
|
||||||
|
$sExpectedFilename = static::DUMMY_DB_NAME . '-' . $oDateTime->format("Y-m-d");
|
||||||
|
|
||||||
|
$oBackup = new DBBackup($oConfig);
|
||||||
|
|
||||||
|
$sTestedFilename = $oBackup->MakeName(null);
|
||||||
|
$this->assertEquals($sExpectedFilename, $sTestedFilename, "Backup filename format doesn't match. Got '$sTestedFilename', expected '$sExpectedFilename'.");
|
||||||
|
|
||||||
|
$sTestedFilename = $oBackup->MakeName();
|
||||||
|
$this->assertEquals($sExpectedFilename, $sTestedFilename, "Backup filename format doesn't match. Got '$sTestedFilename', expected '$sExpectedFilename'.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
139
tests/php-unit-tests/unitary-tests/setup/MFCompilerMenuTest.php
Normal file
139
tests/php-unit-tests/unitary-tests/setup/MFCompilerMenuTest.php
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Setup;
|
||||||
|
|
||||||
|
use ApplicationMenu;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
|
use Config;
|
||||||
|
use MetaModel;
|
||||||
|
use MFCompiler;
|
||||||
|
use ParentMenuNodeCompiler;
|
||||||
|
use RunTimeEnvironment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group menu_compilation
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
* @since 3.1 N°4762
|
||||||
|
* @covers \MFCompiler::DoCompile
|
||||||
|
*/
|
||||||
|
class MFCompilerMenuTest extends ItopTestCase {
|
||||||
|
private static $aPreviousEnvMenus;
|
||||||
|
private static $aPreviousEnvMenuCount;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
require_once APPROOT.'setup/compiler.class.inc.php';
|
||||||
|
require_once APPROOT.'setup/modelfactory.class.inc.php';
|
||||||
|
require_once APPROOT.'application/utils.inc.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tearDown(): void {
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetCurrentEnvDeltaXmlPath(string $sEnv) : string {
|
||||||
|
return APPROOT."data/$sEnv.delta.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CompileMenusProvider(){
|
||||||
|
return [
|
||||||
|
'legacy_algo' => [ 'sEnv' => 'legacy_algo', 'bLegacyMenuCompilation' => true ],
|
||||||
|
'menu_compilation_fix' => [ 'sEnv' => 'menu_compilation_fix', 'bLegacyMenuCompilation' => false ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider CompileMenusProvider
|
||||||
|
*/
|
||||||
|
public function testCompileMenus($sEnv, $bLegacyMenuCompilation){
|
||||||
|
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
|
||||||
|
|
||||||
|
//copy conf from production to phpunit context
|
||||||
|
$sDirPath = dirname($sConfigFilePath);
|
||||||
|
if (! is_dir($sDirPath)){
|
||||||
|
mkdir($sDirPath);
|
||||||
|
}
|
||||||
|
$oConfig = new Config(\utils::GetConfigFilePath());
|
||||||
|
$oConfig->WriteToFile($sConfigFilePath);
|
||||||
|
|
||||||
|
$oConfig = new Config($sConfigFilePath);
|
||||||
|
if ($bLegacyMenuCompilation){
|
||||||
|
ParentMenuNodeCompiler::UseLegacyMenuCompilation();
|
||||||
|
}
|
||||||
|
$oConfig->WriteToFile();
|
||||||
|
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
|
||||||
|
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
|
||||||
|
$oConfig->WriteToFile();
|
||||||
|
|
||||||
|
$sConfigFile = APPCONF.\utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||||
|
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||||
|
|
||||||
|
$aMenuGroups = ApplicationMenu::GetMenuGroups();
|
||||||
|
if (! is_null(static::$aPreviousEnvMenus)){
|
||||||
|
$this->assertEquals(static::$aPreviousEnvMenus, $aMenuGroups);
|
||||||
|
} else {
|
||||||
|
$this->assertNotEquals([], $aMenuGroups);
|
||||||
|
}
|
||||||
|
static::$aPreviousEnvMenus = $aMenuGroups;
|
||||||
|
|
||||||
|
$aMenuCount = ApplicationMenu::GetMenusCount();
|
||||||
|
|
||||||
|
if (! is_null(static::$aPreviousEnvMenuCount)){
|
||||||
|
$this->assertEquals(static::$aPreviousEnvMenuCount, $aMenuCount);
|
||||||
|
} else {
|
||||||
|
$this->assertNotEquals([], $aMenuCount);
|
||||||
|
}
|
||||||
|
static::$aPreviousEnvMenuCount = $aMenuCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CompileMenusWithDeltaProvider(){
|
||||||
|
return [
|
||||||
|
'Menus are broken with specific delta XML using LEGACY algo' => [ 'sDeltaFile' => 'delta_broken_menus.xml', 'sEnv' => 'broken_menus', 'bLegacyMenuCompilation' => true ],
|
||||||
|
'Menus repaired using same delta XML with NEW algo' => [ 'sDeltaFile' => 'delta_broken_menus.xml', 'sEnv' => 'fixed_menus', 'bLegacyMenuCompilation' => false ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider CompileMenusWithDeltaProvider
|
||||||
|
*/
|
||||||
|
public function testCompileMenusWithDelta($sDeltaFile, $sEnv, $bLegacyMenuCompilation){
|
||||||
|
$sProvidedDeltaPath = __DIR__.'/ressources/datamodels/'.$sDeltaFile;
|
||||||
|
if (is_file($sProvidedDeltaPath)){
|
||||||
|
$sDeltaXmlPath = $this->GetCurrentEnvDeltaXmlPath($sEnv);
|
||||||
|
copy($sProvidedDeltaPath, $sDeltaXmlPath);
|
||||||
|
}
|
||||||
|
$sConfigFilePath = \utils::GetConfigFilePath($sEnv);
|
||||||
|
|
||||||
|
//copy conf from production to phpunit context
|
||||||
|
$sDirPath = dirname($sConfigFilePath);
|
||||||
|
if (! is_dir($sDirPath)){
|
||||||
|
mkdir($sDirPath);
|
||||||
|
}
|
||||||
|
$oConfig = new Config(\utils::GetConfigFilePath());
|
||||||
|
$oConfig->WriteToFile($sConfigFilePath);
|
||||||
|
|
||||||
|
$oConfig = new Config($sConfigFilePath);
|
||||||
|
if ($bLegacyMenuCompilation){
|
||||||
|
ParentMenuNodeCompiler::UseLegacyMenuCompilation();
|
||||||
|
}
|
||||||
|
$oConfig->WriteToFile();
|
||||||
|
$oRunTimeEnvironment = new RunTimeEnvironment($sEnv);
|
||||||
|
$oRunTimeEnvironment->CompileFrom(\utils::GetCurrentEnvironment());
|
||||||
|
$oConfig->WriteToFile();
|
||||||
|
|
||||||
|
if ($bLegacyMenuCompilation){
|
||||||
|
/**
|
||||||
|
* PHP Notice: Undefined index: ConfigManagement in /var/www/html/iTop/env-broken_menus/itop-structure/model.itop-structure.php on line 925
|
||||||
|
*/
|
||||||
|
error_reporting(E_ALL & ~E_NOTICE);
|
||||||
|
$this->expectErrorMessage("Call to a member function GetIndex() on null");
|
||||||
|
}
|
||||||
|
$sConfigFile = APPCONF.\utils::GetCurrentEnvironment().'/'.ITOP_CONFIG_FILE;
|
||||||
|
MetaModel::Startup($sConfigFile, false /* $bModelOnly */, true /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
|
||||||
|
|
||||||
|
$this->assertNotEquals([], ApplicationMenu::GetMenuGroups());
|
||||||
|
$this->assertNotEquals([], ApplicationMenu::GetMenusCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0">
|
||||||
|
<menus>
|
||||||
|
<menu id="Contact" xsi:type="DashboardMenuNode" _created_in="itop-config-mgmt" _delta="must_exist">
|
||||||
|
<parent _delta="redefine">ConfigManagementOverview</parent>
|
||||||
|
</menu>
|
||||||
|
<menu id="Location" xsi:type="OQLMenuNode" _created_in="itop-config-mgmt" _delta="delete">
|
||||||
|
<parent _delta="redefine">ConfigManagementOverview</parent>
|
||||||
|
</menu>
|
||||||
|
<menu id="Document" xsi:type="OQLMenuNode" _created_in="itop-config-mgmt" _delta="must_exist">
|
||||||
|
<parent _delta="redefine">ConfigManagementOverview</parent>
|
||||||
|
</menu>
|
||||||
|
</menus>
|
||||||
|
</itop_design>
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\SessionTracker;
|
||||||
|
|
||||||
|
use Combodo\iTop\Application\Helper\Session;
|
||||||
|
use Combodo\iTop\SessionTracker\SessionHandler;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use ContextTag;
|
||||||
|
|
||||||
|
class SessionHandlerTest extends ItopDataTestCase
|
||||||
|
{
|
||||||
|
private $aFiles ;
|
||||||
|
private $oTag ;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->aFiles=[];
|
||||||
|
$this->oTag = new ContextTag(ContextTag::TAG_REST);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
parent::tearDown();
|
||||||
|
$this->oTag = null;
|
||||||
|
|
||||||
|
foreach ($this->aFiles as $sFile){
|
||||||
|
if (is_file($sFile)){
|
||||||
|
@unlink($sFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function CreateUserAndLogIn() : ? string {
|
||||||
|
$_SESSION = [];
|
||||||
|
$oUser = $this->CreateContactlessUser("admin" . uniqid(), 1, "1234@Abcdefg");
|
||||||
|
|
||||||
|
\UserRights::Login($oUser->Get('login'));
|
||||||
|
return $oUser->GetKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GenerateSessionContent(SessionHandler $oSessionHandler, ?string $sPreviousFileVersionContent) : ?string {
|
||||||
|
return $this->InvokeNonPublicMethod(SessionHandler::class, "generate_session_content", $oSessionHandler, $aArgs = [$sPreviousFileVersionContent]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @covers SessionHandler::generate_session_content
|
||||||
|
*/
|
||||||
|
public function testGenerateSessionContentNoUserLoggedIn(){
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
$sContent = $this->GenerateSessionContent($oSessionHandler, null);
|
||||||
|
$this->assertNull($sContent, "Session content should be null when there is no user logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GenerateSessionContentCorruptedPreviousFileContentProvider() {
|
||||||
|
return [
|
||||||
|
'not a json' => [ "not a json" ],
|
||||||
|
'not an array' => [ json_encode("not an array") ],
|
||||||
|
'array without creation_time key' => [ json_encode([]) ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers SessionHandler::generate_session_content
|
||||||
|
* @dataProvider GenerateSessionContentCorruptedPreviousFileContentProvider
|
||||||
|
*/
|
||||||
|
public function testGenerateSessionContent_SessionFileRepairment(?string $sFileContent){
|
||||||
|
$sUserId = $this->CreateUserAndLogIn();
|
||||||
|
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
Session::Set('login_mode', 'foo_login_mode');
|
||||||
|
|
||||||
|
$sContent = $this->GenerateSessionContent($oSessionHandler, $sFileContent);
|
||||||
|
|
||||||
|
$this->assertNotNull($sContent, 'Should not return null');
|
||||||
|
$aJson = json_decode($sContent, true);
|
||||||
|
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sContent);
|
||||||
|
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sContent");
|
||||||
|
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sContent");
|
||||||
|
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sContent");
|
||||||
|
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sContent");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @covers SessionHandler::generate_session_content
|
||||||
|
*/
|
||||||
|
public function testGenerateSessionContent(){
|
||||||
|
$sUserId = $this->CreateUserAndLogIn();
|
||||||
|
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
Session::Set('login_mode', 'foo_login_mode');
|
||||||
|
|
||||||
|
//first time
|
||||||
|
$sFirstContent = $this->GenerateSessionContent($oSessionHandler, null);
|
||||||
|
|
||||||
|
$this->assertNotNull($sFirstContent, 'Should not return null');
|
||||||
|
$aJson = json_decode($sFirstContent, true);
|
||||||
|
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent);
|
||||||
|
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent");
|
||||||
|
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent");
|
||||||
|
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent");
|
||||||
|
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent");
|
||||||
|
|
||||||
|
$iFirstSessionCreationTime = $aJson['creation_time'];
|
||||||
|
|
||||||
|
// Switch context + change user id via impersonation
|
||||||
|
// check it is still tracked in session files
|
||||||
|
$oOtherUser = $this->CreateContactlessUser("admin" . uniqid(), 1, "1234@Abcdefg");
|
||||||
|
$this->assertTrue(\UserRights::Impersonate($oOtherUser->Get('login')), "Failed to execute impersonate on: ".$oOtherUser->Get('login'));
|
||||||
|
$oTag2 = new ContextTag(ContextTag::TAG_SYNCHRO);
|
||||||
|
$sNewContent = $this->GenerateSessionContent($oSessionHandler, $sFirstContent);
|
||||||
|
$this->assertNotNull($sNewContent, 'Should not return null');
|
||||||
|
$aJson = json_decode($sNewContent, true);
|
||||||
|
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sNewContent);
|
||||||
|
$this->assertEquals(ContextTag::TAG_REST . '|' . ContextTag::TAG_SYNCHRO, $aJson['context'] ?? '', "After impersonation, should report the new context tags in [context]: $sNewContent");
|
||||||
|
$this->assertEquals($iFirstSessionCreationTime, $aJson['creation_time'] ?? '', "After impersonation, should still report the the session start timestamp in [creation_time]: $sNewContent");
|
||||||
|
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "After impersonation, should still report the login mode in [login_mode]: $sNewContent");
|
||||||
|
$this->assertEquals($oOtherUser->GetKey(), $aJson['user_id'] ?? '', "Should report the impersonate user in [user_id]: $sNewContent");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function touchSessionFile(SessionHandler $oSessionHandler, $session_id) : ?string {
|
||||||
|
$sRes = $this->InvokeNonPublicMethod(SessionHandler::class, "touch_session_file", $oSessionHandler, $aArgs = [$session_id]);
|
||||||
|
if (!is_null($sRes) && is_file($sRes)) {
|
||||||
|
// Record the file for cleanup on tearDown
|
||||||
|
$this->aFiles[] = $sRes;
|
||||||
|
}
|
||||||
|
clearstatcache();
|
||||||
|
return $sRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @covers SessionHandler::touch_session_file
|
||||||
|
*/
|
||||||
|
public function testTouchSessionFile_NoUserLoggedIn(){
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
$session_id = uniqid();
|
||||||
|
$sFile = $this->touchSessionFile($oSessionHandler, $session_id);
|
||||||
|
$this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name");
|
||||||
|
$sContent = file_get_contents($sFile);
|
||||||
|
$this->assertEquals(null, $sContent, 'Should create an empty file, found: '.$sContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @covers SessionHandler::touch_session_file
|
||||||
|
*/
|
||||||
|
public function testTouchSessionFile_UserLoggedIn(){
|
||||||
|
$sUserId = $this->CreateUserAndLogIn();
|
||||||
|
Session::Set('login_mode', 'foo_login_mode');
|
||||||
|
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
$session_id = uniqid();
|
||||||
|
$sFile = $this->touchSessionFile($oSessionHandler, $session_id);
|
||||||
|
$this->assertEquals(true, is_file($sFile), "Should return a file name: '$sFile' is not a valid file name");
|
||||||
|
$sFirstContent = file_get_contents($sFile);
|
||||||
|
|
||||||
|
$iFirstCTime = filectime($sFile) - 1;
|
||||||
|
// Set it in the past to check that it will be further updated (without the need to sleep...)
|
||||||
|
touch($sFile, $iFirstCTime);
|
||||||
|
|
||||||
|
$this->assertNotNull($sFirstContent, 'Should not return null');
|
||||||
|
$aJson = json_decode($sFirstContent, true);
|
||||||
|
$this->assertNotEquals(false, $aJson, 'Should return a valid json string, found: '.$sFirstContent);
|
||||||
|
$this->assertEquals($sUserId, $aJson['user_id'] ?? '', "Should report the login of the logged in user in [user_id]: $sFirstContent");
|
||||||
|
$this->assertEquals(ContextTag::TAG_REST, $aJson['context'] ?? '', "Should report the context tag(s) in [context]: $sFirstContent");
|
||||||
|
$this->assertIsInt($aJson['creation_time'] ?? '', "Should report the session start timestamp in [creation_time]: $sFirstContent");
|
||||||
|
$this->assertEquals('foo_login_mode', $aJson['login_mode'] ?? '', "Should report the current login mode in [login_mode]: $sFirstContent");
|
||||||
|
|
||||||
|
$this->touchSessionFile($oSessionHandler, $session_id);
|
||||||
|
$sNewContent = file_get_contents($sFile);
|
||||||
|
$this->assertEquals($sFirstContent, $sNewContent, 'On successive calls, should not modify an existing session file');
|
||||||
|
$this->assertGreaterThan($iFirstCTime, filectime($sFile), 'On successive calls, should have changed the file ctime');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers SessionHandler::touch_session_file
|
||||||
|
*/
|
||||||
|
public function testTouchSessionFileWithEmptySessionId() {
|
||||||
|
$this->CreateUserAndLogIn();
|
||||||
|
Session::Set('login_mode', 'toto');
|
||||||
|
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
$this->assertNull($this->touchSessionFile($oSessionHandler, ''), 'Should return null when session id is an empty string');
|
||||||
|
$this->assertNull($this->touchSessionFile($oSessionHandler, false), 'Should return null when session id (boolean) false');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetFilePath(SessionHandler $oSessionHandler, $session_id) : string {
|
||||||
|
$sFile = $this->InvokeNonPublicMethod(SessionHandler::class, "get_file_path", $oSessionHandler, $aArgs = [$session_id]);
|
||||||
|
// Record file for cleanup on tearDown
|
||||||
|
$this->aFiles[] = $sFile;
|
||||||
|
return $sFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GgcWithTimeLimitProvider(){
|
||||||
|
return [
|
||||||
|
'no cleanup time limit' => [
|
||||||
|
'iTimeLimit' => -1,
|
||||||
|
'iExpectedProcessed' => 2
|
||||||
|
],
|
||||||
|
'cleanup time limit in the pass => first file removed only' => [
|
||||||
|
'iTimeLimit' => time() - 1,
|
||||||
|
'iExpectedProcessed' => 1
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers SessionHandler::gc_with_time_limit
|
||||||
|
* @covers SessionHandler::list_session_files
|
||||||
|
* @dataProvider GgcWithTimeLimitProvider
|
||||||
|
*/
|
||||||
|
public function testGgcWithTimeLimit($iTimeLimit, $iExpectedProcessed) {
|
||||||
|
$oSessionHandler = new SessionHandler();
|
||||||
|
//remove all first
|
||||||
|
$oSessionHandler->gc_with_time_limit(-1);
|
||||||
|
$this->assertEquals([], $oSessionHandler->list_session_files(), 'list_session_files should report no file at startup');
|
||||||
|
|
||||||
|
$max_lifetime = 1440;
|
||||||
|
$iNbExpiredFiles = 2;
|
||||||
|
$iNbFiles = 5;
|
||||||
|
$iExpiredTimeStamp = time() - $max_lifetime - 1;
|
||||||
|
for($i=0; $i<$iNbFiles; $i++) {
|
||||||
|
$sFile = $this->GetFilePath($oSessionHandler, uniqid());
|
||||||
|
file_put_contents($sFile, "fakedata");
|
||||||
|
|
||||||
|
if ($iNbExpiredFiles > 0){
|
||||||
|
$iNbExpiredFiles--;
|
||||||
|
touch($sFile, $iExpiredTimeStamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$aFoundSessionFiles = $oSessionHandler->list_session_files();
|
||||||
|
$this->assertEquals($iNbFiles, sizeof($aFoundSessionFiles), 'list_session_files should reports all files');
|
||||||
|
foreach ($aFoundSessionFiles as $sFile){
|
||||||
|
$this->assertTrue(is_file($sFile), 'list_session_files should return a valid file paths, found: '.$sFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$iProcessed = $oSessionHandler->gc_with_time_limit($max_lifetime, $iTimeLimit);
|
||||||
|
$this->assertEquals($iExpectedProcessed, $iProcessed, 'gc_with_time_limit should report the count of expired files');
|
||||||
|
$this->assertEquals($iNbFiles - $iExpectedProcessed, sizeof($oSessionHandler->list_session_files()), 'gc_with_time_limit should actually remove all processed files');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,6 @@ use utils;
|
|||||||
*
|
*
|
||||||
* @package Combodo\iTop\Test\UnitTest\Synchro
|
* @package Combodo\iTop\Test\UnitTest\Synchro
|
||||||
* @group dataSynchro
|
* @group dataSynchro
|
||||||
* @group defaultProfiles
|
|
||||||
*/
|
*/
|
||||||
class DataSynchroTest extends ItopDataTestCase
|
class DataSynchroTest extends ItopDataTestCase
|
||||||
{
|
{
|
||||||
@@ -138,17 +137,19 @@ class DataSynchroTest extends ItopDataTestCase
|
|||||||
|
|
||||||
// Create the data source
|
// Create the data source
|
||||||
//
|
//
|
||||||
$oDataSource = new SynchroDataSource();
|
$aDsParams = [
|
||||||
$oDataSource->Set('name', 'Test data sync '.time());
|
'name' => 'Test data sync '.time(),
|
||||||
$oDataSource->Set('description', 'unit test - created automatically');
|
'description' => 'unit test - created automatically',
|
||||||
$oDataSource->Set('status', 'production');
|
'status' => 'production',
|
||||||
$oDataSource->Set('user_id', 0);
|
'user_id' => 0,
|
||||||
$oDataSource->Set('scope_class', $sClass);
|
'scope_class' => $sClass
|
||||||
|
];
|
||||||
foreach ($aSourceProperties as $sProperty => $value)
|
foreach ($aSourceProperties as $sProperty => $value)
|
||||||
{
|
{
|
||||||
$oDataSource->Set($sProperty, $value);
|
$aDsParams[$sProperty] = $value;
|
||||||
}
|
}
|
||||||
$iDataSourceId = $oDataSource->DBInsert();
|
$oDataSource = $this->createObject('SynchroDataSource', $aDsParams);
|
||||||
|
$iDataSourceId = $oDataSource->GetKey();
|
||||||
|
|
||||||
$oAttributeSet = $oDataSource->Get('attribute_list');
|
$oAttributeSet = $oDataSource->Get('attribute_list');
|
||||||
while ($oAttribute = $oAttributeSet->Fetch())
|
while ($oAttribute = $oAttributeSet->Fetch())
|
||||||
@@ -382,7 +383,7 @@ class DataSynchroTest extends ItopDataTestCase
|
|||||||
'source_data' => array(
|
'source_data' => array(
|
||||||
array('primary_key', 'login', 'password', 'profile_list'),
|
array('primary_key', 'login', 'password', 'profile_list'),
|
||||||
array(
|
array(
|
||||||
array('user_A', 'login_A', 'password_A', 'profileid:10;reason:he/she is managing services'),
|
array('user_A', 'login_A', 'password_A', 'profileid:3;reason:he/she is managing services'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'target_data' => array(
|
'target_data' => array(
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ if (!file_exists($sConfigFile))
|
|||||||
|
|
||||||
require_once(APPROOT.'/application/startup.inc.php');
|
require_once(APPROOT.'/application/startup.inc.php');
|
||||||
|
|
||||||
|
//temporary fix until below bug is resolvedd properly:
|
||||||
|
//N°7008 - Fix missing background tasks in CRON when NOT in "developer_mode"
|
||||||
|
require_once(APPROOT.'/sources/SessionTracker/SessionGC.php');
|
||||||
|
require_once(APPROOT.'/sources/Service/TemporaryObjects/TemporaryObjectGC.php');
|
||||||
|
|
||||||
$oCtx = new ContextTag(ContextTag::TAG_CRON);
|
$oCtx = new ContextTag(ContextTag::TAG_CRON);
|
||||||
|
|
||||||
function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter')
|
function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter')
|
||||||
|
|||||||
Reference in New Issue
Block a user