Compare commits

..

1 Commits

Author SHA1 Message Date
Eric Espie
cc4af0a027 N°6667 - Ignore trigger on state entering with auto-dispatch 2023-08-22 14:30:18 +02:00
116 changed files with 818 additions and 4663 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

View File

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

View File

@@ -228,7 +228,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
"is_link" => true, /** @since 3.1.0 N°6482 */
'uniqueness_rules' => array(
'no_duplicate' => array(
'attributes' => array(

View File

@@ -23,7 +23,7 @@ define('PORTAL_PROFILE_NAME', 'Portal user');
class UserRightsBaseClassGUI extends cmdbAbstractObject
{
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -43,7 +43,7 @@ class UserRightsBaseClassGUI extends cmdbAbstractObject
class UserRightsBaseClass extends DBObject
{
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -100,7 +100,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
$this->m_bCheckReservedNames = false;
}
protected static $m_aActions = array(
UR_ACTION_READ => 'Read',
UR_ACTION_MODIFY => 'Modify',
@@ -113,7 +113,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
protected static $m_aCacheActionGrants = null;
protected static $m_aCacheStimulusGrants = null;
protected static $m_aCacheProfiles = null;
public static function DoCreateProfile($sName, $sDescription, $bReservedName = false)
{
if (is_null(self::$m_aCacheProfiles))
@@ -125,7 +125,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheProfiles[$oProfile->Get('name')] = $oProfile->GetKey();
}
}
}
$sCacheKey = $sName;
if (isset(self::$m_aCacheProfiles[$sCacheKey]))
@@ -137,17 +137,17 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('description', $sDescription);
if ($bReservedName)
{
$oNewObj->DisableCheckOnReservedNames();
$oNewObj->DisableCheckOnReservedNames();
}
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheProfiles[$sCacheKey] = $iId;
self::$m_aCacheProfiles[$sCacheKey] = $iId;
return $iId;
}
public static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true)
{
$sAction = self::$m_aActions[$iAction];
if (is_null(self::$m_aCacheActionGrants))
{
self::$m_aCacheActionGrants = array();
@@ -157,7 +157,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheActionGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('action').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
}
}
}
$sCacheKey = "$iProfile-$sAction-$sClass";
if (isset(self::$m_aCacheActionGrants[$sCacheKey]))
@@ -171,10 +171,10 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('class', $sClass);
$oNewObj->Set('action', $sAction);
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheActionGrants[$sCacheKey] = $iId;
self::$m_aCacheActionGrants[$sCacheKey] = $iId;
return $iId;
}
public static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass)
{
if (is_null(self::$m_aCacheStimulusGrants))
@@ -186,7 +186,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
{
self::$m_aCacheStimulusGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('stimulus').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
}
}
}
$sCacheKey = "$iProfile-$sStimulusCode-$sClass";
if (isset(self::$m_aCacheStimulusGrants[$sCacheKey]))
@@ -199,13 +199,13 @@ class URP_Profiles extends UserRightsBaseClassGUI
$oNewObj->Set('class', $sClass);
$oNewObj->Set('stimulus', $sStimulusCode);
$iId = $oNewObj->DBInsertNoReload();
self::$m_aCacheStimulusGrants[$sCacheKey] = $iId;
self::$m_aCacheStimulusGrants[$sCacheKey] = $iId;
return $iId;
}
/*
* Create the built-in Administrator profile with its reserved name
*/
*/
public static function DoCreateAdminProfile()
{
self::DoCreateProfile(ADMIN_PROFILE_NAME, 'Has the rights on everything (bypassing any control)', true /* reserved name */);
@@ -213,7 +213,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
/*
* Overload the standard behavior to preserve reserved names
*/
*/
public function DoCheckToWrite()
{
parent::DoCheckToWrite();
@@ -255,7 +255,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
function DoShowGrantSumary($oPage)
{
if ($this->GetRawName() == "Administrator")
@@ -267,7 +267,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
// Note: for sure, we assume that the instance is derived from UserRightsProfile
$oUserRights = UserRights::GetModuleInstance();
$aDisplayData = array();
foreach (MetaModel::GetClasses('bizmodel') as $sClass)
{
@@ -284,7 +284,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
}
}
$sStimuli = implode(', ', $aStimuli);
$aDisplayData[] = array(
'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'),
@@ -296,7 +296,7 @@ class URP_Profiles extends UserRightsBaseClassGUI
'stimuli' => $sStimuli,
);
}
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
@@ -334,7 +334,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
"is_link" => true, /** @since 3.1.0 N°6482 */
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -611,7 +611,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$oUserOrgSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserOrg = $oUserOrgSet->Fetch())
{
@@ -633,7 +633,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
$oSearch->AllowAllData();
$oCondition = new BinaryExpression(new FieldExpression('userid'), '=', new VariableExpression('userid'));
$oSearch->AddConditionExpression($oCondition);
$this->m_aUserProfiles[$iUser] = array();
$oUserProfileSet = new DBObjectSet($oSearch, array(), array('userid' => $iUser));
while ($oUserProfile = $oUserProfileSet->Fetch())
@@ -648,7 +648,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
public function ResetCache()
{
// Loaded by Load cache
$this->m_aProfiles = null;
$this->m_aProfiles = null;
$this->m_aUserProfiles = array();
$this->m_aUserOrgs = array();
@@ -658,7 +658,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
// Loaded on demand (time consuming as compared to the others)
$this->m_aClassActionGrants = null;
$this->m_aClassStimulusGrants = null;
$this->m_aObjectActionGrants = array();
}
@@ -694,10 +694,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
{
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$this->m_aClassStimulusGrants = array();
@@ -871,7 +871,7 @@ exit;
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
return $aRes;
}
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
{
$this->LoadCache();
@@ -1009,8 +1009,8 @@ exit;
/**
* Find out which attribute is corresponding the the dimension 'owner org'
* returns null if no such attribute has been found (no filtering should occur)
*/
* returns null if no such attribute has been found (no filtering should occur)
*/
public static function GetOwnerOrganizationAttCode($sClass)
{
$sAttCode = null;

View File

@@ -22,9 +22,9 @@ define('ADMIN_PROFILE_ID', 1);
class UserRightsBaseClass extends cmdbAbstractObject
{
// Whenever something changes, reload the privileges
// Whenever something changes, reload the privileges
protected function AfterInsert()
{
UserRights::FlushPrivileges();
@@ -78,7 +78,7 @@ class URP_Profiles extends UserRightsBaseClass
function GetGrantAsHtml($oUserRights, $sClass, $sAction)
{
$oGrant = $oUserRights->GetClassActionGrant($this->GetKey(), $sClass, $sAction);
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
{
return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
}
@@ -87,7 +87,7 @@ class URP_Profiles extends UserRightsBaseClass
return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
}
}
function DoShowGrantSumary($oPage)
{
if ($this->GetRawName() == "Administrator")
@@ -99,7 +99,7 @@ class URP_Profiles extends UserRightsBaseClass
// Note: for sure, we assume that the instance is derived from UserRightsProjection
$oUserRights = UserRights::GetModuleInstance();
$aDisplayData = array();
foreach (MetaModel::GetClasses('bizmodel') as $sClass)
{
@@ -116,7 +116,7 @@ class URP_Profiles extends UserRightsBaseClass
}
}
$sStimuli = implode(', ', $aStimuli);
$aDisplayData[] = array(
'class' => MetaModel::GetName($sClass),
'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'),
@@ -128,7 +128,7 @@ class URP_Profiles extends UserRightsBaseClass
'stimuli' => $sStimuli,
);
}
$aDisplayConfig = array();
$aDisplayConfig['class'] = array('label' => Dict::S('UI:UserManagement:Class'), 'description' => Dict::S('UI:UserManagement:Class+'));
$aDisplayConfig['read'] = array('label' => Dict::S('UI:UserManagement:Action:Read'), 'description' => Dict::S('UI:UserManagement:Action:Read+'));
@@ -277,7 +277,7 @@ class URP_UserProfile extends UserRightsBaseClass
"db_table" => "priv_urp_userprofile",
"db_key_field" => "id",
"db_finalclass_field" => "",
"is_link" => true, /** @since 3.1.0 N°6482 N°5324 */
"is_link" => true, /** @since 3.1.0 N°6482 */
);
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
@@ -356,7 +356,7 @@ class URP_ProfileProjection extends UserRightsBaseClass
{
$aRes = array($oUser->Get($sColumn));
}
}
elseif (($sExpr == '<any>') || ($sExpr == ''))
{
@@ -427,14 +427,14 @@ class URP_ClassProjection extends UserRightsBaseClass
{
$aRes = array($oObject->Get($sColumn));
}
}
elseif (($sExpr == '<any>') || ($sExpr == ''))
{
$aRes = null;
}
elseif (strtolower(substr($sExpr, 0, 6)) == 'select')
{
{
$sColumn = $this->Get('attribute');
// SELECT...
$oValueSetDef = new ValueSetObjects($sExpr, $sColumn, array(), true /*allow all data*/);
@@ -585,14 +585,14 @@ class UserRightsProjection extends UserRightsAddOnAPI
$oContact->Set('org_id', $iOrgId);
$oContact->Set('email', 'my.email@foo.org');
$iContactId = $oContact->DBInsertNoReload();
$oUser = new UserLocal();
$oUser->Set('login', $sAdminUser);
$oUser->Set('password', $sAdminPwd);
$oUser->Set('contactid', $iContactId);
$oUser->Set('language', $sLanguage); // Language was chosen during the installation
$iUserId = $oUser->DBInsertNoReload();
// Add this user to the very specific 'admin' profile
$oUserProfile = new URP_UserProfile();
$oUserProfile->Set('userid', $iUserId);
@@ -643,24 +643,24 @@ class UserRightsProjection extends UserRightsAddOnAPI
// Could be loaded in a shared memory (?)
$oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Dimensions"));
$this->m_aDimensions = array();
$this->m_aDimensions = array();
while ($oDimension = $oDimensionSet->Fetch())
{
$this->m_aDimensions[$oDimension->GetKey()] = $oDimension;
$this->m_aDimensions[$oDimension->GetKey()] = $oDimension;
}
$oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ClassProjection"));
$this->m_aClassProjs = array();
$this->m_aClassProjs = array();
while ($oClassProj = $oClassProjSet->Fetch())
{
$this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj;
$this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj;
}
$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
$this->m_aProfiles = array();
$this->m_aProfiles = array();
while ($oProfile = $oProfileSet->Fetch())
{
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
$this->m_aProfiles[$oProfile->GetKey()] = $oProfile;
}
$oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserProfile"));
@@ -676,10 +676,10 @@ class UserRightsProjection extends UserRightsAddOnAPI
}
$oProProSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_ProfileProjection"));
$this->m_aProPros = array();
$this->m_aProPros = array();
while ($oProPro = $oProProSet->Fetch())
{
$this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro;
$this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro;
}
/*
@@ -707,7 +707,7 @@ exit;
// Authorize any for this dimension, then no additional criteria is required
continue;
}
// 1 - Get class projection info
//
$oExpression = null;
@@ -731,13 +731,13 @@ exit;
}
elseif (strtolower(substr($sExpr, 0, 6)) == 'select')
{
throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
throw new CoreException('Sorry, projections by the mean of OQL are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
}
else
{
// Constant value(s)
// unsupported
throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
throw new CoreException('Sorry, constant projections are not supported currently, please specify an attribute instead', array('class' => $sClass, 'expression' => $sExpr));
// $aRes = explode(';', trim($sExpr));
}
@@ -866,7 +866,7 @@ exit;
$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iObjectRef][$iActionCode] = $aRes;
return $aRes;
}
public function IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet = null)
{
if (is_null($oInstanceSet))
@@ -934,7 +934,7 @@ exit;
}
else
{
$iInstancePermission = UR_ALLOWED_NO;
$iInstancePermission = UR_ALLOWED_NO;
}
if (isset($iGlobalPermission))
@@ -1140,7 +1140,7 @@ exit;
}
protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object
protected function GetMatchingProfiles($oUser, $sClass, /*DBObject*/ $oObject = null)
{
$iUser = $oUser->GetKey();
@@ -1186,7 +1186,7 @@ exit;
@$aProfileRes[$iProfile] += 1;
}
}
$aRes = array();
$iDimCount = count($this->m_aDimensions);
foreach ($aProfileRes as $iProfile => $iMatches)
@@ -1200,7 +1200,7 @@ exit;
// store into the cache
$this->m_aMatchingProfiles[$iUser][$sClass][$iObjectRef] = $aRes;
return $aRes;
return $aRes;
}
public function FlushPrivileges()

View File

@@ -2247,49 +2247,6 @@ interface iModuleExtension
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
*
@@ -2297,19 +2254,19 @@ abstract class AbstractWelcomePopup implements iWelcomePopup
*/
interface iKPILoggerExtension
{
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Init the statistics collected
*
* @return void
*/
public function InitStats();
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
*
* @return mixed
*/
public function LogOperation($oKpiLogData);
}
/**
* Add a new KPI to the stats
*
* @param \Combodo\iTop\Core\Kpi\KpiLogData $oKpiLogData
*
* @return mixed
*/
public function LogOperation($oKpiLogData);
}

View File

@@ -34,15 +34,15 @@ class AuditCategory extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application,grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_auditcategory",
"db_key_field" => "id",
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",
"reconc_keys" => array('name'),
"db_table" => "priv_auditcategory",
"db_key_field" => "id",
"db_finalclass_field" => "",
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
'style' => new ormStyle(null, null, null, null, null, '../images/icons/icons8-audit-folder.svg'),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_AddAttribute(new AttributeString("name", array("description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));

View File

@@ -35,7 +35,7 @@ class AuditDomain extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application,grant_by_profile",
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"complementary_name_attcode" => array('description'),

View File

@@ -35,7 +35,7 @@ class AuditRule extends cmdbAbstractObject
{
$aParams = array
(
"category" => "application,grant_by_profile",
"category" => "application, grant_by_profile",
"key_type" => "autoincrement",
"name_attcode" => "name",
"state_attcode" => "",

View File

@@ -918,7 +918,7 @@ class RuntimeDashboard extends Dashboard
{
$bCustomized = false;
$sDashboardFileSanitized = utils::RealPath(APPROOT.$sDashboardFile, APPROOT);
$sDashboardFileSanitized = utils::RealPath($sDashboardFile, APPROOT);
if (false === $sDashboardFileSanitized) {
throw new SecurityException('Invalid dashboard file !');
}
@@ -1141,7 +1141,7 @@ JS
$oToolbar->AddSubBlock($oActionButton);
$aActions = array();
$sFile = addslashes(utils::LocalPath($this->sDefinitionFile));
$sFile = addslashes($this->sDefinitionFile);
$sJSExtraParams = json_encode($aExtraParams);
if ($this->HasCustomDashboard()) {
$oEdit = new JSPopupMenuItem('UI:Dashboard:Edit', Dict::S('UI:Dashboard:EditCustom'), "return EditDashboard('{$this->sId}', '$sFile', $sJSExtraParams)");

View File

@@ -40,36 +40,6 @@
<presentation/>
<methods/>
</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>
<portals>
<portal id="backoffice" _delta="define">

View File

@@ -1246,10 +1246,6 @@ JS
} else {
$oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams);
}
$sClassDescription = MetaModel::GetClassDescription($sClass);
if (utils::IsNotNullOrEmptyString($sClassDescription)) {
$oBlock->SetClassDescription($sClassDescription);
}
return $oBlock;
}

View File

@@ -248,7 +248,6 @@ class LoginWebPage extends NiceWebPage
$oEmail = new Email();
$oEmail->SetRecipientTO($sTo);
$sFrom = MetaModel::GetConfig()->Get('forgot_password_from');
$sFrom = utils::IsNullOrEmptyString($sFrom) ? MetaModel::GetConfig()->Get('email_default_sender_address') : $sFrom;
$oEmail->SetRecipientFrom($sFrom);
$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);

View File

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

View File

@@ -296,7 +296,7 @@ class QueryOQL extends Query
}
catch
(OQLException $e) {
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::S('UI:RunQuery:Error'), $e->getHtmlDesc())
$oAlert = AlertUIBlockFactory::MakeForFailure(Dict::Format('UI:RunQuery:Error'), $e->getHtmlDesc())
->SetIsClosable(false)
->SetIsCollapsible(false);
$oAlert->AddCSSClass('mb-5');

View File

@@ -0,0 +1,29 @@
<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>

View File

@@ -163,7 +163,7 @@ class UIExtKeyWidget
$oPage->add_linked_script('../js/extkeywidget.js');
$oPage->add_linked_script('../js/forms-json-utils.js');
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_MODIFY) && $bAllowTargetCreation);
$bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
$bExtensions = true;
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';

View File

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

View File

@@ -650,9 +650,6 @@ class ActionEmail extends ActionNotification
$aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']';
$aMessageContent['body'] = $sTestBody;
$aMessageContent['to'] = $this->Get('test_recipient');
// N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses
$aMessageContent['cc'] = '';
$aMessageContent['bcc'] = '';
}
// Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification
$aMessageContent['in_reply_to'] = $aMessageContent['references'];

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -380,7 +380,7 @@ class CMDBSource
public static function GetDBVendor()
{
$sDBVendor = static::ENUM_DB_VENDOR_MYSQL;
$sVersionComment = static::GetServerVariable('version') . ' - ' . static::GetServerVariable('version_comment');
if(preg_match('/mariadb/i', $sVersionComment) === 1)
{
@@ -390,7 +390,7 @@ class CMDBSource
{
$sDBVendor = static::ENUM_DB_VENDOR_PERCONA;
}
return $sDBVendor;
}
@@ -934,7 +934,7 @@ class CMDBSource
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
while ($aRow = $oResult->fetch_array($iMode))
{
$aData[] = $aRow;
@@ -1088,7 +1088,7 @@ class CMDBSource
if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false;
$aFieldData = $aTableInfo["Fields"][$iKey];
if (!array_key_exists("Key", $aFieldData)) return false;
return ($aFieldData["Key"] == "PRI");
return ($aFieldData["Key"] == "PRI");
}
public static function IsAutoIncrement($sTable, $sField)
@@ -1099,7 +1099,7 @@ class CMDBSource
$aFieldData = $aTableInfo["Fields"][$sField];
if (!array_key_exists("Extra", $aFieldData)) return false;
//MyHelpers::debug_breakpoint($aFieldData);
return (strstr($aFieldData["Extra"], "auto_increment"));
return (strstr($aFieldData["Extra"], "auto_increment"));
}
public static function IsField($sTable, $sField)
@@ -1366,13 +1366,13 @@ class CMDBSource
public static function GetTableFieldsList($sTable)
{
assert(!empty($sTable));
$aTableInfo = self::GetTableInfo($sTable);
if (empty($aTableInfo)) return array(); // #@# or an error ?
return array_keys($aTableInfo["Fields"]);
}
// Cache the information about existing tables, and their fields
private static $m_aTablesInfo = array();
private static function _TablesInfoCacheReset($sTableName = null)
@@ -1505,7 +1505,7 @@ class CMDBSource
{
throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
}
$aRows = array();
while ($aRow = $oResult->fetch_array(MYSQLI_ASSOC))
{
@@ -1514,7 +1514,7 @@ class CMDBSource
$oResult->free();
return $aRows;
}
/**
* Returns the value of the specified server variable
* @param string $sVarName Name of the server variable
@@ -1530,7 +1530,7 @@ class CMDBSource
/**
* Returns the privileges of the current user
* @return string privileges in a raw format
*/
*/
public static function GetRawPrivileges()
{
try
@@ -1556,8 +1556,8 @@ class CMDBSource
/**
* Determine the slave status of the server
* @return bool true if the server is slave
*/
* @return bool true if the server is slave
*/
public static function IsSlaveServer()
{
try

View File

@@ -1193,30 +1193,6 @@ class Config
'source_of_value' => '',
'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' => [
'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
@@ -1377,14 +1353,6 @@ class Config
'source_of_value' => '',
'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' => [
'type' => 'bool',
'description' => 'Whether or not the quick create is enabled',
@@ -1667,14 +1635,6 @@ class Config
'source_of_value' => '',
'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' => [
'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)',

View File

@@ -219,19 +219,6 @@
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="AuditDomain" _delta="define">
<parent>cmdbAbstractObject</parent>
<properties>
<category>application, grant_by_profile</category>
</properties>
<fields>
<field id="name" xsi:type="AttributeString"/>
<field id="description" xsi:type="AttributeString"/>
<field id="icon" xsi:type="AttributeImage"/>
<field id="categories_list" xsi:type="AttributeLinkedSet"/>
<field id="friendlyname" xsi:type="AttributeFriendlyName"/>
</fields>
</class>
<class id="Query" _delta="define">
<!-- Generated by toolkit/export-class-to-meta.php -->
<parent>cmdbAbstractObject</parent>

View File

@@ -6,9 +6,7 @@
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventException;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\EventServiceLog;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
/**
@@ -59,7 +57,7 @@ require_once('mutex.class.inc.php');
/**
* A persistent object, as defined by the metamodel
* A persistent object, as defined by the metamodel
*
* @package iTopORM
* @api
@@ -205,8 +203,6 @@ abstract class DBObject implements iDisplay
const MAX_UPDATE_LOOP_COUNT = 10;
private $aEventListeners = [];
/**
* DBObject constructor.
*
@@ -259,10 +255,6 @@ abstract class DBObject implements iDisplay
$this->RegisterEventListeners();
}
/**
* @see RegisterCRUDListener
* @see EventService::RegisterListener()
*/
protected function RegisterEventListeners()
{
}
@@ -307,9 +299,9 @@ abstract class DBObject implements iDisplay
/**
* Whether the object is already persisted in DB or not.
*
*
* @api
*
*
* @return bool
*/
public function IsNew()
@@ -319,9 +311,9 @@ abstract class DBObject implements iDisplay
/**
* Returns an Id for memory objects
*
*
* @internal
*
*
* @param string $sClass
*
* @return int
@@ -358,7 +350,7 @@ abstract class DBObject implements iDisplay
$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey ($sFriendlyname)<br/>\n";
return $sRet;
}
/**
* Alias of DBObject::Reload()
*
@@ -381,7 +373,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see m_bFullyLoaded
*
*
* @return bool
* @throws CoreException
*/
@@ -504,7 +496,7 @@ abstract class DBObject implements iDisplay
{
$aAttList = $aAttToLoad[$sClassAlias];
}
foreach($aAttList as $sAttCode=>$oAttDef)
{
// Skip links (could not be loaded by the mean of this query)
@@ -564,7 +556,7 @@ abstract class DBObject implements iDisplay
$bFullyLoaded = false;
}
}
// Load extended data
if ($aExtendedDataSpec != null)
{
@@ -588,7 +580,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Set()
*
*
* @param string $sAttCode
* @param mixed $value
*/
@@ -793,11 +785,11 @@ abstract class DBObject implements iDisplay
/**
* Get the label of an attribute.
*
*
* Shortcut to the field's AttributeDefinition->GetLabel()
*
* @api
*
*
* @param string $sAttCode
*
* @return string
@@ -870,7 +862,7 @@ abstract class DBObject implements iDisplay
*
* @internal
* @see Get
*
*
* @param string $sAttCode
*
* @return int|mixed|null
@@ -975,7 +967,7 @@ abstract class DBObject implements iDisplay
* Returns the default value of the $sAttCode.
*
* Returns the default value of the given attribute.
*
*
* @internal
*
* @param string $sAttCode
@@ -996,12 +988,12 @@ abstract class DBObject implements iDisplay
* @internal
*
* @return array|null
*/
*/
public function GetExtendedData()
{
return $this->m_aExtendedData;
}
/**
* Set the HighlightCode
*
@@ -1023,7 +1015,7 @@ abstract class DBObject implements iDisplay
{
$fCurrentRank = $aHighlightScale[$this->m_sHighlightCode]['rank'];
}
if (array_key_exists($sCode, $aHighlightScale))
{
$fRank = $aHighlightScale[$sCode]['rank'];
@@ -1033,13 +1025,13 @@ abstract class DBObject implements iDisplay
}
}
}
/**
* Get the current HighlightCode
*
*
* @internal
* @used-by DBObject::ComputeHighlightCode()
*
*
* @return string|null The Hightlight code (null if none set, meaning rank = 0)
*/
protected function GetHighlightCode()
@@ -1088,7 +1080,7 @@ abstract class DBObject implements iDisplay
* corresponding to the external key and getting the value from it
*
* UNUSED ?
*
*
* @internal
* @todo: check if this is dead code.
*
@@ -1159,7 +1151,7 @@ abstract class DBObject implements iDisplay
/**
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1205,11 +1197,11 @@ abstract class DBObject implements iDisplay
/**
* Get the value as it must be in the edit areas (forms)
*
*
* Makes a raw text representation of the value.
*
* @internal
*
*
* @param string $sAttCode
*
* @return int|mixed|string
@@ -1239,7 +1231,7 @@ abstract class DBObject implements iDisplay
else
{
$sEditValue = 0;
}
}
}
else
{
@@ -1255,14 +1247,14 @@ abstract class DBObject implements iDisplay
/**
* Get $sAttCode formatted as XML
*
*
* The returned value is a text that is suitable for insertion into an XML node.
* Depending on the type of attribute, the returned text is either:
* * A literal, with XML entities already escaped,
* * XML
*
* @api
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1300,10 +1292,10 @@ abstract class DBObject implements iDisplay
}
/**
*
*
* @see GetAsHTML()
* @see GetOriginal()
*
*
* @param string $sAttCode
* @param bool $bLocalize
*
@@ -1478,7 +1470,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @param string $sClass
*
* @return mixed
@@ -1533,7 +1525,7 @@ abstract class DBObject implements iDisplay
* Get the id
*
* @api
*
*
* @return string|null
*/
public function GetKey()
@@ -1544,7 +1536,7 @@ abstract class DBObject implements iDisplay
/**
* Primary key Setter
* Usable only for not yet persisted DBObjects
*
*
* @internal
*
* @param int $iNewKey the desired identifier
@@ -1557,7 +1549,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("An object id must be an integer value ($iNewKey)");
}
if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey))
{
throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database");
@@ -1567,7 +1559,7 @@ abstract class DBObject implements iDisplay
/**
* Get the icon representing this object
*
*
* @api
*
* @param boolean $bImgTag If true the result is a full IMG tag (or an empty string if no icon is defined)
@@ -1657,7 +1649,7 @@ abstract class DBObject implements iDisplay
*
* Returns the label as defined in the dictionary for the language of the current user
*
* @api
* @api
*
* @return string (empty for default name scheme)
*/
@@ -1724,7 +1716,7 @@ abstract class DBObject implements iDisplay
/**
* Helper to get the state
*
*
* @api
*
* @return mixed|string '' if no state attribute, object representing its value otherwise
@@ -1746,9 +1738,9 @@ abstract class DBObject implements iDisplay
/**
* Get the label (raw text) of the current state
* helper for MetaModel::GetStateLabel()
*
*
* @api
*
*
* @return mixed|string
*
* @throws ArchivedObjectException
@@ -1795,7 +1787,7 @@ abstract class DBObject implements iDisplay
* Define attributes read-only from the end-user perspective
*
* @return array|null List of attcodes
*/
*/
public static function GetReadOnlyAttributes()
{
return null;
@@ -1804,14 +1796,14 @@ abstract class DBObject implements iDisplay
/**
* Get predefined objects
*
*
* The predefined objects will be synchronized with the DB at each install/upgrade
* As soon as a class has predefined objects, then nobody can create nor delete objects
*
* @internal
*
* @return array An array of id => array of attcode => php value(so-called "real value": integer, string, ormDocument, DBObjectSet, etc.)
*/
*/
public static function GetPredefinedObjects()
{
return null;
@@ -1940,7 +1932,7 @@ abstract class DBObject implements iDisplay
* Note: Attributes (and flags) from the target state and the transition are combined.
*
* @internal
*
*
* @param string $sStimulus
* @param string $sOriginState Default is current state
*
@@ -2147,7 +2139,7 @@ abstract class DBObject implements iDisplay
/**
* @internal
*
*
* @throws \CoreException
* @throws \OQLException
*
@@ -2519,7 +2511,7 @@ abstract class DBObject implements iDisplay
*
* an array of displayable error is added in {@see DBObject::$m_aDeleteIssues}
*
* @internal
* @internal
*
* @param \DeletionPlan $oDeletionPlan
*
@@ -2636,7 +2628,7 @@ abstract class DBObject implements iDisplay
{
// The value is a scalar, the comparison must be 100% strict
if($this->m_aOrigValues[$sAtt] !== $proposedValue)
{
{
//echo "$sAtt:<pre>\n";
//var_dump($this->m_aOrigValues[$sAtt]);
//var_dump($proposedValue);
@@ -2758,7 +2750,7 @@ abstract class DBObject implements iDisplay
/**
* Used only by insert, Meant to be overloaded
*
*
* @overwritable-hook You can extend this method in order to provide your own logic.
*/
protected function OnObjectKeyReady()
@@ -2866,7 +2858,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -2875,7 +2867,7 @@ abstract class DBObject implements iDisplay
}
$aHierarchicalKeys = array();
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) {
// Skip this attribute if not defined in this table
if ((!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode) && !$oAttDef->CopyOnAllTables())
@@ -2885,7 +2877,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
foreach($aAttColumns as $sColumn => $sValue)
{
$aFieldsToWrite[] = "`$sColumn`";
$aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -2909,7 +2901,7 @@ abstract class DBObject implements iDisplay
self::$m_aBulkInsertCols[$sClass][$sTable] = implode(', ', $aFieldsToWrite);
}
self::$m_aBulkInsertItems[$sClass][$sTable][] = '('.implode (', ', $aValuesToWrite).')';
$iNewKey = 999999; // TODO - compute next id....
}
else
@@ -2994,7 +2986,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -3029,7 +3021,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($value);
foreach($aAttColumns as $sColumn => $sValue)
{
$aFieldsToWrite[] = "`$sColumn`";
$aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -3169,10 +3161,6 @@ abstract class DBObject implements iDisplay
// First query built upon on the root class, because the ID must be created first
$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
if ($sClass != $sRootClass) {
$this->DBInsertSingleTable($sClass);
@@ -3224,7 +3212,6 @@ abstract class DBObject implements iDisplay
}
}
$this->SetReadWrite();
$this->m_bIsInDB = true;
$this->m_bDirty = false;
foreach ($this->m_aCurrValues as $sAttCode => $value) {
@@ -3235,7 +3222,7 @@ abstract class DBObject implements iDisplay
}
// Prevent DBUpdate at this point (reentrance protection)
//MetaModel::StartReentranceProtection($this);
MetaModel::StartReentranceProtection($this);
try {
$this->PostInsertActions();
@@ -3314,7 +3301,7 @@ abstract class DBObject implements iDisplay
$this->RecordObjCreation();
return $ret;
}
/**
* This function is automatically called after cloning an object with the "clone" PHP language construct
* The purpose of this method is to reset the appropriate attributes of the object in
@@ -3405,7 +3392,6 @@ abstract class DBObject implements iDisplay
}
}
$this->SetReadOnly('No modification allowed during transaction');
$iTransactionRetry = 1;
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
if ($bIsTransactionEnabled) {
@@ -3520,8 +3506,6 @@ abstract class DBObject implements iDisplay
// 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)
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
$this->SetReadWrite();
$this->m_bDirty = false;
$this->m_aTouchedAtt = array();
$this->m_aModifiedAtt = array();
@@ -3590,7 +3574,8 @@ abstract class DBObject implements iDisplay
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// - TriggerOnObjectUpdate
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
$aClassList = MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL);
$aParams = array('class_list' => $aClassList);
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)'),
array(), $aParams);
while ($oTrigger = $oSet->Fetch()) {
@@ -3604,6 +3589,44 @@ abstract class DBObject implements iDisplay
}
}
$sClass = get_class($this);
if (MetaModel::HasLifecycle($sClass))
{
$sStateAttCode = MetaModel::GetStateAttributeCode($sClass);
if (isset($this->m_aPreviousValuesForUpdatedAttributes[$sStateAttCode])) {
$sPreviousState = $this->m_aPreviousValuesForUpdatedAttributes[$sStateAttCode];
// Change state triggers...
$aParams = array(
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
'previous_state' => $sPreviousState,
'new_state' => $this->Get($sStateAttCode),
);
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state'), array(), $aParams);
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateLeave $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state'), array(), $aParams);
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateEnter $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
// Activate any existing trigger
// - TriggerOnObjectMention
// Forgotten by the fix of N°3245
@@ -3941,7 +3964,7 @@ abstract class DBObject implements iDisplay
* First, checks if the object can be deleted regarding database integrity.
* If the answer is yes, it performs any required cleanup (delete other objects or reset external keys) in addition to the object
* deletion.
*
*
* @api
*
* @param \DeletionPlan $oDeletionPlan Do not use: aims at dealing with recursion
@@ -4299,36 +4322,6 @@ abstract class DBObject implements iDisplay
$this->DBWrite();
}
// Change state triggers...
$aParams = array(
'class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL),
'previous_state' => $sPreviousState,
'new_state' => $sNewState,
);
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateLeave AS t WHERE t.target_class IN (:class_list) AND t.state=:previous_state"), array(), $aParams);
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateLeave $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnStateEnter AS t WHERE t.target_class IN (:class_list) AND t.state=:new_state"), array(), $aParams);
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnStateEnter $oTrigger */
try {
$oTrigger->DoActivate($this->ToArgs('this'));
}
catch (Exception $e) {
$oTrigger->LogException($e, $this);
utils::EnrichRaisedException($oTrigger, $e);
}
}
$this->FireEvent(EVENT_DB_AFTER_APPLY_STIMULUS, $aEventData);
}
else
@@ -4364,7 +4357,7 @@ abstract class DBObject implements iDisplay
*
* @api
*
*/
*/
public function Reset($sAttCode)
{
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
@@ -4376,7 +4369,7 @@ abstract class DBObject implements iDisplay
* Suitable for use as a lifecycle action
*
* @api
*/
*/
public function Copy($sDestAttCode, $sSourceAttCode)
{
$oTypeValueToCopy = MetaModel::GetAttributeDef(get_class($this), $sSourceAttCode);
@@ -4706,7 +4699,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
}
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
if (!$oKeyAttDef instanceof AttributeExternalKey)
{
@@ -4724,14 +4717,14 @@ abstract class DBObject implements iDisplay
$ret = $oRemoteObj->GetForTemplate($sRemoteAttCode);
}
}
else
else
{
switch($sPlaceholderAttCode)
{
case 'id':
$ret = $this->GetKey();
break;
case 'name()':
$ret = $this->GetName();
break;
@@ -4918,7 +4911,7 @@ abstract class DBObject implements iDisplay
if ($oOwner)
{
$sLinkSetOwnerClass = get_class($oOwner);
$oMyChangeOp = MetaModel::NewObject($sChangeOpClass);
$oMyChangeOp->Set("objclass", $sLinkSetOwnerClass);
$oMyChangeOp->Set("objkey", $iLinkSetOwnerId);
@@ -4945,7 +4938,7 @@ abstract class DBObject implements iDisplay
{
/** @var \AttributeLinkedSet $oLinkSet */
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_LIST) == 0) continue;
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove');
if ($oMyChangeOp)
@@ -5015,7 +5008,7 @@ abstract class DBObject implements iDisplay
// Keep track of link changes
//
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_DETAILS) == 0) continue;
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksTune');
if ($oMyChangeOp)
@@ -5164,7 +5157,7 @@ abstract class DBObject implements iDisplay
$this->FireEventCheckToDelete($oDeletionPlan);
$this->DoCheckToDelete($oDeletionPlan);
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
// Getting and setting time limit are not symmetric:
@@ -5346,7 +5339,7 @@ abstract class DBObject implements iDisplay
$aSynchroClasses[] = $sTarget;
}
}
foreach($aSynchroClasses as $sClass)
{
if ($this instanceof $sClass)
@@ -6197,51 +6190,6 @@ abstract class DBObject implements iDisplay
return OPT_ATT_NORMAL;
}
public final function GetListeners(): array
{
$aListeners = [];
foreach ($this->aEventListeners as $aEventListener) {
$aListeners = array_merge($aListeners, $aEventListener);
}
return $aListeners;
}
/**
* Register a callback for a specific event. The method to call will be saved in the object instance itself whereas calling {@see EventService::RegisterListener()} would
* save a callable (thus the method name AND the whole DBObject instance)
*
* @param string $sEvent corresponding event
* @param string $callback The callback method to call
* @param float $fPriority optional priority for callback order
* @param string $sModuleId
*
* @see EventService::RegisterListener()
*
* @since 3.1.0-3 3.1.1 3.2.0 N°6716
*/
final protected function RegisterCRUDListener(string $sEvent, string $callback, float $fPriority = 0.0, string $sModuleId = '')
{
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
$aEventCallbacks[] = array(
'event' => $sEvent,
'callback' => $callback,
'priority' => $fPriority,
'module' => $sModuleId,
);
usort($aEventCallbacks, function ($a, $b) {
$fPriorityA = $a['priority'];
$fPriorityB = $b['priority'];
if ($fPriorityA == $fPriorityB) {
return 0;
}
return ($fPriorityA < $fPriorityB) ? -1 : 1;
});
$this->aEventListeners[$sEvent] = $aEventCallbacks;
}
/**
* @param string $sEvent
* @param array $aEventData
@@ -6253,53 +6201,15 @@ abstract class DBObject implements iDisplay
*/
public function FireEvent(string $sEvent, array $aEventData = array()): void
{
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
$aEventData['object'] = $this;
// Call local listeners first
$aEventCallbacks = $this->aEventListeners[$sEvent] ?? [];
$oFirstException = null;
$sFirstExceptionMessage = '';
foreach ($aEventCallbacks as $aEventCallback) {
$oKPI = new ExecutionKPI();
$sCallback = $aEventCallback['callback'];
if (!method_exists($this, $sCallback)) {
EventServiceLog::Error("Callback '".get_class($this).":$sCallback' does not exist");
continue;
}
EventServiceLog::Debug("Fire event '$sEvent' calling '".get_class($this).":$sCallback'");
try {
call_user_func([$this, $sCallback], new EventData($sEvent, null, $aEventData));
}
catch (EventException $e) {
EventServiceLog::Error("Event '$sEvent' for '$sCallback'} failed with blocking error: ".$e->getMessage());
throw $e;
}
catch (Exception $e) {
$sMessage = "Event '$sEvent' for '$sCallback'} failed with non-blocking error: ".$e->getMessage();
EventServiceLog::Error($sMessage);
if (is_null($oFirstException)) {
$sFirstExceptionMessage = $sMessage;
$oFirstException = $e;
}
}
finally {
$oKPI->ComputeStats('FireEvent', $sEvent);
if (EventService::IsEventRegistered($sEvent)) {
$aEventData['debug_info'] = 'from: '.get_class($this).':'.$this->GetKey();
$aEventData['object'] = $this;
$aEventSources = [$this->m_sObjectUniqId];
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
$aEventSources[] = $sClass;
}
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
}
if (!is_null($oFirstException)) {
throw new Exception($sFirstExceptionMessage, $oFirstException->getCode(), $oFirstException);
}
// Call global event listeners
if (!EventService::IsEventRegistered($sEvent)) {
return;
}
$aEventSources = [];
foreach (MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL, false) as $sClass) {
$aEventSources[] = $sClass;
}
EventService::FireEvent(new EventData($sEvent, $aEventSources, $aEventData));
}
//////////////////

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -576,11 +576,6 @@ class LogChannels
public const DATATABLE = 'Datatable';
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

View File

@@ -1241,7 +1241,7 @@ abstract class MetaModel
}
$sTable = self::DBGetTable($sClass);
// Could be completed later with all the classes that are using a given table
// Could be completed later with all the classes that are using a given table
if (!array_key_exists($sTable, $aTables)) {
$aTables[$sTable] = array();
}
@@ -3522,7 +3522,7 @@ abstract class MetaModel
}
// Set the "host class" as soon as possible, since HierarchicalKeys use it for their 'target class' as well
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
// and this needs to be know early (for Init_IsKnowClass 19 lines below)
$oAtt->SetHostClass($sTargetClass);
// Some attributes could refer to a class
@@ -3564,7 +3564,7 @@ abstract class MetaModel
self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt;
self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass;
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
}
/**
@@ -3764,7 +3764,7 @@ abstract class MetaModel
self::$m_aStimuli[$sTargetClass][$oStimulus->GetCode()] = $oStimulus;
// I wanted to simplify the syntax of the declaration of objects in the biz model
// Therefore, the reference to the host class is set there
// Therefore, the reference to the host class is set there
$oStimulus->SetHostClass($sTargetClass);
}
@@ -4219,77 +4219,40 @@ abstract class MetaModel
}
else
{
$aCurrentUser = [];
$aCurrentContact = [];
$aCurrentUser = array();
$aCurrentContact = array();
foreach ($aExpectedArgs as $expression)
{
$aName = explode('->', $expression->GetName());
if ($aName[0] == 'current_contact_id') {
$aPlaceholders['current_contact_id'] = UserRights::GetContactId();
} else if ($aName[0] == 'current_user') {
}
if ($aName[0] == 'current_user') {
array_push($aCurrentUser, $aName[1]);
} else if ($aName[0] == 'current_contact') {
}
if ($aName[0] == 'current_contact') {
array_push($aCurrentContact, $aName[1]);
}
}
if (count($aCurrentUser) > 0) {
static::FillObjectPlaceholders($aPlaceholders, 'current_user', UserRights::GetUserObject(), $aCurrentUser);
$oUser = UserRights::GetUserObject();
$aPlaceholders['current_user->object()'] = $oUser;
foreach ($aCurrentUser as $sField) {
$aPlaceholders['current_user->'.$sField] = $oUser->Get($sField);
}
}
if (count($aCurrentContact) > 0) {
static::FillObjectPlaceholders($aPlaceholders, 'current_contact', UserRights::GetContactObject(), $aCurrentContact);
$oPerson = UserRights::GetContactObject();
$aPlaceholders['current_contact->object()'] = $oPerson;
foreach ($aCurrentContact as $sField) {
$aPlaceholders['current_contact->'.$sField] = $oPerson->Get($sField);
}
}
}
return $aPlaceholders;
}
/**
* @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
*
@@ -6516,7 +6479,7 @@ abstract class MetaModel
$aCache['m_aExtensionClassNames'] = self::$m_aExtensionClassNames;
$aCache['m_Category2Class'] = self::$m_Category2Class;
$aCache['m_aRootClasses'] = self::$m_aRootClasses; // array of "classname" => "rootclass"
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
$aCache['m_aParentClasses'] = self::$m_aParentClasses; // array of ("classname" => array of "parentclass")
$aCache['m_aChildClasses'] = self::$m_aChildClasses; // array of ("classname" => array of "childclass")
$aCache['m_aClassParams'] = self::$m_aClassParams; // array of ("classname" => array of class information)
$aCache['m_aAttribDefs'] = self::$m_aAttribDefs; // array of ("classname" => array of attributes)
@@ -7623,20 +7586,6 @@ abstract class MetaModel
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
*

View File

@@ -20,8 +20,6 @@ $ibo-dashlet-badge--icon--size: 48px !default;
$ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default;
$ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !default;
/* CSS variables (can be changed directly from the browser) */
:root {
--ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width};
@@ -76,27 +74,18 @@ $ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !defaul
@extend %ibo-hyperlink-inherited-colors;
}
}
.ibo-dashlet-badge--action-list-count {
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
@extend %ibo-font-ral-bol-450;
.ibo-dashlet-badge--action-list-count{
margin-right: $ibo-dashlet-badge--action-list-count--margin-right;
@extend %ibo-font-ral-bol-450;
}
.ibo-dashlet-badge--action-list-label {
display: inline-block;
@extend %ibo-text-truncated-with-ellipsis;
.ibo-dashlet-badge--action-list-label{
display: inline-block;
@extend %ibo-text-truncated-with-ellipsis;
}
.ibo-dashlet-badge--action-create {
@extend %ibo-baseline-centered-content;
@extend %ibo-font-size-150;
.ibo-dashlet-badge--action-create{
@extend %ibo-baseline-centered-content;
@extend %ibo-font-size-150;
}
.ibo-dashlet-badge--action-create-icon {
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
}
.ibo-dashlet-badge--body--tooltip-title {
@extend %ibo-font-weight-600;
margin-bottom: $ibo-dashlet-badge--body--tooltip-title--margin-bottom;
.ibo-dashlet-badge--action-create-icon{
margin-right: $ibo-dashlet-badge--action-icon--margin-right;
}

View File

@@ -51,4 +51,4 @@ tr.ibo-csv-import--row-added td {
font-size: $ibo-csv-import--download-file--font-size;
color: $ibo-csv-import--download-file--color;
margin: $ibo-csv-import--download-file--margin;
}
}

View File

@@ -17,9 +17,7 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
#welcome_popup{
display: flex;
}
.ibo-welcome-popup--columns{
display: flex;
}
.ibo-welcome-popup--image{
display: flex;
@@ -46,39 +44,7 @@ $ibo-welcome-popup--text--options--bottom: 10px !default;
}
}
}
.ibo-welcome-popup--dialog {
width: 60rem;
}
.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;
}
.ibo-welcome-popup--text--options{
position: absolute;
bottom: $ibo-welcome-popup--text--options--bottom;
}

File diff suppressed because one or more lines are too long

View File

@@ -344,7 +344,6 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
while ($oAttachment = $oSet->Fetch())
{
$oTempAttachment = clone $oAttachment;
$oTempAttachment->Set('expire', time() + utils::GetConfig()->Get('draft_attachments_lifetime'));
$oTempAttachment->Set('item_id', null);
$oTempAttachment->Set('temp_id', $sTempId);
$oTempAttachment->DBInsert();

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -34,7 +34,7 @@ class DBRestore extends DBBackup
protected function LogInfo($sMsg)
{
IssueLog::Info('non juste info: '.$sMsg);
//IssueLog::Info('non juste info: '.$sMsg);
}
protected function LogError($sMsg)
@@ -91,7 +91,7 @@ class DBRestore extends DBBackup
{
$this->LogError("mysql said: $sLine");
}
if (count($aOutput) == 1)
if (count($aOutput) == 1)
{
$sMoreInfo = trim($aOutput[0]);
}
@@ -187,7 +187,7 @@ class DBRestore extends DBBackup
@chmod($sConfigFile, 0770); // Allow overwriting the file
rename($sDataDir.'/config-itop.php', $sConfigFile);
@chmod($sConfigFile, 0440); // Read-only
$aExtraFiles = $this->ListExtraFiles($sDataDir);
foreach($aExtraFiles as $sSourceFilePath => $sDestinationFilePath) {
SetupUtils::builddir(dirname($sDestinationFilePath));

View File

@@ -88,7 +88,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
// Errors
'iTopUpdate:Error:MissingFunction' => 'Lehetetlen elindítani a frissítést, hiányzó funkció',
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$s',
'iTopUpdate:Error:MissingFile' => 'Hiányzó fájl: %1$',
'iTopUpdate:Error:CorruptedFile' => 'A %1$s fájl sérült',
'iTopUpdate:Error:BadFileFormat' => 'A frissítési fájl nem zip fájl',
'iTopUpdate:Error:BadFileContent' => 'A frissítési fájl nem alkalmazás archívum',

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<itop_design version="3.1">
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1">
<classes/>
<user_rights>
<groups>
@@ -125,7 +125,6 @@
<group id="Audit" _delta="define">
<classes>
<!-- This class list is also present in AdminTools group -->
<class id="AuditDomain"/>
<class id="AuditCategory"/>
<class id="AuditRule"/>
<class id="ResourceRunQueriesMenu"/>
@@ -167,7 +166,6 @@
<class id="URP_UserProfile"/>
<class id="URP_Profiles"/>
<!-- Audit group -->
<class id="AuditDomain"/>
<class id="AuditCategory"/>
<class id="AuditRule"/>
<!-- Query group -->
@@ -546,6 +544,5 @@
<groups/>
</profile>
</profiles>
<dictionaries/>
</user_rights>
</itop_design>

View File

@@ -1,13 +0,0 @@
<?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.',
));

View File

@@ -1,13 +0,0 @@
<?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.',
));

View File

@@ -36,7 +36,6 @@ SetupWebPage::AddModule(
// Components
//
'datamodel' => array(
'src/UserProfilesEventListener.php'
),
'webservice' => array(
//'webservices.itop-profiles-itil.php',

View File

@@ -1,394 +0,0 @@
<?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);
}
}
}
}

View File

@@ -99,9 +99,9 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:Contract/Attribute:organization_name' => 'Nom client',
'Class:Contract/Attribute:organization_name+' => 'Nom commun',
'Class:Contract/Attribute:contacts_list' => 'Contacts',
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts pour ce contrat client',
'Class:Contract/Attribute:contacts_list+' => 'Tous les contacts for ce contrat client',
'Class:Contract/Attribute:documents_list' => 'Documents',
'Class:Contract/Attribute:documents_list+' => 'Tous les documents pour ce contrat client',
'Class:Contract/Attribute:documents_list+' => 'Tous les documents for ce contrat client',
'Class:Contract/Attribute:description' => 'Description',
'Class:Contract/Attribute:description+' => '',
'Class:Contract/Attribute:start_date' => 'Date de début',

View File

@@ -157,8 +157,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Vertragstyp-Name',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'Alle für diesen Vertrag erworbenen Services',
));
//

View File

@@ -169,8 +169,6 @@ Dict::Add('EN US', 'English', 'English', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Contract type name',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'All the services purchased with this contract',
));
//

View File

@@ -157,8 +157,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Class:ProviderContract/Attribute:contracttype_id+' => '',
'Class:ProviderContract/Attribute:contracttype_name' => 'Nom Type de contrat',
'Class:ProviderContract/Attribute:contracttype_name+' => '',
'Class:ProviderContract/Attribute:services_list' => 'Services',
'Class:ProviderContract/Attribute:services_list+' => 'Tous les services achetés par ce contrat',
));
//

View File

@@ -49,7 +49,6 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:AttributeTagSet' => 'List of tags',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'click to add',
'Core:Placeholder:CannotBeResolved' => '(%1$s : cannot be resolved)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s from %3$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s from child classes)',

View File

@@ -452,7 +452,6 @@ Dict::Add('EN US', 'English', 'English', array(
We hope youll enjoy this version as much as we enjoyed imagining and creating it.</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:MyCalls' => 'My requests',
'UI:WelcomeMenu:OpenIncidents' => 'Open incidents: %1$d',
@@ -838,7 +837,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:RunQuery:DevelopedOQLCount' => 'Developed OQL for count',
'UI:RunQuery:ResultSQLCount' => 'Resulting SQL for count',
'UI:RunQuery:ResultSQL' => 'Resulting SQL',
'UI:RunQuery:Error' => 'An error occured while running the query: %1$s',
'UI:RunQuery:Error' => 'An error occured while running the query',
'UI:Query:UrlForExcel' => 'URL to use for MS-Excel web queries',
'UI:Query:UrlV1' => 'The list of fields has been left unspecified. The page <em>export-V2.php</em> cannot be invoked without this information. Therefore, the URL suggested here below points to the legacy page: <em>export.php</em>. This legacy version of the export has the following limitation: the list of exported fields may vary depending on the output format and the data model of '.ITOP_APPLICATION_SHORT.'. <br/>Should you want to guarantee that the list of exported columns will remain stable on the long run, then you must specify a value for the attribute "Fields" and use the page <em>export-V2.php</em>.',
'UI:Schema:Title' => ITOP_APPLICATION_SHORT.' objects schema',

View File

@@ -444,7 +444,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
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>',
'UI:WelcomePopup:Button:Acknowledge' => 'Ok, descartar este mensaje',
'UI:WelcomeMenu:AllOpenRequests' => 'Requerimientos Abiertos: %1$d',
'UI:WelcomeMenu:MyCalls' => 'Mis Requerimientos',
'UI:WelcomeMenu:OpenIncidents' => 'Incidentes Abiertos: %1$d',

View File

@@ -39,7 +39,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
'Core:AttributeTagSet' => 'Liste d\'étiquettes',
'Core:AttributeTagSet+' => '',
'Core:AttributeSet:placeholder' => 'cliquer pour ajouter',
'Core:Placeholder:CannotBeResolved' => '(%1$s : non remplacé)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromClass' => '%1$s (%2$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromOneChildClass' => '%1$s (%2$s de la classe %3$s)',
'Core:AttributeClassAttCodeSet:ItemLabel:AttributeFromSeveralChildClasses' => '%1$s (%2$s d\'une sous-classe)',

View File

@@ -433,7 +433,6 @@ 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>
<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:MyCalls' => 'Mes appels support',
'UI:WelcomeMenu:OpenIncidents' => 'Incidents en cours: %1$d',

View File

@@ -21,7 +21,7 @@
* along with iTop. If not, see <http://www.gnu.org/licenses/>
*/
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Core:DeletedObjectLabel' => '%1$s (törölve)',
'Core:DeletedObjectLabel' => '%1s (törölve)',
'Core:DeletedObjectTip' => 'A %1$s objektum törölve (%2$s)',
'Core:UnknownObjectLabel' => 'Objektum nem található (osztály: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'Az objektumot nem sikerült megtalálni. Lehet, hogy már törölték egy ideje, és a naplót azóta törölték.',

View File

@@ -509,7 +509,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
'UI:Error:2ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s és %2$s.',
'UI:Error:3ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s és %3$s.',
'UI:Error:4ParametersMissing' => 'Hiba: a következő paramétereket meg kell adni ennél a műveletnél: %1$s, %2$s, %3$s és %4$s.',
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$s',
'UI:Error:IncorrectOQLQuery_Message' => 'Hiba: nem megfelelő OQL lekérdezés: %1$',
'UI:Error:AnErrorOccuredWhileRunningTheQuery_Message' => 'Hiba történt a lekérdezés futtatása közben: %1$s',
'UI:Error:ObjectAlreadyUpdated' => 'Hiba: az objketum már korábban módosításra került.',
'UI:Error:ObjectCannotBeUpdated' => 'Hiba: az objektum nem frissíthető.',
@@ -715,7 +715,7 @@ Reméljük, hogy ezt a verziót ugyanúgy kedvelni fogja, mint ahogy mi élvezt
'UI:CSVReport-Value-Issue-Null' => 'A nulla nem engedélyezett',
'UI:CSVReport-Value-Issue-NotFound' => 'Az objektum nincs meg',
'UI:CSVReport-Value-Issue-FoundMany' => '%1$d egyezés található',
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
'UI:CSVReport-Value-Issue-Readonly' => 'A \'%1$\'s attribútum csak olvasható (jelenlegi érték: %2$s, várható érték: %3$s)',
'UI:CSVReport-Value-Issue-Format' => 'A bevitel feldolgozása sikertelen: %1$s',
'UI:CSVReport-Value-Issue-NoMatch' => 'A \'%1$s\' attribútum nem várt értéket kapott: nincs egyezés, ellenőrizze a beírást',
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s~~',

View File

@@ -20,7 +20,7 @@
* @licence http://opensource.org/licenses/AGPL-3.0
*/
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Core:DeletedObjectLabel' => '%1$s (削除されました)',
'Core:DeletedObjectLabel' => '%1s (削除されました)',
'Core:DeletedObjectTip' => 'オブジェクトは削除されました %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'オブジェクトは見つかりません (クラス: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'オブジェクトは見つかりません。しばらく前に削除され、その後ログが削除されたかもしれません。',

View File

@@ -915,7 +915,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:Delete:ConfirmDeletionOf_Count_ObjectsOf_Class' => '%2$sクラスの%1$dオブジェクトの削除',
'UI:Delete:CannotDeleteBecause' => '削除できません: %1$s',
'UI:Delete:ShouldBeDeletedAtomaticallyButNotPossible' => '自動的に削除されるべきですが、出来ません。: %1$s',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$s',
'UI:Delete:MustBeDeletedManuallyButNotPossible' => '手動で削除されるべきですが、出来ません。: %1$',
'UI:Delete:WillBeDeletedAutomatically' => '自動的に削除されます。',
'UI:Delete:MustBeDeletedManually' => '手動で削除されるべきです。',
'UI:Delete:CannotUpdateBecause_Issue' => '自動的に更新されるべきですが、しかし: %1$s',
@@ -1159,7 +1159,8 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'Enum:Undefined' => '未定義',
'UI:DurationForm_Days_Hours_Minutes_Seconds' => '%1$s 日 %2$s 時 %3$s 分 %4$s 秒',
'UI:ModifyAllPageTitle' => '全てを修正',
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$sの%1$dオブジェクトを修正',
'UI:Modify_ObjectsOf_Class' => 'Modifying objects of class %1$s~~',
'UI:Modify_N_ObjectsOf_Class' => 'クラス%2$Sの%1$dオブジェクトを修正',
'UI:Modify_M_ObjectsOf_Class_OutOf_N' => 'クラス%2$sの%3$d中%1$dを修正',
'UI:Menu:ModifyAll' => '修正...',
'UI:Menu:ModifyAll_Class' => 'Modify %1$s objects...~~',
@@ -1179,7 +1180,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:BulkModify_Count_DistinctValues' => '%1$d 個の個別の値:',
'UI:BulkModify:Value_Exists_N_Times' => '%1$s, %2$d 回存在',
'UI:BulkModify:N_MoreValues' => '%1$d 個以上の値...',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$sにセットしょうとしています。',
'UI:AttemptingToSetAReadOnlyAttribute_Name' => '読み込み専用フィールド %1$にセットしょうとしています。',
'UI:FailedToApplyStimuli' => 'アクションは失敗しました。',
'UI:StimulusModify_N_ObjectsOf_Class' => '%1$s: クラス%3$sの%2$dオブジェクトを修正',
'UI:CaseLogTypeYourTextHere' => 'テキストを入力ください:',

View File

@@ -9,7 +9,7 @@
*
*/
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Core:DeletedObjectLabel' => '%1$sы (удален)',
'Core:DeletedObjectLabel' => '%1ы (удален)',
'Core:DeletedObjectTip' => 'Объект был удален %1$s (%2$s)',
'Core:UnknownObjectLabel' => 'Объект не найден (class: %1$s, id: %2$d)',
'Core:UnknownObjectTip' => 'Объект не удается найти. Возможно, он был удален некоторое время назад, и журнал с тех пор был очищен.',

View File

@@ -497,7 +497,7 @@ We hope youll enjoy this version as much as we enjoyed imagining and creating
'UI:Error:MandatoryTemplateParameter_group_by' => 'Parameter group_by je povinný. Skontrolujte definíciu šablóny zobrazenia.',
'UI:Error:InvalidGroupByFields' => 'Neplatný zoznam polí pre skupinu podľa: "%1$s".',
'UI:Error:UnsupportedStyleOfBlock' => 'Chyba: nepodporovaný štýl bloku: "%1$s".',
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %1$s nebol nájdený ako externý kľúč v triede %2$s',
'UI:Error:IncorrectLinkDefinition_LinkedClass_Class' => 'Nesprávna definícia spojenia : trieda objektov na manažovanie : %l$s nebol nájdený ako externý kľúč v triede %2$s',
'UI:Error:Object_Class_Id_NotFound' => 'Objekt: %1$s:%2$d nebol nájdený.',
'UI:Error:WizardCircularReferenceInDependencies' => 'Chyba: Cyklický odkaz v závislostiach medzi poliami, skontrolujte dátový model.',
'UI:Error:UploadedFileTooBig' => 'Nahraný súbor je príliš veľký. (Max povolená veľkosť je %1$s). Ak chcete zmeniť tento limit, obráťte sa na správcu ITOP . (Skontrolujte, PHP konfiguráciu pre upload_max_filesize a post_max_size na serveri).',
@@ -1301,7 +1301,7 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
'UI:DashletGroupBy:Prop-GroupBy:DayOfMonth' => 'Deň v mesiaci pre %1$s',
'UI:DashletGroupBy:Prop-GroupBy:Select-Hour' => '%1$s (hodina)',
'UI:DashletGroupBy:Prop-GroupBy:Select-Month' => '%1$s (mesiac)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$s (deň v týžni)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek' => '%1$ (deň v týžni)',
'UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth' => '%1$s (deň v mesiaci)',
'UI:DashletGroupBy:MissingGroupBy' => 'Prosím zvoľte pole na ktorom objekty budú zoskupené spolu',
'UI:DashletGroupByPie:Label' => 'Koláčový graf',

View File

@@ -278,7 +278,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'Change:AttName_SetTo_NewValue_PreviousValue_OldValue' => '%1$s\'nin değeri %2$s olarak atandı (önceki değer: %3$s)',
'Change:AttName_SetTo' => '%1$s\'nin değeri %2$s olarak atandı',
'Change:Text_AppendedTo_AttName' => '%2$s\'ye %1$s eklendi',
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$s nin değeri deiştirildi, önceki değer: %2$s',
'Change:AttName_Changed_PreviousValue_OldValue' => '%1$\'nin değeri deiştirildi, önceki değer: %2$s',
'Change:AttName_Changed' => '%1$s değiştirildi',
'Change:AttName_EntryAdded' => '%1$s değiştirilmiş, yeni giriş eklendi.',
'Change:State_Changed_NewValue_OldValue' => 'Changed from %2$s to %1$s~~',

View File

@@ -27,8 +27,8 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'UI:Layout:NavigationMenu:MenuFilter:Input:Hint' => 'As correspondências em todos os grupos de menus serão exibidas',
'UI:Layout:NavigationMenu:MenuFilter:Placeholder:Hint' => 'Nenhum resultado para este filtro de menu',
'UI:Layout:NavigationMenu:UserInfo:WelcomeMessage:Text' => 'Olá %1$s!',
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$s',
'UI:Layout:NavigationMenu:UserInfo:Picture:AltText' => 'Imagem do contato %1$',
'UI:Layout:NavigationMenu:UserMenu:Toggler:Label' => 'Abrir menu do usuário',
'UI:Layout:NavigationMenu:KeyboardShortcut:FocusFilter' => 'Filtrar entradas de menu',
));
));

BIN
images/welcome.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -332,14 +332,11 @@ $(function()
oParams.dashlet_class = sDashletClass;
oParams.dashlet_id = sDashletId;
oParams.dashlet_type = options.dashlet_type;
oParams.ajax_promise_id = 'ajax_promise_' + sDashletId;
var me = this;
$.post(this.options.render_to, oParams, function (data) {
me.ajax_div.html(data);
window[oParams.ajax_promise_id].then(function(){
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
me.mark_as_modified();
});
me.add_dashlet_finalize(options, sDashletId, sDashletClass);
me.mark_as_modified();
});
},
on_dashlet_moved: function (oDashlet, oReceiver, bRefresh) {

View File

@@ -117,7 +117,6 @@ $(function()
locked_by_someone_else: 'locked_by_someone_else',
},
},
release_lock_promise_resolve: null, // N°4494 - Resolve callback of the Promise used for the action following the log entry send, which must be done only once the lock is released
// the constructor
_create: function () {
@@ -501,7 +500,7 @@ $(function()
{
// Hide all filters' options only if click wasn't on one of them
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
this._HideAllFiltersOptions();
}
},
@@ -948,9 +947,9 @@ $(function()
// Send request to server
$.post(
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
oParams,
'json'
GetAbsoluteUrlAppRoot()+'pages/ajax.render.php',
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErrorThrown) {
CombodoModal.OpenErrorModal(sErrorThrown);
@@ -973,24 +972,12 @@ $(function()
// For now, we don't hide the forms as the user may want to add something else
me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop');
// Redirect to stimulus
// - Convert undefined, null and empty string to null
sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode;
if (null !== sStimulusCode) {
if (me.options.lock_enabled) {
// Use a Promise to ensure that we redirect to the stimulus page ONLY when the lock is released, otherwise we might lock ourselves
const oPromise = new Promise(function(resolve) {
// Store the resolve callback so we can call it later from outside
me.release_lock_promise_resolve = resolve;
});
oPromise.then(function () {
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
// Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry
me.release_lock_promise_resolve = null;
});
} else {
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
}
window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode;
}
})
.always(function () {
@@ -1008,7 +995,7 @@ $(function()
_IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){
let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]');
let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1;
oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue);
},
/**
@@ -1156,10 +1143,11 @@ $(function()
else {
oParams.operation = 'check_lock_state';
}
$.post(
this.options.lock_endpoint,
oParams,
'json'
this.options.lock_endpoint,
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErrorThrown) {
// In case of HTTP request failure (not lock request), put the details in the JS console
@@ -1208,9 +1196,6 @@ $(function()
// Tried to release our lock
else if ('release_lock' === oParams.operation) {
sNewLockStatus = me.enums.lock_status.unknown;
if (me.release_lock_promise_resolve !== null) {
me.release_lock_promise_resolve();
}
}
// Just checked if object was locked
@@ -1445,9 +1430,9 @@ $(function()
limit_results_length: bLimitResultsLength,
};
$.post(
this.options.load_more_entries_endpoint,
oParams,
'json'
this.options.load_more_entries_endpoint,
oParams,
'json'
)
.fail(function (oXHR, sStatus, sErroThrown) {
CombodoModal.OpenErrorModal(sErrorThrown);

View File

@@ -153,15 +153,16 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
"dataType": "html"
})
.done(function (data) {
/* N°6152 - Hide during data loading and before open */
$('#dlg_'+me.id).hide();
$('#dlg_'+me.id).html(data);
window[sPromiseId].then(function () {
$('#dlg_'+me.id).dialog('open');
me.UpdateSizes(null, null);
if (me.bDoSearch) {
if (me.bDoSearch)
{
me.SearchObjectsToAdd();
} else {
}
else
{
$('#count_'+me.id).change(function () {
let c = this.value;
me.UpdateButtons(c);

View File

@@ -14,7 +14,6 @@ return array(
'AbstractPortalUIExtension' => $baseDir . '/application/applicationextension.inc.php',
'AbstractPreferencesExtension' => $baseDir . '/application/applicationextension.inc.php',
'AbstractWeeklyScheduledProcess' => $baseDir . '/core/backgroundprocess.inc.php',
'AbstractWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
'Action' => $baseDir . '/core/action.class.inc.php',
'ActionChecker' => $baseDir . '/core/userrights.class.inc.php',
'ActionEmail' => $baseDir . '/core/action.class.inc.php',
@@ -364,8 +363,6 @@ return array(
'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\\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\\Controller\\AbstractController' => $baseDir . '/sources/Controller/AbstractController.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => $baseDir . '/sources/Controller/AjaxRenderController.php',
@@ -375,7 +372,6 @@ return array(
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.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\\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',
@@ -476,8 +472,6 @@ return array(
'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\\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',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Config' => $baseDir . '/core/config.class.inc.php',
@@ -2991,7 +2985,6 @@ return array(
'iTopWebPage' => $baseDir . '/sources/Application/WebPage/iTopWebPage.php',
'iTopWizardWebPage' => $baseDir . '/sources/Application/WebPage/iTopWizardWebPage.php',
'iTopXmlException' => $baseDir . '/application/exceptions/iTopXmlException.php',
'iWelcomePopup' => $baseDir . '/application/applicationextension.inc.php',
'iWorkingTimeComputer' => $baseDir . '/core/computing.inc.php',
'lnkAuditCategoryToAuditDomain' => $baseDir . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => $baseDir . '/core/trigger.class.inc.php',

View File

@@ -378,7 +378,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'AbstractPortalUIExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractPreferencesExtension' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'AbstractWeeklyScheduledProcess' => __DIR__ . '/../..' . '/core/backgroundprocess.inc.php',
'AbstractWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'Action' => __DIR__ . '/../..' . '/core/action.class.inc.php',
'ActionChecker' => __DIR__ . '/../..' . '/core/userrights.class.inc.php',
'ActionEmail' => __DIR__ . '/../..' . '/core/action.class.inc.php',
@@ -728,8 +727,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'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\\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\\Controller\\AbstractController' => __DIR__ . '/../..' . '/sources/Controller/AbstractController.php',
'Combodo\\iTop\\Controller\\AjaxRenderController' => __DIR__ . '/../..' . '/sources/Controller/AjaxRenderController.php',
@@ -739,7 +736,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.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\\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',
@@ -840,8 +836,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'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\\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',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
@@ -3355,7 +3349,6 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'iTopWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWebPage.php',
'iTopWizardWebPage' => __DIR__ . '/../..' . '/sources/Application/WebPage/iTopWizardWebPage.php',
'iTopXmlException' => __DIR__ . '/../..' . '/application/exceptions/iTopXmlException.php',
'iWelcomePopup' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'iWorkingTimeComputer' => __DIR__ . '/../..' . '/core/computing.inc.php',
'lnkAuditCategoryToAuditDomain' => __DIR__ . '/../..' . '/application/audit.domain.class.inc.php',
'lnkTriggerAction' => __DIR__ . '/../..' . '/core/trigger.class.inc.php',

View File

@@ -5,7 +5,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'name' => 'combodo/itop',
'dev' => true,
),
@@ -25,7 +25,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '204a6d8e51619cd669c6d9d7722edaa36d1c3394',
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(

View File

@@ -20,7 +20,6 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Controller\Base\Layout\ObjectController;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
/**
* Displays a popup welcome message, once per session at maximum
@@ -30,18 +29,19 @@ use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
*
* @return void
*/
function DisplayWelcomePopup(WebPage $oP): void
function DisplayWelcomePopup(WebPage $oP)
{
if (!Session::IsSet('welcome'))
{
$oWelcomePopupService = new WelcomePopupService();
$aMessages = $oWelcomePopupService->GetMessages();
if (count($aMessages) > 0)
// Check, only once per session, if the popup should be displayed...
// If the user did not already ask for hiding it forever
$bPopup = appUserPreferences::GetPref('welcome_popup', true);
if ($bPopup)
{
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup', ['messages' => $aMessages]);
TwigHelper::RenderIntoPage($oP, APPROOT.'/', 'templates/pages/backoffice/welcome_popup/welcome_popup');
Session::Set('welcome', 'ok');
}
Session::Set('welcome', 'ok'); // Try just once per session
}
}
}
/**
@@ -66,7 +66,7 @@ function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction)
}
// Get the list of missing mandatory fields for the target state, considering only the changes from the previous form (i.e don't prompt twice)
$aExpectedAttributes = $oObj->GetTransitionAttributes($sNextAction);
if (count($aExpectedAttributes) == 0)
{
// If all the mandatory fields are already present, just apply the transition silently...
@@ -85,7 +85,7 @@ function ApplyNextAction(Webpage $oP, CMDBObject $oObj, $sNextAction)
// redirect to the 'stimulus' action
$oAppContext = new ApplicationContext();
//echo "<p>Missing Attributes <pre>".print_r($aExpectedAttributes, true)."</pre></p>\n";
$oP->add_header('Location: '.utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=stimulus&class='.get_class($oObj).'&stimulus='.$sNextAction.'&id='.$oObj->getKey().'&'.$oAppContext->GetForLink());
}
}
@@ -243,7 +243,7 @@ function DisplayMultipleSelectionForm(WebPage $oP, DBSearch $oFilter, string $sN
$aExtraParams['surround_with_panel'] = true;
if(array_key_exists('icon', $aDisplayParams)){
$aExtraParams['panel_icon'] = $aDisplayParams['icon'];
}
}
if(array_key_exists('title', $aDisplayParams)){
$aExtraParams['panel_title'] = $aDisplayParams['title'];
}
@@ -292,7 +292,7 @@ function DisplayNavigatorGroupTab($oP)
}
/***********************************************************************************
*
*
* Main user interface page starts here
*
***********************************************************************************/
@@ -700,7 +700,7 @@ try
break;
///////////////////////////////////////////////////////////////////////////////////////////
/** @deprecated 3.1.0 Use the "object.new" route instead */
// Kept for backward compatibility
case 'new': // Form to create a new object
@@ -1638,4 +1638,4 @@ class UI
);
cmdbAbstractObject::DoBulkModify($oP, $sClass, $aSelectedObj, 'preview_or_modify_all', $bPreview, $sCancelUrl, $aContext);
}
}
}

View File

@@ -105,7 +105,7 @@ if ($oFilter != null)
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php';
$aExtraParams['table_id'] = '1';
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
$aExtraParams['submit_on_load'] = false;
//$aExtraParams['class'] = $sClassName;
$oBlock->Display($oP, 0, $aExtraParams);
// Search results

View File

@@ -17,7 +17,6 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Controller\WelcomePopupController;
require_once('../approot.inc.php');
@@ -69,7 +68,7 @@ try
break;
default:
$oTag = new ContextTag(ContextTag::TAG_CONSOLE);
ContextTag::AddContext(ContextTag::TAG_CONSOLE);
}
// First check if we can redirect the route to a dedicated controller
@@ -202,10 +201,10 @@ try
$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates);
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get("auth_user"),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get("auth_user"),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj
);
$aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array());
@@ -277,10 +276,10 @@ try
$oPage->SetContentType('text/html');
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj,
);
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
@@ -304,10 +303,10 @@ try
}
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'origin' => 'console',
'source_obj' => $oObj,
);
$aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass());
@@ -419,10 +418,10 @@ try
$iInputId = utils::ReadParam('iInputId', '');
$sAttCode = utils::ReadParam('sAttCode', '');
$sJson = utils::ReadParam('json', '', 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
$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
$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
if (!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)) {
if(!$bTargetClassSelected && MetaModel::HasChildrenClasses($sTargetClass)){
$oWidget->GetClassSelectionForm($oPage);
} else {
$aPrefillFormParam = array();
@@ -431,11 +430,11 @@ try
$oObj = $oWizardHelper->GetTargetObject();
$oAppContext = new ApplicationContext();
$aPrefillFormParam = array(
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'user' => Session::Get('auth_user'),
'context' => $oAppContext->GetAsHash(),
'att_code' => $sAttCode,
'source_obj' => $oObj,
'origin' => 'console'
'origin' => 'console'
);
} else {
// Search form: no current object
@@ -528,8 +527,7 @@ try
$oObj->Set($sAttCode, $defaultValue);
}
$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) {
$sId = $oWizardHelper->GetIdForField($sAttCode);
if ($sId != '') {
@@ -575,8 +573,7 @@ try
$sTargetState = utils::ReadParam('target_state', '');
$iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id');
$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;
// DisplayBlock
@@ -603,7 +600,8 @@ try
} else {
try {
$oFilter = DBSearch::unserialize($sFilter);
} catch (CoreException $e) {
}
catch (CoreException $e) {
$sFilter = utils::HtmlEntities($sFilter);
$oPage->p("Invalid query (invalid filter) : <code>$sFilter</code>");
IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter");
@@ -668,8 +666,7 @@ try
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
$aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson).
',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;
case 'pie':
@@ -680,8 +677,7 @@ try
$aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs);
$aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns).
',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;
}
} else {
@@ -781,14 +777,14 @@ try
$oFilter = new DBObjectSearch($sClass);
$oSet = new CMDBObjectSet($oFilter);
$sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array(
'currentId' => $currentId,
'baseClass' => $sRootClass,
'action' => $sAction,
'table_id' => $sTableId,
'selection_mode' => $sSelectionMode,
'currentId' => $currentId,
'baseClass' => $sRootClass,
'action' => $sAction,
'table_id' => $sTableId,
'selection_mode' => $sSelectionMode,
'result_list_outer_selector' => $sResultListOuterSelector,
'cssCount' => $scssCount,
'table_inner_id' => $sTableInnerId
'cssCount' => $scssCount,
'table_inner_id' => $sTableInnerId
));
$oPage->add($sHtml);
break;
@@ -825,13 +821,13 @@ try
TemporaryObjectManager::GetInstance()->CancelAllTemporaryObjects($iTransactionId);
IssueLog::Trace('on_form_cancel', $sObjClass, array(
'$iObjKey' => $iObjKey,
'$iObjKey' => $iObjKey,
'$sTransactionId' => $iTransactionId,
'$sTempId' => $sTempId,
'$sToken' => $sToken,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'$sTempId' => $sTempId,
'$sToken' => $sToken,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
break;
@@ -883,9 +879,11 @@ try
$oDoc = utils::ReadPostedDocument('dashboard_upload_file');
$oDashboard->FromXml($oDoc->GetData());
$oDashboard->Save();
} catch (DOMException $e) {
}
catch (DOMException $e) {
$aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile'));
} catch (Exception $e) {
}
catch (Exception $e) {
$aResult = array('error' => $e->getMessage());
}
} else {
@@ -1056,8 +1054,7 @@ EOF
$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'
$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 = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
@@ -1116,8 +1113,7 @@ EOF
}
if ($oDashlet->IsFormRedrawNeeded()) {
$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 = str_replace("\n", '', $sHtml);
$sHtml = str_replace("\r", '', $sHtml);
@@ -1363,8 +1359,7 @@ JS
}
$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])) {
$bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge'];
}
@@ -1398,11 +1393,9 @@ EOF;
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\n");
$oPage->add("<div class=\"page_header\">\n");
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 {
$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");
$oLeafsFilter->AddCondition('id', $aLeafs, 'IN');
@@ -1418,8 +1411,7 @@ EOF;
if (array_key_exists($sClassName, $aAccelerators)) {
$oPage->add("<div class=\"search-class-result search-class-$sClassName\">\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->p('&nbsp;'); // Some space ?
@@ -1499,8 +1491,7 @@ EOF
$oFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$oSet = new DBObjectSet($oFilter);
$oPage->add("<div class=\"page_header\">\n");
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found',
$oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
$oPage->add("<h2>".MetaModel::GetClassIcon($sClass)."&nbsp;<span class=\"hilite\">".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."</h2>\n");
$oPage->add("</div>\n");
if ($oSet->Count() > 0) {
$aLeafs = array();
@@ -1588,11 +1579,10 @@ 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>');
$aLabels = array(
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
'cancel_button' => Dict::S('UI:Button:Cancel'),
'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??)
'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'),
'cancel_button' => Dict::S('UI:Button:Cancel'),
'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??)
);
$sJSLabels = json_encode($aLabels);
$sFilter = addslashes($sFilter);
@@ -1704,8 +1694,7 @@ EOF
if ($sDirection == 'up') {
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
} 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
@@ -1765,11 +1754,9 @@ EOF
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
$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(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(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)
@@ -1781,10 +1768,8 @@ EOF
$sListClass = get_class(current($aObjects));
$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
$sIconUrl = MetaModel::GetClassIcon($sListClass, false);
$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:RelationGroupNumber_N',
(1 + $idx)), Metamodel::GetName($sListClass));
$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:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass));
$oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2));
$oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet));
@@ -1858,8 +1843,7 @@ EOF
if ($sDirection == 'up') {
$oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts);
} 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
@@ -1890,15 +1874,13 @@ EOF
$oSearch = new DBObjectSearch($sListClass);
$oSearch->AddCondition('id', $aDefinition['keys'], 'IN');
$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->Display($oPage, 'group_'.$iBlock++, array(
'surround_with_panel' => true,
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']),
Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
));
}
break;
@@ -1912,12 +1894,11 @@ EOF
$oSearch->SetShowObsoleteData(utils::ShowObsoleteData());
$oBlock = new DisplayBlock($oSearch, 'list');
$oBlock->Display($oPage, 'list_'.$iBlock++, array(
'table_id' => 'ImpactAnalysis_'.$sListClass,
'table_id' => 'ImpactAnalysis_'.$sListClass,
'surround_with_panel' => true,
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys),
Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
'panel_class' => $sListClass,
'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass)),
'panel_icon' => MetaModel::GetClassIcon($sListClass, false),
));
}
break;
@@ -1969,8 +1950,7 @@ EOF
$sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection;
$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;
case 'export_build':
@@ -2020,7 +2000,7 @@ EOF
$aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey);
$aResult = [
'locked' => $aLockData['locked'],
'locked' => $aLockData['locked'],
'message' => '',
];
@@ -2107,11 +2087,11 @@ EOF
$aResult = array(
'uploaded' => 0,
'fileName' => '',
'url' => '',
'icon' => '',
'msg' => '',
'att_id' => 0,
'preview' => 'false',
'url' => '',
'icon' => '',
'msg' => '',
'att_id' => 0,
'preview' => 'false',
);
$sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class'));
@@ -2146,19 +2126,20 @@ EOF
}
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'$aResult' => $aResult,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'$operation' => $operation,
'$aResult' => $aResult,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
} else {
$aResult['error'] = $oDoc->GetFileName().' is not a valid image format.';
}
} catch (FileUploadException $e) {
}
catch (FileUploadException $e) {
$aResult['error'] = $e->GetMessage();
}
}
@@ -2175,8 +2156,8 @@ EOF
if (!InlineImage::IsImage($sDocMimeType)) {
LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image',
array(
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'ImgMimeType' => $sDocMimeType,
));
} else {
@@ -2193,21 +2174,22 @@ EOF
$iAttId = $oAttachment->DBInsert();
IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array(
'$operation' => $operation,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'$operation' => $operation,
'secret' => $oAttachment->Get('secret'),
'temp_id' => $sTempId,
'item_class' => $sObjClass,
'user' => UserRights::GetUser(),
'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',
array(
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'operation' => 'cke_upload_and_browse',
'class' => $sObjClass,
'exceptionMsg' => $e,
));
}
@@ -2321,8 +2303,7 @@ $('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true });
EOF
);
$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>");
if ($oSet->Count() == 0) {
@@ -2379,8 +2360,7 @@ EOF
$aTriggerMentionedSearches = [];
$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 */
while ($oTrigger = $oTriggerSet->Fetch()) {
$sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter');
@@ -2414,8 +2394,7 @@ EOF
// Add condition to filter on the friendlyname
$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);
@@ -2434,8 +2413,8 @@ EOF
$sObjectClass = get_class($oObject);
$iObjectId = $oObject->GetKey();
$aMatch = [
'class' => $sObjectClass,
'id' => $iObjectId,
'class' => $sObjectClass,
'id' => $iObjectId,
'friendlyname' => $oObject->Get('friendlyname'),
];
@@ -2444,8 +2423,7 @@ EOF
/** @var \ormDocument $oImage */
$oImage = $oObject->Get($sObjectImageAttCode);
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'] = '';
} else {
// If no image found, fallback on initials
@@ -2482,7 +2460,8 @@ EOF
$aRenderRes = $oRenderer->Render($aRequestedFields);
$aResult['form']['updated_fields'] = $aRenderRes;
} catch (Exception $e) {
}
catch (Exception $e) {
$aResult['error'] = $e->getMessage();
}
$oPage->SetData($aResult);
@@ -2499,9 +2478,10 @@ EOF
$oController = new PreferencesController();
$aResult = $oController->SetUserPicture();
$aResult['success'] = true;
} catch (Exception $oException) {
}
catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2520,9 +2500,10 @@ EOF
$aResult = [
'success' => true,
];
} catch (Exception $oException) {
}
catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2535,9 +2516,10 @@ EOF
try {
$oController = new ActivityPanelController();
$aResult = $oController->AddCaseLogsEntries();
} catch (Exception $oException) {
}
catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2550,9 +2532,10 @@ EOF
try {
$oController = new ActivityPanelController();
$aResult = $oController->LoadMoreEntries();
} catch (Exception $oException) {
}
catch (Exception $oException) {
$aResult = [
'success' => false,
'success' => false,
'error_message' => $oException->getMessage(),
];
}
@@ -2568,24 +2551,6 @@ EOF
$oAjaxRenderController->GetMenusCount($oPage);
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
//--------------------------------

View File

@@ -141,58 +141,55 @@ JS
//
//////////////////////////////////////////////////////////////////////////
$bIsSiloSelectionEnabled = MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
if ($bIsSiloSelectionEnabled)
{
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// Favorite organizations: the organizations listed in the drop-down menu
$sOQL = ApplicationMenu::GetFavoriteSiloQuery();
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oFavoriteOrganizationsBlock = new Panel(Dict::S('UI:FavoriteOrganizations'), array(), 'grey', 'ibo-favorite-organizations');
$oFavoriteOrganizationsBlock->SetSubTitle(Dict::S('UI:FavoriteOrganizations+'));
$oFavoriteOrganizationsBlock->AddCSSClass('ibo-datatable-panel');
$oFavoriteOrganizationsForm = new Form();
$oFavoriteOrganizationsBlock->AddSubBlock($oFavoriteOrganizationsForm);
// 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;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
$sIdFavoriteOrganizations = 1;
$oFavoriteOrganizationsForm->AddSubBlock($oBlock->GetDisplay($oP, $sIdFavoriteOrganizations, [
'menu' => false,
'selection_mode' => true,
'selection_type' => 'multiple',
'table_id' => 'user_prefs',
'surround_with_panel' => false,
'selected_rows' => $aFavoriteOrgs,
]));
$oFavoriteOrganizationsForm->AddSubBlock($oAppContext->GetForFormBlock());
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// Button toolbar
$oFavoriteOrganizationsToolBar = ToolbarUIBlockFactory::MakeForButton(null, ['ibo-is-fullwidth']);
$oFavoriteOrganizationsForm->AddSubBlock($oFavoriteOrganizationsToolBar);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// - Cancel button
$oFavoriteOrganizationsCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'));
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsCancelButton);
$oFavoriteOrganizationsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'");
// - Submit button
$oFavoriteOrganizationsSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), 'operation', 'apply', true);
$oFavoriteOrganizationsToolBar->AddSubBlock($oFavoriteOrganizationsSubmitButton);
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
// TODO 3.0 have this code work again, currently it prevents the display of favorite organizations and shortcuts.
// if ($aFavoriteOrgs == null) {
// // All checked
// $oP->add_ready_script(
// <<<JS
// $('#$sIdFavoriteOrganizations.checkAll').prop('checked', true);
// checkAllDataTable('datatable_$sIdFavoriteOrganizations',true,'$sIdFavoriteOrganizations');
//JS
// );
//
// }
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
$oContentLayout->AddMainBlock($oFavoriteOrganizationsBlock);
}
//////////////////////////////////////////////////////////////////////////
//
// Shortcuts

View File

@@ -290,6 +290,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
if (!MetaModel::IsAbstract($sChildClass)) {
$oObject = MetaModel::NewObject($sChildClass);
$aSources[] = $oObject->GetObjectUniqId();
break;
}
}
@@ -298,6 +299,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
}
} else {
$oObject = MetaModel::NewObject($sClass);
$aSources[] = $oObject->GetObjectUniqId();
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
$aSources[] = $sParentClass;
}
@@ -318,19 +320,12 @@ function DisplayEvents(WebPage $oPage, $sClass)
});
$aColumns = [
'event' => ['label' => Dict::S('UI:Schema:Events:Event')],
'callback' => ['label' => Dict::S('UI:Schema:Events:Listener')],
'listener' => ['label' => Dict::S('UI:Schema:Events:Listener')],
'priority' => ['label' => Dict::S('UI:Schema:Events:Rank')],
'module' => ['label' => Dict::S('UI:Schema:Events:Module')],
];
// Get the object listeners first
$aRows = [];
$oReflectionClass = new ReflectionClass($sClass);
if ($oReflectionClass->isInstantiable()) {
/** @var DBObject $oClass */
$oClass = new $sClass();
$aRows = $oClass->GetListeners();
}
foreach ($aListeners as $aListener) {
if (is_object($aListener['callback'][0])) {
$sListenerClass = $sClass;
@@ -348,7 +343,7 @@ function DisplayEvents(WebPage $oPage, $sClass)
}
$aRows[] = [
'event' => $aListener['event'],
'callback' => $sListener,
'listener' => $sListener,
'priority' => $aListener['priority'],
'module' => $aListener['module'],
];

View File

@@ -109,7 +109,6 @@ try
$aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php';
$aExtraParams['table_id'] = '1';
$aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown;
$aExtraParams['submit_on_load'] = false;
$oBlock->Display($oP, 0, $aExtraParams);
// Search results

View File

@@ -3,7 +3,7 @@
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -146,23 +146,18 @@ class DBBackup
/**
* Create a normalized backup name, depending on the current date/time and Database
*
* @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 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 \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
*/
public function MakeName(?string $sNameSpec = null, ?DateTime $oDateTime = null)
public function MakeName(string $sNameSpec = "__DB__-%Y-%m-%d", DateTime $oDateTime = null)
{
if ($oDateTime === null) {
$oDateTime = new DateTime();
}
//N°6640
if ($sNameSpec === null) {
$sNameSpec = "__DB__-%Y-%m-%d";
}
$sFileName = $sNameSpec;
$sFileName = str_replace('__HOST__', $this->sDBHost, $sFileName);
$sFileName = str_replace('__DB__', $this->sDBName, $sFileName);
@@ -227,7 +222,7 @@ class DBBackup
*
* @param string $sSourceConfigFile
* @param string $sTmpFolder
* @param bool $bSkipSQLDumpForTesting
* @param bool $bSkipSQLDumpForTesting
*
* @return array list of files to archive
* @throws \Exception
@@ -278,7 +273,7 @@ class DBBackup
if(!file_exists(APPROOT.'/'.$sExtraFileOrDir)) {
continue; // Ignore non-existing files
}
$sExtraFullPath = utils::RealPath(APPROOT.'/'.$sExtraFileOrDir, APPROOT);
if ($sExtraFullPath === false)
{

View File

@@ -23,15 +23,15 @@ use Combodo\iTop\DesignElement;
require_once(APPROOT.'setup/setuputils.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');
class DOMFormatException extends Exception
{
/**
* Overrides the Exception default constructor to automatically add informations about the concerned node (path and
* line number)
*
*
* @param string $message
* @param $code
* @param $previous
@@ -49,7 +49,7 @@ class DOMFormatException extends Exception
/**
* Compiler class
*/
*/
class MFCompiler
{
const DATA_PRECOMPILED_FOLDER = 'data'.DIRECTORY_SEPARATOR.'precompiled_styles'.DIRECTORY_SEPARATOR;
@@ -356,7 +356,7 @@ class MFCompiler
apc_clear_cache();
}
}
/**
* Perform the actual "Compilation" of all modules
@@ -368,16 +368,21 @@ class MFCompiler
*/
protected function DoCompile($sTempTargetDir, $sFinalTargetDir, $oP = null, $bUseSymbolicLinks = false)
{
$aAllClasses = []; // flat list of classes
$aModulesInfo = []; // Hash array of module_name => array('version' => string, 'root_dir' => string)
$aAllClasses = array(); // flat list of classes
$aModulesInfo = array(); // Hash array of module_name => array('version' => string, 'root_dir' => string)
// 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;
/**
* @since 3.1 N°4762
*/
$oParentMenuNodeCompiler = new ParentMenuNodeCompiler($this);
$oParentMenuNodeCompiler->LoadXmlMenus($this->oFactory);
$sModuleMenu = $oMenuNode->getAttribute('_created_in');
$aMenusByModule[$sModuleMenu][] = $sMenuId;
}
// 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
@@ -424,7 +429,6 @@ class MFCompiler
static::SetUseSymbolicLinksFlag($bUseSymbolicLinks);
$oParentMenuNodeCompiler->LoadModuleMenuInfo($aModules);
foreach ($aModules as $foo => $oModule) {
$sModuleName = $oModule->GetName();
$sModuleVersion = $oModule->GetVersion();
@@ -509,7 +513,7 @@ class MFCompiler
}
}
if (is_null($oParentMenuNodeCompiler->GetMenusByModule($sModuleName)))
if (!array_key_exists($sModuleName, $aMenusByModule))
{
$this->Log("Found module without menus declared: $sModuleName");
}
@@ -529,19 +533,79 @@ class $sMenuCreationClass extends ModuleHandlerAPI
global \$__comp_menus__; // ensure that the global variable is indeed global !
EOF;
$oParentMenuNodeCompiler->CompileModuleMenus($oModule, $sTempTargetDir, $sFinalTargetDir, $sRelativeDir, $oP);
// Preliminary: determine parent menus not defined within the current module
$aMenusToLoad = array();
$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";
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAll() as $sPHPLine)
foreach ($aMenuLinesForAll as $sPHPLine)
{
$sCompiledCode .= $sIndent.$sPHPLine."\n";
}
if (count($oParentMenuNodeCompiler->GetMenuLinesForAdmins()) > 0)
if (count($aMenuLinesForAdmins) > 0)
{
$sCompiledCode .= $sIndent."if (UserRights::IsAdministrator())\n";
$sCompiledCode .= $sIndent."{\n";
foreach ($oParentMenuNodeCompiler->GetMenuLinesForAdmins() as $sPHPLine)
foreach ($aMenuLinesForAdmins as $sPHPLine)
{
$sCompiledCode .= $sIndent."\t".$sPHPLine."\n";
}
@@ -573,7 +637,7 @@ EOF;
$sCompiledCode .= $aSnippet['content']."\n";
}
}
// Create (overwrite if existing) the compiled file
//
if (strlen($sCompiledCode) > 0)
@@ -677,7 +741,7 @@ PHP;
$this->sMainPHPCode .= $aSnippet['content']."\n";
}
}
// Compile the portals
/** @var \MFElement $oPortalsNode */
$oPortalsNode = $this->oFactory->GetNodes('/itop_design/portals')->item(0);
@@ -691,7 +755,7 @@ PHP;
/** @var \MFElement $oParametersNode */
$oParametersNode = $this->oFactory->GetNodes('/itop_design/module_parameters')->item(0);
$this->CompileParameters($oParametersNode, $sTempTargetDir, $sFinalTargetDir);
if (array_key_exists('_core_', $this->aSnippets))
{
foreach( $this->aSnippets['_core_']['after'] as $aSnippet)
@@ -725,7 +789,7 @@ PHP;
$sCurrDate = date(DATE_ISO8601);
// Autoload
$sPHPFile = $sTempTargetDir.'/autoload.php';
$sPHPFileContent =
$sPHPFileContent =
<<<EOF
<?php
//
@@ -734,7 +798,7 @@ PHP;
//
EOF
;
$sPHPFileContent .= "\nMetaModel::IncludeModule(MODULESROOT.'/core/main.php');\n";
$sPHPFileContent .= implode("\n", $aDataModelFiles);
$sPHPFileContent .= implode("\n", $aWebservicesFiles);
@@ -742,14 +806,14 @@ EOF
$sModulesInfo = str_replace("'".$sRelFinalTargetDir."/", "\$sCurrEnv.'/", $sModulesInfo);
$sPHPFileContent .= "\nfunction GetModulesInfo()\n{\n\$sCurrEnv = 'env-'.utils::GetCurrentEnvironment();\nreturn ".$sModulesInfo.";\n}\n";
file_put_contents($sPHPFile, $sPHPFileContent);
} // DoCompile()
/**
* Helper to form a valid ZList from the array built by GetNodeAsArrayOfItems()
*
* @param array $aItems
*/
*/
protected function ArrayOfItemsToZList(&$aItems)
{
// Note: $aItems can be null in some cases so we have to protect it otherwise a PHP warning will be thrown during the foreach
@@ -781,7 +845,7 @@ EOF
* Helper to format the flags for an attribute, in a given state
* @param object $oAttNode DOM node containing the information to build the flags
* Returns string PHP flags, based on the OPT_ATT_ constants, or empty (meaning 0, can be omitted)
*/
*/
protected function FlagsToPHP($oAttNode)
{
static $aNodeAttributeToFlag = array(
@@ -791,7 +855,7 @@ EOF
'must_change' => 'OPT_ATT_MUSTCHANGE',
'hidden' => 'OPT_ATT_HIDDEN',
);
$aFlags = array();
foreach ($aNodeAttributeToFlag as $sNodeAttribute => $sFlag)
{
@@ -803,7 +867,7 @@ EOF
}
if (empty($aFlags))
{
$aFlags[] = 'OPT_ATT_NORMAL'; // When no flag is defined, reset the state to "normal"
$aFlags[] = 'OPT_ATT_NORMAL'; // When no flag is defined, reset the state to "normal"
}
$sRes = implode(' | ', $aFlags);
return $sRes;
@@ -825,7 +889,7 @@ EOF
'details' => 'LINKSET_TRACKING_DETAILS',
'all' => 'LINKSET_TRACKING_ALL',
);
static $aXmlToPHP_Others = array(
'none' => 'ATTRIBUTE_TRACKING_NONE',
'all' => 'ATTRIBUTE_TRACKING_ALL',
@@ -866,7 +930,7 @@ EOF
'in_place' => 'LINKSET_EDITMODE_INPLACE',
'add_remove' => 'LINKSET_EDITMODE_ADDREMOVE',
);
if (!array_key_exists($sEditMode, $aXmlToPHP))
{
throw new DOMFormatException("Edit mode: unknown value '$sEditMode'");
@@ -874,10 +938,10 @@ EOF
return $aXmlToPHP[$sEditMode];
}
/**
* Format a path (file or url) as an absolute path or relative to the module or the app
*/
*/
protected function PathToPHP($sPath, $sModuleRelativeDir, $bIsUrl = false)
{
if ($sPath == '')
@@ -970,7 +1034,7 @@ EOF
else
{
throw new DOMFormatException("missing (or empty) mandatory tag '$sTag' under the tag '".$oNode->nodeName."'");
}
}
}
/**
@@ -1074,7 +1138,7 @@ EOF
/**
* Adds quotes and escape characters
*/
*/
protected function QuoteForPHP($sStr, $bSimpleQuotes = false)
{
if ($bSimpleQuotes)
@@ -1181,7 +1245,7 @@ EOF
$sScalar = (string)(int)$sText;
}
break;
case 'float':
if (is_null($sText))
{
@@ -1193,7 +1257,7 @@ EOF
$sScalar = (string)(float)$sText;
}
break;
case 'bool':
if (is_null($sText))
{
@@ -1385,12 +1449,17 @@ EOF
}
$sMethods .= "\n $sCallbackFct\n\n";
}
if (strpos($sCallback, '::') === false) {
$sEventListener = '[$this, \''.$sCallback.'\']';
} else {
$sEventListener = "'$sCallback'";
}
$sListenerRank = (float)($oListener->GetChildText('rank', '0'));
$sEvents .= <<<PHP
// listenerId = $sListenerId
\$this->RegisterCRUDListener("$sEventName", '$sCallback', $sListenerRank, '$sModuleRelativeDir');
Combodo\iTop\Service\Events\EventService::RegisterListener("$sEventName", $sEventListener, \$this->m_sObjectUniqId, [], null, $sListenerRank, '$sModuleRelativeDir');
PHP;
}
}
@@ -2653,7 +2722,7 @@ CSS;
* @throws \DOMException
* @throws \DOMFormatException
*/
public function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
protected function CompileMenu($oMenu, $sTempTargetDir, $sFinalTargetDir, $sModuleRelativeDir, $oP)
{
$this->CompileFiles($oMenu, $sTempTargetDir.'/'.$sModuleRelativeDir, $sFinalTargetDir.'/'.$sModuleRelativeDir, $sModuleRelativeDir);
@@ -2749,11 +2818,11 @@ CSS;
case '1':
$sSearchFormOpen = 'true';
break;
case '0':
$sSearchFormOpen = 'false';
break;
default:
$sSearchFormOpen = 'true';
}
@@ -2842,7 +2911,7 @@ CSS;
foreach($this->oFactory->ListFields($oClass) as $oField)
{
$sAttType = $oField->getAttribute('xsi:type');
if (($sAttType == 'AttributeExternalKey') || ($sAttType == 'AttributeHierarchicalKey'))
{
$sOnTargetDel = $oField->GetChildText('on_target_delete');
@@ -2868,7 +2937,7 @@ CSS;
$oClasses = $oGroup->GetUniqueElement('classes');
foreach($oClasses->getElementsByTagName('class') as $oClass)
{
$sClass = $oClass->getAttribute("id");
$aClasses[] = $sClass;
@@ -2886,7 +2955,7 @@ CSS;
$aProfiles[1] = array(
'name' => 'Administrator',
'description' => 'Has the rights on everything (bypassing any control)',
);
);
$aGrants = array();
$oProfiles = $oUserRightsNode->GetUniqueElement('profiles');
@@ -2918,7 +2987,7 @@ CSS;
}
$sGrant = $oAction->GetText();
$bGrant = ($sGrant == 'allow');
if ($sGroupId == '*')
{
$aGrantClasses = array('*');
@@ -3157,7 +3226,7 @@ Dict::SetLanguagesList(
$sLanguagesDump
);
EOF;
file_put_contents($sLanguagesFile, $sLanguagesFileContent);
}
@@ -3194,7 +3263,7 @@ EOF;
{
throw new DOMFormatException('Could not find the file with ref '.$sFileId);
}
$sName = $oNodes->item(0)->GetChildText('name');
$sData = base64_decode($oNodes->item(0)->GetChildText('data'));
$aPathInfo = pathinfo($sName);
@@ -3208,7 +3277,7 @@ EOF;
}
$oParentNode = $oFileRef->parentNode;
$oParentNode->removeChild($oFileRef);
$oTextNode = $oParentNode->ownerDocument->createTextNode($sRelativePath.'/images/'.$sFile);
$oParentNode->appendChild($oTextNode);
}
@@ -3289,7 +3358,7 @@ EOF;
'utility_imports' => array(),
'stylesheets' => array(),
);
if($oThemesCommonNodes !== null) {
/** @var \DOMNodeList $oThemesCommonVariables */
$oThemesCommonVariables = $oThemesCommonNodes->GetNodes('variables/variable');
@@ -3297,7 +3366,7 @@ EOF;
$sVariableId = $oVariable->getAttribute('id');
$aThemesCommonParameters['variables'][$sVariableId] = $oVariable->GetText();
}
/** @var \DOMNodeList $oThemesCommonImports */
$oThemesCommonImports = $oThemesCommonNodes->GetNodes('imports/import');
foreach ($oThemesCommonImports as $oImport) {
@@ -3311,7 +3380,7 @@ EOF;
SetupLog::Warning('CompileThemes: Theme common has an import (#'.$sImportId.') without explicit xsi:type, it will be ignored. Check Datamodel XML Reference to fix it.');
}
}
// Stylesheets
// - Manually added in the XML
/** @var \DOMNodeList $oThemesCommonStylesheets */
@@ -3376,7 +3445,7 @@ EOF;
$aThemeParameters[$sThemeParameterName] = array_merge($aThemeParameter, $aThemesCommonParameters[$sThemeParameterName]);
}
}
$aThemes[$sThemeId] = [
'theme_parameters' => $aThemeParameters,
'precompiled_stylesheet' => $oTheme->GetChildText('precompiled_stylesheet', ''),
@@ -3570,8 +3639,8 @@ EOF;
{
SetupUtils::rrmdir($sTempTargetDir.'/branding/images');
}
// Compile themes
// Compile themes
$this->CompileThemes($oBrandingNode, $sTempTargetDir);
}
}
@@ -3612,11 +3681,11 @@ EOF;
{
$aPortalsConfig[$sPortalId]['deny'][] = $oProfile->getAttribute('id');
}
}
}
}
uasort($aPortalsConfig, array(get_class($this), 'SortOnRank'));
$this->sMainPHPCode .= "\n";
$this->sMainPHPCode .= "/**\n";
$this->sMainPHPCode .= " * Portal(s) definition(s) extracted from the XML definition at compile time\n";
@@ -3659,7 +3728,7 @@ EOF;
$oParamsReader = new MFParameters($oParams);
$aParametersConfig[$sModuleId] = $oParamsReader->GetAll();
}
$this->sMainPHPCode .= "\n";
$this->sMainPHPCode .= "/**\n";
$this->sMainPHPCode .= " * Modules parameters extracted from the XML definition at compile time\n";

View File

@@ -1,287 +0,0 @@
<?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;
}
}

View File

@@ -7,7 +7,6 @@
namespace Combodo\iTop\Application\Helper;
use Combodo\iTop\SessionTracker\SessionHandler;
use utils;
/**
@@ -35,7 +34,6 @@ class Session
}
if (!self::$bIsInitialized) {
SessionHandler::session_set_save_handler();
session_name('itop-'.md5(APPROOT));
}
@@ -216,4 +214,4 @@ class Session
return utils::IsModeCLI();
}
}
}

View File

@@ -9,7 +9,6 @@ namespace Combodo\iTop\Application\UI\Base\Component\Dashlet;
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
use utils;
class DashletBadge extends DashletContainer
{
@@ -30,11 +29,6 @@ class DashletBadge extends DashletContainer
protected $iCount;
/** @var string */
protected $sClassLabel;
/**
* @var string
* @since 3.1.1 3.2.0
*/
protected $sClassDescription;
/** @var string */
protected $sCreateActionUrl;
@@ -68,7 +62,6 @@ class DashletBadge extends DashletContainer
$this->sCreateActionUrl = $sCreateActionUrl;
$this->sCreateActionLabel = $sCreateActionLabel;
$this->aRefreshParams = $aRefreshParams;
$this->sClassDescription = '';
}
@@ -192,37 +185,6 @@ class DashletBadge extends DashletContainer
return $this;
}
/**
* @return string
* @since 3.1.1 3.2.0
*/
public function GetClassDescription(): string
{
return $this->sClassDescription;
}
/**
* @param string $sClassDescription
*
* @return DashletBadge
* @since 3.1.1 3.2.0
*/
public function SetClassDescription(string $sClassDescription)
{
$this->sClassDescription = $sClassDescription;
return $this;
}
/**
* @return bool
* @since 3.1.1
*/
public function HasClassDescription(): bool
{
return utils::IsNotNullOrEmptyString($this->sClassDescription);
}
public function GetJSRefresh(): string
{
return "$('#".$this->sId."').block();

View File

@@ -49,16 +49,6 @@ class NewsroomMenuFactory
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
*
@@ -110,4 +100,4 @@ class NewsroomMenuFactory
);
return $aParams;
}
}
}

View File

@@ -21,7 +21,6 @@ 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 Dict;
use JSPopupMenuItem;
@@ -57,68 +56,30 @@ class PopoverMenuFactory
->SetHorizontalPosition(PopoverMenu::ENUM_HORIZONTAL_POSITION_ALIGN_OUTER_RIGHT)
->SetVerticalPosition(PopoverMenu::ENUM_VERTICAL_POSITION_ABOVE);
$aUserMenuItems = [];
// Allowed portals
$aAllowedPortalsItems = static::PrepareAllowedPortalsItemsForUserMenu();
self::AddPopoverMenuItems($aAllowedPortalsItems, $aUserMenuItems);
if (!empty($aAllowedPortalsItems)) {
$oMenu->AddSection('allowed_portals')
->SetItems('allowed_portals', $aAllowedPortalsItems);
}
// User related pages
self::AddPopoverMenuItems(static::PrepareUserRelatedItemsForUserMenu(), $aUserMenuItems);
$oMenu->AddSection('user_related')
->SetItems('user_related', static::PrepareUserRelatedItemsForUserMenu());
// API: iPopupMenuExtension::MENU_USER_ACTIONS
$aAPIItems = static::PrepareAPIItemsForUserMenu($oMenu);
self::AddPopoverMenuItems($aAPIItems, $aUserMenuItems);
if (count($aAPIItems) > 0) {
$oMenu->AddSection('popup_menu_extension-menu_user_actions')
->SetItems('popup_menu_extension-menu_user_actions', $aAPIItems);
}
// Misc links
/*$oMenu->AddSection('misc')
->SetItems('misc', static::PrepareMiscItemsForUserMenu());*/
self::AddPopoverMenuItems(static::PrepareMiscItemsForUserMenu(), $aUserMenuItems);
self::SortPopoverMenuItems($aUserMenuItems);
$oMenu->AddSection('misc')
->AddItems('misc', $aUserMenuItems);
->SetItems('misc', static::PrepareMiscItemsForUserMenu());
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
@@ -312,4 +273,4 @@ class PopoverMenuFactory
return $oMenu;
}
}
}

View File

@@ -34,7 +34,6 @@ use MetaModel;
use UIExtKeyWidget;
use UserRights;
use utils;
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\NewsroomMenu\NewsroomMenuFactory;
/**
* Class NavigationMenu
@@ -275,7 +274,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
*/
public function IsNewsroomEnabled(): bool
{
return (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders());
return MetaModel::GetConfig()->Get('newsroom_enabled');
}
/**

View File

@@ -48,7 +48,7 @@ class NavigationMenuFactory
{
$oNewsroomMenu = null;
if (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders())
if (MetaModel::GetConfig()->Get('newsroom_enabled'))
{
$oNewsroomMenu = NewsroomMenuFactory::MakeNewsroomMenuForNavigationMenu();
}
@@ -57,4 +57,4 @@ class NavigationMenuFactory
new ApplicationContext(), PopoverMenuFactory::MakeUserMenuForNavigationMenu(), $oNewsroomMenu, NavigationMenu::BLOCK_CODE
);
}
}
}

View File

@@ -70,9 +70,6 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
/** @var AttributeLinkedSet $oAttDef attribute link set */
protected AttributeLinkedSet $oAttDef;
/** @var bool $bIsAttEditable Is attribute editable */
protected bool $bIsAttEditable;
/** @var string $sTargetClass links target classname */
protected string $sTargetClass;
@@ -122,12 +119,11 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
private function Init()
{
$this->sTargetClass = $this->GetTargetClass();
$this->InitIsAttEditable();
// User rights
$this->bIsAllowCreate = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
$this->bIsAllowModify = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
$this->bIsAllowDelete = $this->bIsAttEditable && UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
$this->bIsAllowCreate = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_CREATE) == UR_ALLOWED_YES;
$this->bIsAllowModify = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_MODIFY) == UR_ALLOWED_YES;
$this->bIsAllowDelete = UserRights::IsActionAllowed($this->oAttDef->GetLinkedClass(), UR_ACTION_DELETE) == UR_ALLOWED_YES;
}
@@ -200,26 +196,6 @@ abstract class AbstractBlockLinkSetViewTable extends UIContentBlock
$this->AddSubBlock($oBlock->GetRenderContent($oPage, $this->GetExtraParam(), $this->sTableId));
}
/**
* @return void
* @throws \CoreException
*/
private function InitIsAttEditable(): void
{
$iFlags = 0;
if ($this->oDbObject->IsNew())
{
$iFlags = $this->oDbObject->GetInitialStateAttributeFlags($this->sAttCode);
}
else
{
$iFlags = $this->oDbObject->GetAttributeFlags($this->sAttCode);
}
$this->bIsAttEditable = !($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE | OPT_ATT_HIDDEN));
}
/**
* GetTableId.
*

View File

@@ -242,6 +242,13 @@ JS
// TODO 3.0.0: Reuse theming mechanism for Full Moon
$sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl();
$this->add_linked_stylesheet($sCssThemeUrl);
$sCssRelPath = utils::GetCSSFromSASS(
'css/backoffice/main.scss',
array(
APPROOT.'css/backoffice/',
)
);
}
protected function GetReadyScriptsStartedTrigger(): ?string

View File

@@ -1,27 +0,0 @@
<?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' => [],
],
];
}
}

View File

@@ -1,234 +0,0 @@
<?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
}
}

View File

@@ -733,7 +733,6 @@ class AjaxRenderController
} else {
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
}
$oFullSetFilter->SetShowObsoleteData(utils::ShowObsoleteData());
$oWidget->DoAddObjects($oPage, $iMaxAddedId, $oFullSetFilter, $oObj);
$oKPI->ComputeAndReport('Data write');
}

View File

@@ -1,24 +0,0 @@
<?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);
}
}

View File

@@ -10,7 +10,6 @@ use Closure;
use Combodo\iTop\Service\Events\Description\EventDescription;
use ContextTag;
use CoreException;
use DBObject;
use Exception;
use ExecutionKPI;
use ReflectionClass;
@@ -54,12 +53,6 @@ final class EventService
/**
* Register a callback for a specific event
*
* **Warning** : be ultra careful on memory footprint ! each callback will be saved in {@see aEventListeners}, and a callback is
* made of the whole object instance and the method name ({@link https://www.php.net/manual/en/language.types.callable.php}).
* For example to register on DBObject instances, you should better use {@see DBObject::RegisterCRUDListener()}
*
* @uses aEventListeners
*
* @api
* @param string $sEvent corresponding event
* @param callable $callback The callback to call
@@ -68,12 +61,8 @@ final class EventService
* @param array|string|null $context context filter
* @param float $fPriority optional priority for callback order
*
* @return string registration identifier
* @return string Id of the registration
*
* @see DBObject::RegisterCRUDListener() to register in DBObject instances instead, to reduce memory footprint (callback saving)
*
* @since 3.1.0 method creation
* @since 3.1.0-3 3.1.1 3.2.0 N°6716 PHPDoc change to warn on memory footprint, and {@see DBObject::RegisterCRUDListener()} alternative
*/
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, array $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
{
@@ -145,8 +134,8 @@ final class EventService
return;
}
$oFirstException = null;
$sFirstExceptionMessage = null;
$oLastException = null;
$sLastExceptionMessage = null;
$bEventFired = false;
foreach (self::GetListeners($sEvent, $eventSource) as $aEventCallback) {
if (!self::MatchContext($aEventCallback['context'])) {
@@ -164,12 +153,9 @@ final class EventService
throw $e;
}
catch (Exception $e) {
$sExceptionMessage = "Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with non-blocking error: ".$e->getMessage();
EventServiceLog::Error($sExceptionMessage);
if (is_null($oFirstException)){
$oFirstException = $e;
$sFirstExceptionMessage = $sExceptionMessage;
}
$sLastExceptionMessage = "Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with non-blocking error: ".$e->getMessage();
EventServiceLog::Error($sLastExceptionMessage);
$oLastException = $e;
}
}
if ($bEventFired) {
@@ -177,9 +163,9 @@ final class EventService
}
$oKPI->ComputeStats('FireEvent', $sEvent);
if (!is_null($oFirstException)) {
EventServiceLog::Error("Throwing the last exception caught: $sFirstExceptionMessage");
throw $oFirstException;
if (!is_null($oLastException)) {
EventServiceLog::Error("Throwing the last exception caught: $sLastExceptionMessage");
throw $oLastException;
}
}

View File

@@ -1,32 +0,0 @@
<?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";
}
}

View File

@@ -1,240 +0,0 @@
<?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_*");
}
}

View File

@@ -3,16 +3,7 @@
{% apply spaceless %}
<div class="ibo-dashlet-badge--body{% if oUIBlock.IsHidden() %} ibo-is-hidden{% endif %}" id="{{ oUIBlock.GetId() }}"
data-role="ibo-dashlet-badge--body"
{% if oUIBlock.HasClassDescription() %}
{# Display both class name and description as the name could be truncated if too long #}
data-tooltip-content="{{ '<div class="ibo-dashlet-badge--body--tooltip-title">'|escape }}{{ oUIBlock.GetClassLabel() }}{{ '</div><div class="ibo-dashlet-badge--body--tooltip-description">'|escape }}{{ oUIBlock.GetClassDescription() }}{{ '</div>'|escape }}"
data-tooltip-html-enabled="true"
{% else %}
{# Display only class name as it could be truncated if too long #}
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}"
{% endif %}
{# Delay display to avoid having all tooltips appearing when mouse is just passing through the tabs #}
data-tooltip-show-delay="300">
data-tooltip-content="{{ oUIBlock.GetClassLabel() }}">
<div class="ibo-dashlet-badge--icon-container">
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #}
<img class="ibo-dashlet-badge--icon" src="{{ oUIBlock.GetClassIconUrl() }}" alt="">

View File

@@ -23,16 +23,16 @@
{% if aAction.confirmation is defined %}
// Prepare confirmation title
let sTitle = '{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s }}';
let sTitle = `{{ 'UI:Datatables:RowActions:ConfirmationDialog'|dict_s|raw }}`;
{% if aAction.confirmation.title is defined %}
sTitle = '{{ aAction.confirmation.title|dict_s }}';
sTitle = `{{ aAction.confirmation.title|dict_s|raw }}`;
{% endif %}
sTitle = sTitle.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);
// Prepare confirmation message
let sMessage = '{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s }}';
let sMessage = `{{ 'UI:Datatables:RowActions:ConfirmationMessage'|dict_s|raw }}`;
{% if aAction.confirmation.message is defined %}
sMessage = '{{ aAction.confirmation.message|dict_s }}';
sMessage = `{{ aAction.confirmation.message|dict_s|raw }}`;
{% endif %}
sMessage = sMessage.replaceAll('{item}', aRowData['{{ aAction.confirmation.row_data }}']);

View File

@@ -1,10 +0,0 @@
<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>

View File

@@ -1,25 +1,14 @@
<div id="welcome_popup_dialog" class="ibo-welcome-popup--dialog ibo-is-hidden">
<div class="ibo-welcome-popup--content">
{% for message in messages %}
<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 }}">
{% if message.twig is defined %}
{{ include([message.twig ~ '.html.twig', message.twig ~ '.twig', message.twig], message.parameters ?? {}, sandboxed = true) }}
{% else %}
{{ message.html|raw }}
{% endif %}
<div class="ibo-welcome-popup--button" data-message-uuid="{{ message.uuid }}">
{% UIButton ForPrimaryAction{'sLabel':'UI:WelcomePopup:Button:Acknowledge'|dict_s, 'bIsSubmit': false } %}
</div>
</div>
{% endfor %}
</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>
<div id="welcome_popup">
<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 class="ibo-welcome-popup--text--options">
<input type="checkbox" checked id="display_welcome_popup"/><label for="display_welcome_popup">{{'UI:DisplayThisMessageAtStartup'| dict_s}}</label>
</div>
</div>
</div>

View File

@@ -1,38 +1,14 @@
$('#welcome_popup_dialog').removeClass('ibo-is-hidden');
$('#welcome_popup_dialog').dialog({
modal: true,
width: '60%',
autoOpen: true,
title: $('div[data_role=welcome-popup-title]').first().attr('data-title'),
close: function() { $('#welcome_popup_dialog').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();
}
}
$('#welcome_popup').dialog( { width:'60%', height: 'auto', title: '{{ 'UI:WelcomeMenu:Title'|dict_s }}', autoOpen: true, modal:true,
close: function() {
var bDisplay = $('#display_welcome_popup:checked').length;
SetUserPreference('welcome_popup', bDisplay, true);
},
buttons: [{
text: "{{ 'UI:Button:Ok'|dict_s }}", click: function() {
$(this).dialog( "close" ); $(this).remove();
}}],
});
if ($('#welcome_popup').height() > ($(window).height()-70))
{
$('#welcome_popup').height($(window).height()-70);
}

View File

@@ -1,8 +1,3 @@
[infra]
php_version=7.4-apache
; N°6629 perf bug on some tests on mariadb for now, so specifying MySQL
db_version=5.7
[itop]
itop_setup=tests/setup_params/default-params.xml
itop_backup=tests/backups/backup-itop.tar.gz

View File

@@ -87,10 +87,6 @@ Add annotation `@runInSeparateProcess`
Each and every test case will run in a separate
process.
Note : before N°6658 (3.0.4 / 3.1.1 / 3.2.0) we were also adding the `@backupGlobals disabled`
and `@preserveGlobalState disabled` annotations. This is no longer necessary as the first has this default value
already, and the second one is now set in iTopTestCase as a PHP class attribute.
#### At the test class level
Add annotation `@runTestsInSeparateProcesses`
Each and every test case in the class will run in a separate

View File

@@ -1,248 +0,0 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Integration;
use Combodo\iTop\Test\UnitTest\ItopTestCase;
class DictionariesConsistencyAfterSetupTest extends ItopTestCase
{
//used by testDictEntryValues
//to filter false positive broken traductions
private static $aLabelCodeNotToCheck = [
//use of Dict::S not Format
"UI:Audit:PercentageOk",
//unused dead labels
"Class:DatacenterDevice/Attribute:redundancy/count",
"Class:DatacenterDevice/Attribute:redundancy/disabled",
"Class:DatacenterDevice/Attribute:redundancy/percent",
"Class:TriggerOnThresholdReached/Attribute:threshold_index+"
];
public function FormatProvider(){
return [
'key does not exist in dictionnary' => [
'sTemplate' => null,
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
],
'traduction that breaks expected nb of arguments' => [
'sTemplate' => 'toto %1$s titi %2$s',
'sExpectedTraduction' => 'ITOP::DICT:FORMAT:BROKEN:KEY - 1',
],
'traduction ok' => [
'sTemplate' => 'toto %1$s titi',
'sExpectedTraduction' => 'toto 1 titi',
],
];
}
/**
* @param $sTemplate : if null it will not create dict entry
* @since 2.7.10 N°5491 - Inconsistent dictionary entries regarding arguments to pass to Dict::Format
* @dataProvider FormatProvider
*/
public function testFormatWithOneArgumentAndCustomKey(?string $sTemplate, $sExpectedTranslation){
//tricky way to mock GetLabelAndLangCode behavior via connected user language
$sLangCode = \Dict::GetUserLanguage();
$aDictByLang = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
$sDictKey = 'ITOP::DICT:FORMAT:BROKEN:KEY';
if (! is_null($sTemplate)){
$aDictByLang[$sLangCode][$sDictKey] = $sTemplate;
}
$this->SetNonPublicStaticProperty(\Dict::class, 'm_aData', $aDictByLang);
$this->assertEquals($sExpectedTranslation, \Dict::Format($sDictKey, "1"));
}
//test works after setup (no annotation @beforesetup)
//even if it does not extend ItopDataTestCase
private function ReadDictKeys($sLangCode) : array {
\Dict::InitLangIfNeeded($sLangCode);
$aDictEntries = $this->GetNonPublicStaticProperty(\Dict::class, 'm_aData');
return $aDictEntries[$sLangCode];
}
/**
* foreach dictionnary label map (key/value) it counts the number argument that should be passed to use Dict::Format
* examples:
* for "gabu zomeu" label there are no args
* for "shadok %1 %2 %3" there are 3 args
*
* limitation: there is no validation check for "%3 itop %2 combodo" which seems unconsistent
* @param $aDictEntry
*
* @return array
*/
private function GetKeyArgCountMap($aDictEntry) {
$aKeyArgsCount = [];
foreach ($aDictEntry as $sKey => $sValue){
$aKeyArgsCount[$sKey] = $this->countArg($sValue);
}
ksort($aKeyArgsCount);
return $aKeyArgsCount;
}
private function countArg($sLabel) {
$iMaxIndex = 0;
if (preg_match_all("/%(\d+)/", $sLabel, $aMatches)){
$aSubMatches = $aMatches[1];
if (is_array($aSubMatches)){
foreach ($aSubMatches as $aCurrentMatch){
$iIndex = $aCurrentMatch;
$iMaxIndex = ($iMaxIndex < $iIndex) ? $iIndex : $iMaxIndex;
}
}
} else if ((false !== strpos($sLabel, "%s"))
|| (false !== strpos($sLabel, "%d"))
){
$iMaxIndex = 1;
}
return $iMaxIndex;
}
/**
* Warning: hardcoded list of languages
* It is hard to have it dynamically via Dict::GetLanguages as for each lang Dict::Init should be called first
**/
public function LangCodeProvider(){
return [
'cs' => [ 'CS CZ' ],
'da' => [ 'DA DA' ],
'de' => [ 'DE DE' ],
'en' => [ 'EN US' ],
'es' => [ 'ES CR' ],
'fr' => [ 'FR FR' ],
'hu' => [ 'HU HU' ],
'it' => [ 'IT IT' ],
'ja' => [ 'JA JP' ],
'nl' => [ 'NL NL' ],
'pt' => [ 'PT BR' ],
'ru' => [ 'RU RU' ],
'sk' => [ 'SK SK' ],
'tr' => [ 'TR TR' ],
'zh' => [ 'ZH CN' ],
];
}
/**
* compare en and other dictionaries and check that for all labels there is the same number of arguments
* if not Dict::Format could raise an exception for some languages. translation should be done again...
* @dataProvider LangCodeProvider
*/
public function testDictEntryValues($sLanguageCodeToTest)
{
$sReferenceLangCode = 'EN US';
$aReferenceLangDictEntry = $this->ReadDictKeys($sReferenceLangCode);
$aDictEntry = $this->ReadDictKeys($sLanguageCodeToTest);
$aKeyArgsCountMap = [];
$aKeyArgsCountMap[$sReferenceLangCode] = $this->GetKeyArgCountMap($aReferenceLangDictEntry);
//$aKeyArgsCountMap[$sCode] = $this->GetKeyArgCountMap($aDictEntry);
//set user language
$this->SetNonPublicStaticProperty(\Dict::class, 'm_sCurrentLanguage', $sLanguageCodeToTest);
$aMismatchedKeys = [];
foreach ($aKeyArgsCountMap[$sReferenceLangCode] as $sKey => $iExpectedNbOfArgs){
if (0 === $iExpectedNbOfArgs){
//no arg needed in EN.
//let s assume job has been done correctly in EN to simplify
continue;
}
if (in_array($sKey, self::$aLabelCodeNotToCheck)){
//false positive: do not test
continue;
}
if (array_key_exists($sKey, $aDictEntry)){
$aPlaceHolders = [];
for ($i=0; $i<$iExpectedNbOfArgs; $i++){
$aPlaceHolders[]=$i;
}
$sLabelTemplate = $aDictEntry[$sKey];
try{
vsprintf($sLabelTemplate, $aPlaceHolders);
} catch(\Throwable $e){
$sError = $e->getMessage();
if (array_key_exists($sError, $aMismatchedKeys)){
$aMismatchedKeys[$sError][$sKey] = $iExpectedNbOfArgs;
} else {
$aMismatchedKeys[$sError] = [$sKey => $iExpectedNbOfArgs];
}
}
}
}
$iCount = 0;
foreach ($aMismatchedKeys as $sError => $aKeys){
var_dump($sError);
foreach ($aKeys as $sKey => $iExpectedNbOfArgs) {
$iCount++;
if ($sReferenceLangCode === $sLanguageCodeToTest) {
var_dump([
'key label' => $sKey,
'expected nb of expected args' => $iExpectedNbOfArgs,
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
]);
} else {
var_dump([
'key label' => $sKey,
'expected nb of expected args' => $iExpectedNbOfArgs,
"key value in $sLanguageCodeToTest" => $aDictEntry[$sKey],
"key value in $sReferenceLangCode" => $aReferenceLangDictEntry[$sKey],
]);
}
}
}
$sErrorMsg = sprintf("%s broken propertie(s) on $sLanguageCodeToTest dictionaries! either change the dict value in $sLanguageCodeToTest or add it in ignored label list (cf aLabelCodeNotToCheck)", $iCount);
$this->assertEquals([], $aMismatchedKeys, $sErrorMsg);
}
public function VsprintfProvider(){
return [
'not enough args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => [],
],
'exact nb of args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => ["1"],
],
'too much args' => [
"sLabelTemplate" => "$1%s",
"aPlaceHolders" => ["1", "2"],
],
'\"% ok\" without args' => [
"sLabelTemplate" => "% ok",
"aPlaceHolders" => [],
],
'\"% ok $1%s\" without args' => [
"sLabelTemplate" => "% ok",
"aPlaceHolders" => ['1'],
],
];
}
/**
* @dataProvider VsprintfProvider
public function testVsprintf($sLabelTemplate, $aPlaceHolders){
try{
$this->markTestSkipped("usefull to check a specific PHP version behavior");
vsprintf($sLabelTemplate, $aPlaceHolders);
$this->assertTrue(true);
} catch(\Throwable $e) {
$this->assertTrue(false, "label \'" . $sLabelTemplate . " failed with " . var_export($aPlaceHolders, true) );
}
}
*/
}

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
bootstrap="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>

View File

@@ -16,7 +16,9 @@ namespace Combodo\iTop\Test\UnitTest;
use ArchivedObjectException;
use CMDBObject;
use CMDBSource;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Config;
use Contact;
use DBObject;
use DBObjectSet;
@@ -32,6 +34,7 @@ use MetaModel;
use Person;
use PluginManager;
use Server;
use SetupUtils;
use TagSetFieldData;
use Ticket;
use URP_UserProfile;
@@ -54,7 +57,6 @@ define('TAG_ATTCODE', 'domains');
*
* @since 2.7.7 3.0.1 3.1.0 N°4624 processIsolation is disabled by default and must be enabled in each test needing it (basically all tests using
* iTop datamodel)
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
*/
abstract class ItopDataTestCase extends ItopTestCase
{
@@ -948,9 +950,6 @@ abstract class ItopDataTestCase extends ItopTestCase
}
}
/**
* @since 3.0.4 3.1.1 3.2.0 N°6658 method creation
*/
protected function assertDBChangeOpCount(string $sClass, $iId, int $iExpectedCount)
{
$oSearch = new \DBObjectSearch('CMDBChangeOp');

View File

@@ -10,12 +10,14 @@ use CMDBSource;
use MySQLTransactionNotClosedException;
use PHPUnit\Framework\TestCase;
use SetupUtils;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
/**
* Class ItopTestCase
*
* Helper class to extend for tests that DO NOT need to access the DataModel or the Database
*
* @since 3.0.4 3.1.1 3.2.0 N°6658 move some setUp/tearDown code to the corresponding methods *BeforeClass to speed up tests process time.
* @author Eric Espie <eric.espie@combodo.com>
* @package Combodo\iTop\Test\UnitTest
*/
abstract class ItopTestCase extends TestCase
{
@@ -23,10 +25,7 @@ abstract class ItopTestCase extends TestCase
public static $DEBUG_UNIT_TEST = false;
/**
* @link https://docs.phpunit.de/en/9.6/annotations.html#preserveglobalstate PHPUnit `preserveGlobalState` annotation documentation
*
* @since 3.0.4 3.1.1 3.2.0 N°6658 Override default value creation so that we don't need to add the annotation on each test classes that have runInSeparateProcess.
* This parameter isn't used when test is run in the same process so ok to change it globally !
* Override the default value to disable the backup of globals in case of tests run in a separate process
*/
protected $preserveGlobalState = false;
@@ -77,9 +76,7 @@ abstract class ItopTestCase extends TestCase
/**
* @throws \MySQLTransactionNotClosedException see N°5538
*
* @since 2.7.8 3.0.3 3.1.0 N°5538
* @since 3.0.4 3.1.1 3.2.0 N°6658 if transaction not closed, we are now doing a rollback
*/
protected function tearDown(): void
{
@@ -95,20 +92,28 @@ abstract class ItopTestCase extends TestCase
}
}
/**
* Helper than can be called in the context of a data provider
*
* @since 3.0.4 3.1.1 3.2.0 N°6658 method creation
*/
/** Helper than can be called in the context of a data provider */
public static function GetAppRoot()
{
if (defined('APPROOT')) {
return APPROOT;
}
$sAppRootPath = static::GetFirstDirUpContainingFile(__DIR__, 'approot.inc.php');
return $sAppRootPath . '/';
$sSearchPath = __DIR__;
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
if (file_exists($sSearchPath.'/approot.inc.php')) {
break;
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in '.$sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
}
return $sSearchPath.'/';
}
/**
@@ -147,23 +152,6 @@ abstract class ItopTestCase extends TestCase
require_once $this->GetAppRoot() . $sFileRelPath;
}
/**
* Helper to load a module file. The caller test must be in that module !
* Will browse dir up to find a module.*.php
*
* @param string $sFileRelPath for example 'portal/src/Helper/ApplicationHelper.php'
* @since 2.7.10 3.1.1 3.2.0 N°6709 method creation
*/
protected function RequireOnceCurrentModuleFile(string $sFileRelPath): void
{
$aStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
$sCallerFileFullPath = $aStack[0]['file'];
$sCallerDir = dirname($sCallerFileFullPath);
$sModuleRootPath = static::GetFirstDirUpContainingFile($sCallerDir, 'module.*.php');
require_once $sModuleRootPath . $sFileRelPath;
}
/**
* Require once a unit test file (eg. a mock class) from its relative path from the *current* dir.
* This ensure that required files don't crash when unit tests dir is moved in the iTop structure (see N°5608)
@@ -181,26 +169,6 @@ abstract class ItopTestCase extends TestCase
require_once $sCallerDirAbsPath . DIRECTORY_SEPARATOR . $sFileRelPath;
}
private static function GetFirstDirUpContainingFile(string $sSearchPath, string $sFileToFindGlobPattern): ?string
{
for ($iDepth = 0; $iDepth < 8; $iDepth++) {
$aGlobFiles = glob($sSearchPath . '/' . $sFileToFindGlobPattern);
if (is_array($aGlobFiles) && (count($aGlobFiles) > 0)) {
return $sSearchPath . '/';
}
$iOffsetSep = strrpos($sSearchPath, '/');
if ($iOffsetSep === false) {
$iOffsetSep = strrpos($sSearchPath, '\\');
if ($iOffsetSep === false) {
// Do not throw an exception here as PHPUnit will not show it clearly when determing the list of test to perform
return 'Could not find the approot file in ' . $sSearchPath;
}
}
$sSearchPath = substr($sSearchPath, 0, $iOffsetSep);
}
return null;
}
protected function debug($sMsg)
{
if (static::$DEBUG_UNIT_TEST) {
@@ -275,8 +243,9 @@ abstract class ItopTestCase extends TestCase
return $method->invokeArgs($oObject, $aArgs);
}
/**
* @since 2.7.10 3.1.0
* @since 3.1.0
*/
public function GetNonPublicStaticProperty(string $sClass, string $sProperty)
{
@@ -303,7 +272,7 @@ abstract class ItopTestCase extends TestCase
}
/**
* @since 2.7.10 3.1.0
* @since 3.1.0
*/
private function GetProperty(string $sClass, string $sProperty): \ReflectionProperty
{
@@ -329,7 +298,7 @@ abstract class ItopTestCase extends TestCase
}
/**
* @since 2.7.10 3.1.0
* @since 3.1.0
*/
public function SetNonPublicStaticProperty(string $sClass, string $sProperty, $value)
{
@@ -409,4 +378,4 @@ abstract class ItopTestCase extends TestCase
}
closedir($dir);
}
}
}

View File

@@ -6,6 +6,8 @@
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class ApplicationObjectExtensionTest extends \Combodo\iTop\Test\UnitTest\ItopDataTestCase
{

View File

@@ -1,96 +0,0 @@
<?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);
}
}

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