Compare commits

...

19 Commits

Author SHA1 Message Date
Eric Espie
b87845c844 Merge branch 'support/3.0' into feature/faf_sqlite 2022-03-07 18:33:43 +01:00
Molkobain
1f65ab5a92 N°4642 - Improve conventions
* Rename SCSS variables
  * Optimize SCSS rules
2022-03-07 17:05:29 +01:00
Molkobain
7cfac7300b N°4642 - Fix typo in SCSS variables 2022-03-07 16:31:43 +01:00
Molkobain
ede720c9b9 Merge remote-tracking branch 'origin/support/2.7' into support/3.0 2022-03-07 12:52:09 +01:00
Molkobain
684efee5d2 N°4664 - Code format 2022-03-07 12:49:27 +01:00
acognet
e347989dcb N°4664 - Core Update : block zip file upload until instance declared OK 2022-03-07 12:05:54 +01:00
Eric Espie
37dd887cf1 SQLite Log format 2022-03-07 12:00:04 +01:00
Eric Espie
b6cfb40f8f SQLite Log format 2022-03-07 11:56:42 +01:00
acognet
e1051e7a47 N°4654 - Missing licence Information in About iTop for non admin users - add since 3.0.1 2022-03-07 11:35:18 +01:00
Molkobain
7e8eb26866 N°4654 - Change block ID to match conventions 2022-03-07 09:50:07 +01:00
Molkobain
6775a3e928 Code cleanup 2022-03-07 09:47:54 +01:00
Molkobain
faa38155e5 N°4911 - Mentions: Fix DBObject::Reload() for classes with an image attribute 2022-03-06 22:44:13 +01:00
Molkobain
558bbc3357 Revert "N°4911 - Mentions: Fix Person picture not displayed if marker configured on parent class (Contact)"
This reverts commit 106127e6b7.

As the image attribute can be different depending on the object finalclass, it cannot be added in the DBObjectSet::OptimizeColumnLoad(), which means that retrieving it within the loop might lead to a complete DBObject::Reload() of the object which can have a real impact on performances depending on the objects.
2022-03-06 22:44:13 +01:00
Molkobain
cd7f9e478f N°4913 - Avoid object initials to overflow in medallions 2022-03-06 22:44:12 +01:00
Molkobain
e3586cff65 Unit tests: Add unit cyrillic alphabet test case for utils::ToAcronym() 2022-03-06 16:31:48 +01:00
Molkobain
106127e6b7 N°4911 - Mentions: Fix Person picture not displayed if marker configured on parent class (Contact) 2022-03-06 12:49:11 +01:00
Molkobain
2299983db3 N°4741 - Factorize activation of on mention triggers 2022-03-06 11:54:39 +01:00
acognet
9f27cf2b84 N°4525 - bad source for extensions in system information and about iTop with iTop Pr 2022-03-03 15:14:28 +01:00
Pierre Goiffon
f78986009f Improve messages of iTopModuleXmlInstallationChecklistTest::testAllModuleAreIncludedInInstallationXml 2022-03-03 10:46:06 +01:00
35 changed files with 225 additions and 148 deletions

View File

@@ -251,7 +251,7 @@ class UIExtKeyWidget
$aOption['picture_url'] = $oImage->GetDisplayURL($sClassAllowed, $oObj->GetKey(), $sObjectImageAttCode);
$aOption['initials'] = '';
} else {
$aOption['initials'] = utils::ToAcronym($oObj->Get('friendlyname'));
$aOption['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObj->Get('friendlyname')));
}
}
array_push($aOptions, $aOption);
@@ -829,7 +829,7 @@ JS
}
if (array_key_exists('initials', $aValue)) {
$aElt['initials'] = $aValue['initials'];
$aElt['initials'] = utils::FormatInitialsForMedallion($aValue['initials']);
if (array_key_exists('picture_url', $aValue)) {
$aElt['picture_url'] = $aValue['picture_url'];
}

View File

@@ -3050,6 +3050,20 @@ HTML;
return $aMentionedObjects;
}
/**
* Note: This method is not ideal, but other solutions seemed even less ideal:
* * Add a "$sMaxLength" param. to utils::ToAcronym(): Does not work for every use cases (see corresponding ticket) as in some parts utils::ToAcronym isn't necessarly meant to be used in a medallion.
*
* @param string $sInitials
*
* @return string Truncates $sInitials so it can fit in medallions
* @since 3.0.1 N°4913
*/
public static function FormatInitialsForMedallion(string $sInitials): string
{
return mb_substr($sInitials, 0, 3);
}
/**
* @param $sUrl
* @param string $sParamName

View File

@@ -2936,52 +2936,7 @@ abstract class DBObject implements iDisplay
}
// - TriggerOnObjectMention
// 1 - Check if any caselog updated
$aChanges = $this->m_aOrigValues;
$aUpdatedLogAttCodes = array();
foreach($aChanges as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if($oAttDef instanceof AttributeCaseLog && $value->GetModifiedEntry() !== '')
{
$aUpdatedLogAttCodes[] = $sAttCode;
}
}
// 2 - Find mentioned objects
$aMentionedObjects = array();
foreach ($aUpdatedLogAttCodes as $sAttCode) {
/** @var \ormCaseLog $oUpdatedCaseLog */
$oUpdatedCaseLog = $this->Get($sAttCode);
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
}
// 3 - Trigger for those objects
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
foreach ($aMentionedIds as $sMentionedId) {
/** @var \DBObject $oMentionedObject */
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
$aTriggerArgs = $this->ToArgs('this') + array('mentioned->object()' => $oMentionedObject);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \TriggerOnObjectMention $oTrigger */
try {
// Ensure to handle only mentioned object in the trigger's scope
if ($oTrigger->IsMentionedObjectInScope($oMentionedObject) === false) {
continue;
}
$oTrigger->DoActivate($aTriggerArgs);
}
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
$this->ActivateOnMentionTriggers(true);
return $this->m_iKey;
}
@@ -3245,50 +3200,7 @@ abstract class DBObject implements iDisplay
// Activate any existing trigger
$sClass = get_class($this);
// - TriggerOnObjectMention
// 1 - Check if any caselog updated
$aUpdatedLogAttCodes = array();
foreach($aChanges as $sAttCode => $value)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if($oAttDef instanceof AttributeCaseLog)
{
$aUpdatedLogAttCodes[] = $sAttCode;
}
}
// 2 - Find mentioned objects
$aMentionedObjects = array();
foreach ($aUpdatedLogAttCodes as $sAttCode) {
/** @var \ormCaseLog $oUpdatedCaseLog */
$oUpdatedCaseLog = $this->Get($sAttCode);
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
}
// 3 - Trigger for those objects
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
foreach ($aMentionedIds as $sMentionedId) {
/** @var \DBObject $oMentionedObject */
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
$aTriggerArgs = $this->ToArgs('this') + array('mentioned->object()' => $oMentionedObject);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \TriggerOnObjectMention $oTrigger */
try {
// Ensure to handle only mentioned object in the trigger's scope
if ($oTrigger->IsMentionedObjectInScope($oMentionedObject) === false) {
continue;
}
$oTrigger->DoActivate($aTriggerArgs);
}
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
$this->ActivateOnMentionTriggers(false);
$bHasANewExternalKeyValue = false;
$aHierarchicalKeys = array();
@@ -3501,6 +3413,74 @@ abstract class DBObject implements iDisplay
return $this->m_iKey;
}
/**
* Activate TriggerOnObjectMention triggers for the current object
*
* @param bool $bNewlyCreatedObject
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
* @since 3.0.1 N°4741
*/
private function ActivateOnMentionTriggers(bool $bNewlyCreatedObject): void
{
$sClass = get_class($this);
$aChanges = $bNewlyCreatedObject ? $this->m_aOrigValues : $this->ListChanges();
// 1 - Check if any caselog updated
$aUpdatedLogAttCodes = [];
foreach ($aChanges as $sAttCode => $value) {
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeCaseLog) {
// Skip empty log on creation
if ($bNewlyCreatedObject && $value->GetModifiedEntry() === '') {
continue;
}
$aUpdatedLogAttCodes[] = $sAttCode;
}
}
// 2 - Find mentioned objects
$aMentionedObjects = [];
foreach ($aUpdatedLogAttCodes as $sAttCode) {
/** @var \ormCaseLog $oUpdatedCaseLog */
$oUpdatedCaseLog = $this->Get($sAttCode);
$aMentionedObjects = array_merge_recursive($aMentionedObjects, utils::GetMentionedObjectsFromText($oUpdatedCaseLog->GetModifiedEntry()));
}
// 3 - Trigger for those objects
// TODO: This should be refactored and moved into the caselogs loop, otherwise, we won't be able to know which case log triggered the action.
foreach ($aMentionedObjects as $sMentionedClass => $aMentionedIds) {
foreach ($aMentionedIds as $sMentionedId) {
/** @var \DBObject $oMentionedObject */
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
$aTriggerArgs = $this->ToArgs('this') + ['mentioned->object()' => $oMentionedObject];
$aParams = ['class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL)];
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), [], $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \TriggerOnObjectMention $oTrigger */
try {
// Ensure to handle only mentioned object in the trigger's scope
if ($oTrigger->IsMentionedObjectInScope($oMentionedObject) === false) {
continue;
}
$oTrigger->DoActivate($aTriggerArgs);
}
catch (Exception $e) {
utils::EnrichRaisedException($oTrigger, $e);
}
}
}
}
}
/**
* @internal
* Save updated fields previous values for {@see DBObject::DBUpdate()} callbacks

View File

@@ -455,6 +455,7 @@ class LogFileNameBuilderFactory
class FileLog
{
protected $oFileNameBuilder;
protected $bHasSQLite;
/**
* FileLog constructor.
@@ -467,6 +468,22 @@ class FileLog
public function __construct($sFileName = '')
{
$this->oFileNameBuilder = LogFileNameBuilderFactory::GetInstance($sFileName);
$sLogFilePath = $this->oFileNameBuilder->GetLogFilePath();
$this->bHasSQLite = class_exists('SQLite3');
if ($this->bHasSQLite) {
if (empty($sLogFilePath)) {
return;
}
$bCreate = false;
$sDBFilePath = "$sLogFilePath.db";
if (!is_file($sDBFilePath)) {
$bCreate = true;
}
$this->DB = new SQLite3($sDBFilePath);
if ($bCreate) {
$this->DB->exec('CREATE TABLE log (id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT, level TEXT, user TEXT, content LONGTEXT, context LONGTEXT, channel TEXT)');
}
}
}
public function Error($sText, $sChannel = '', $aContext = array())
@@ -502,6 +519,17 @@ class FileLog
protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array())
{
if ($this->bHasSQLite) {
$stmt = $this->DB->prepare('INSERT INTO log (date, level, user, content, context, channel) VALUES (:date, :level, :user, :content, :context, :channel)');
$stmt->bindValue(':date', date('Y-m-d H:i:s'));
$stmt->bindValue(':level', $sLevel);
$stmt->bindValue(':user', LogAPI::GetUserInfo());
$stmt->bindValue(':content', $sText);
$stmt->bindValue(':context', empty($aContext) ? '' : var_export($aContext, true));
$stmt->bindValue(':channel', $sChannel);
$stmt->execute();
}
$sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7));
$sTextPrefix .= ' | ';
$sTextPrefix .= str_pad(LogAPI::GetUserInfo(), 5)." | ";

View File

@@ -1,29 +1,26 @@
/*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @copyright Copyright (C) 2010-2022 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/* SCSS variables */
$ibo-caselog-entry-in-collapsible-section--body--background-color: transparentize($ibo-color-grey-100,0.5) !default;
$ibo-caselog-entry-in-collapsible-section--body--padding: $ibo-spacing-300 !default;
$ibo-caselog-entry-in-collapsible-section--body--color: $ibo-color-grey-900 !default;
$ibo-collapsible-section-within-alert--body--padding: $ibo-spacing-300 !default;
$ibo-collapsible-section-within-alert--body--text-color: $ibo-color-grey-900 !default;
/* - caselog display in ormcaselog */
.ibo-alert--body {
.ibo-collapsible-section {
margin: 0;
min-width: 22em;
.ibo-collapsible-section--header .ibo-collapsible-section--title {
@extend %ibo-font-size-100;
.ibo-collapsible-section--header .ibo-collapsible-section--title,
.ibo-collapsible-section--body {
@extend %ibo-font-size-150;
}
.ibo-collapsible-section--body {
@extend %ibo-font-size-100;
color: $ibo-caselog-entry-in-collapsible-section--body--color;
padding: $ibo-caselog-entry-in-collapsible-section--body--padding;
background-color: $ibo-caselog-entry-in-collapsible-section--body--background-color;
color: $ibo-collapsible-section-within-alert--body--text-color;
padding: $ibo-collapsible-section-within-alert--body--padding;
}
}
> * + .ibo-collapsible-section {
margin-top: 8px;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -76,6 +76,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Anwendungsupgrade nicht möglich: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Vorsicht: App-Upgrade kann fehlerschlagen: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Einige angepasste Dateien wurden erkannt</b>, eine Teil-Update kann nicht ausgeführt werden.<br/>Befolgen Sie das <a href="%2$s">Verfahren</a>, um Ihr iTop manuell zu aktualisieren. Sie müssen das <a href="%1$s">Setup</a> benutzen, um Ihre Applikation zu aktualisieren.<br />',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Bereit zum Upgrade',

View File

@@ -76,7 +76,7 @@ Dict::Add('EN US', 'English', 'English', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.',
'iTopUpdate:UI:CheckInProgress'=>'Please wait during integrity check',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check',

View File

@@ -77,6 +77,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'La aplicación no puede ser actualizada: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Advertencia: la actualización de la aplicación puede fallar: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Listo para empezar',

View File

@@ -76,6 +76,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'L\'application ne peut pas être mise à jour : %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Attention : la mise à jour de l\'application peut échouer : %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Des fichiers modifiés ont été détectés</b>, une mise à jour partielle ne peut pas être effectuée.<br />Suivez la <a href="%2$s"> procedure</a> pour mettre à jour manuellement votre iTop. Vous devez utiliser la page <a href="%1$s">d\'installation</a> pour mettre à jour l\'application.',
'iTopUpdate:UI:CheckInProgress' => 'Veuillez patienter pendant la vérification des fichiers',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Prêt pour l\\installation',

View File

@@ -76,6 +76,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -78,6 +78,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Updaten van de toepassing is niet mogelijk: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Klaar om verder te gaan',

View File

@@ -76,6 +76,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Nie można zaktualizować aplikacji: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Ostrzeżenie: aktualizacja aplikacji może się nie powieść: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Gotowy do startu',

View File

@@ -76,6 +76,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Aplicação não pode ser atualizada: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Atenção: a atualização da aplicação pode falhar: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Pronto para começar',

View File

@@ -64,6 +64,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Приложение не может быть обновлено: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Всё готово к началу',

View File

@@ -76,6 +76,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
'iTopUpdate:UI:CanCoreUpdate:No' => 'Application cannot be updated: %1$s~~',
'iTopUpdate:UI:CanCoreUpdate:Warning' => 'Warning: application update can fail: %1$s~~',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => 'Ready to start~~',

View File

@@ -76,6 +76,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'iTopUpdate:UI:CanCoreUpdate:No' => '应用无法升级: %1$s',
'iTopUpdate:UI:CanCoreUpdate:Warning' => '警告: 应用升级可能失败: %1$s',
'iTopUpdate:UI:CannotUpdateUseSetup' => '<b>Some modified files were detected</b>, a partial update cannot be executed.</br>Follow the <a href="%2$s"> procedure</a> in order to manually upgrade your iTop. You must use the <a href="%1$s">setup</a> to update the application.~~',
'iTopUpdate:UI:CheckInProgress' => 'Please wait during integrity check~~',
// Setup Messages
'iTopUpdate:UI:SetupMessage:Ready' => '准备开始',

View File

@@ -81,8 +81,10 @@ class FilesIntegrity
* Check that files present in iTop folder corresponds to the manifest
*
* @param string $sRootPath
* @param bool $bCheckNewModule
*
* @throws \Combodo\iTop\FilesInformation\Service\FileIntegrityException
* @since 3.0.1 Add $bCheckNewModule parameter
*/
public static function CheckInstallationIntegrity($sRootPath = APPROOT, $bCheckNewModule = false)
{

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -47,6 +47,3 @@ SetupWebPage::AddModule(
),
)
);
?>

View File

@@ -1428,7 +1428,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'UI:About:InstallationOptions' => 'Options d\'installation',
'UI:About:ManualExtensionSource' => 'Extension',
'UI:About:Extension_Version' => 'Version: %1$s',
'UI:About:RemoteExtensionSource' => 'iTop Hub',
'UI:About:RemoteExtensionSource' => 'Data',
'UI:DisconnectedDlgMessage' => 'Vous êtes déconnecté(e). Vous devez vous identifier pour pouvoir continuer à utiliser l\'application.',
'UI:DisconnectedDlgTitle' => 'Attention !',

View File

@@ -1431,7 +1431,7 @@ Keď sú priradené spúštačom, každej akcii je dané číslo "príkazu", šp
'UI:About:InstallationOptions' => 'Installation options~~',
'UI:About:ManualExtensionSource' => 'Extension~~',
'UI:About:Extension_Version' => 'Version: %1$s~~',
'UI:About:RemoteExtensionSource' => 'iTop Hub~~',
'UI:About:RemoteExtensionSource' => 'Data~~',
'UI:DisconnectedDlgMessage' => 'You are disconnected. You must identify yourself to continue using the application.~~',
'UI:DisconnectedDlgTitle' => 'Warning!~~',

View File

@@ -19,11 +19,11 @@
// Helpers
function ShowAboutBox(sTitle)
{
var loadingDialog = $('<div id="block-about-iTop"></div>');
var loadingDialog = $('<div id="ibo-about-box--loader"></div>');
loadingDialog.dialog( {title:sTitle,autoOpen: true, modal: true, width: 700, height:350});
$('#block-about-iTop').block();
$('#ibo-about-box--loader').block();
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){
$('#block-about-iTop').unblock();
$('#ibo-about-box--loader').unblock();
$('body').append(data);
}).always(function() {
loadingDialog.empty();

View File

@@ -2515,7 +2515,7 @@ EOF
);
$oSet = new DBObjectSet($oSearch, [], $aSearchParams);
$oSet->OptimizeColumnLoad(array($oSearch->GetClassAlias() => array()));
$oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => [$sObjectImageAttCode]]);
$oSet->SetLimit(MetaModel::GetConfig()->Get('max_autocomplete_results'));
// Note: We have to this manually because of a bug in DBSearch not checking the user prefs. by default.
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
@@ -2540,7 +2540,7 @@ EOF
} else {
// If no image found, fallback on initials
$aMatch['picture_style'] = '';
$aMatch['initials'] = utils::ToAcronym($oObject->Get('friendlyname'));
$aMatch['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObject->Get('friendlyname')));
}
}

View File

@@ -773,12 +773,13 @@ class AjaxRenderController
}
/**
* display list of licenses in "About iTop" popup
* Display list of licenses in "About iTop" popup
* @param \AjaxPage $oPage
*
* @throws \Exception
* @since 3.0.1
*/
private static function DisplayAboutLicenses( AjaxPage $oPage): void
private static function DisplayAboutLicenses(AjaxPage $oPage): void
{
$sCurrEnv = utils::GetCurrentEnvironment();
require_once(APPROOT.'setup/setuputils.class.inc.php');
@@ -804,6 +805,7 @@ JS
$oPage->add('</fieldset>');
$oPage->add("</div>");
}
/**
* Display about iTop for all user non admin
* @param \AjaxPage $oPage
@@ -829,7 +831,7 @@ EOF
$oPage->add('<a href="http://www.combodo.com" title="www.combodo.com" target="_blank" style="background: none;"><img src="../images/logo-combodo.png?t='.utils::GetCacheBusterTimestamp().'"/></a>');
$oPage->add('<div>'.$sVersionString.'</div>');
$oPage->add("</div>");
self::DisplayAboutLicenses( $oPage);
self::DisplayAboutLicenses($oPage);
$oPage->add("</div>");
}
@@ -885,8 +887,6 @@ EOF
}
$aAvailableModules = $oRuntimeEnv->AnalyzeInstallation(MetaModel::GetConfig(), $aSearchDirs);
$aItopSettings = array('cron_max_execution_time', 'timezone');
$aPHPSettings = array('memory_limit', 'max_execution_time', 'upload_max_filesize', 'post_max_size');
$aMySQLSettings = array('max_allowed_packet', 'key_buffer_size', 'query_cache_size');

View File

@@ -648,7 +648,7 @@ HTML
// Open medallion from profile picture or first name letter
$bEntryHasMedallionPicture = (empty($aContactPicturesCache[$iEntryUserId]) === false);
$sEntryMedallionStyle = $bEntryHasMedallionPicture ? ' background-image: url(\''.$aContactPicturesCache[$iEntryUserId].'\');' : '';
$sEntryMedallionContent = $bEntryHasMedallionPicture ? '' : UserRights::GetUserInitials($sEntryUserLogin);
$sEntryMedallionContent = $bEntryHasMedallionPicture ? '' : utils::FormatInitialsForMedallion(UserRights::GetUserInitials($sEntryUserLogin));
// - Entry tooltip
$sEntryMedallionTooltip = utils::HtmlEntities($sEntryUserLogin);
$sEntryMedallionTooltipPlacement = ($iEntryUserId === $iCurrentUserId) ? 'left' : 'right';

View File

@@ -10,7 +10,7 @@
{% if oUIBlock.GetAuthorPictureAbsUrl() is not empty %}
<img class="ibo-activity-entry--author-picture" src="{{ oUIBlock.GetAuthorPictureAbsUrl() }}" alt="{{ oUIBlock.GetAuthorInitials() }}">
{% else %}
<div class="ibo-activity-entry--author-initials">{{ oUIBlock.GetAuthorInitials() }}</div>
<div class="ibo-activity-entry--author-initials">{{ oUIBlock.GetAuthorInitials()|slice(0, 3) }}</div>
{% endif %}
{% endblock %}
</div>

View File

@@ -434,7 +434,7 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase
public function testToAcronym(string $sInput, string $sExceptedAcronym)
{
$sTestedAcronym = utils::ToAcronym($sInput);
$this->assertEquals($sTestedAcronym, $sExceptedAcronym, "Acronym for '$sInput' doesn't match. Got '$sTestedAcronym', expected '$sExceptedAcronym'.");
$this->assertEquals($sExceptedAcronym, $sTestedAcronym, "Acronym for '$sInput' doesn't match. Got '$sTestedAcronym', expected '$sExceptedAcronym'.");
}
/**
@@ -479,6 +479,10 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase
'Jada Pinkett-smith',
'JP',
],
'Several words, cyrillic alphabet' => [
'Денис Александра',
'ДА',
],
];
}
@@ -550,6 +554,40 @@ class UtilsTest extends \Combodo\iTop\Test\UnitTest\ItopTestCase
];
}
/**
* @dataProvider FormatInitialsForMedallionProvider
* @covers utils::FormatInitialsForMedallion
*
* @param string $sInput
* @param string $sExpected
*/
public function testFormatInitialsForMedallion(string $sInput, string $sExpected)
{
$sTested = utils::FormatInitialsForMedallion($sInput);
$this->assertEquals($sExpected, $sTested);
}
/**
* @since 3.0.1
*/
public function FormatInitialsForMedallionProvider()
{
return [
'All letters kept (2)' => [
'AB',
'AB',
],
'All letters kept (3)' => [
'ABC',
'ABC',
],
'Only 3 first letters kept (4)' => [
'ABCD',
'ABC',
],
];
}
/**
* @param string $sExpressionToConvert
* @param int $iExpectedConvertedValue

View File

@@ -36,30 +36,36 @@ class iTopModuleXmlInstallationChecklistTest extends ItopTestCase
public function testAllModuleAreIncludedInInstallationXml()
{
$sInstallationXmlPath = APPROOT.'datamodels/2.x/installation.xml';
if (!is_file($sInstallationXmlPath))
{
if (!is_file($sInstallationXmlPath)) {
$sInstallationXmlPath = APPROOT.'datamodels/1.x/installation.xml';
}
$this->assertTrue(is_file($sInstallationXmlPath), "$sInstallationXmlPath does not exist");
$sInstallationXmlContent = file_get_contents($sInstallationXmlPath);
preg_match_all("|<module>(.*)</module>|", $sInstallationXmlContent, $aMatches);
$aDeclaredModules = [] ;
if (!empty($aMatches))
{
foreach ($aMatches[1] as $sModule)
{
if (!array_key_exists($sModule, $aDeclaredModules))
{
$aDeclaredModules = [];
if (!empty($aMatches)) {
foreach ($aMatches[1] as $sModule) {
if (!array_key_exists($sModule, $aDeclaredModules)) {
$aDeclaredModules[$sModule] = $sModule;
}
}
}
$this->assertArraySubset($this->GetFilteredModulesFromDatamodels(APPROOT.'/datamodels'), $aDeclaredModules, false, "$sInstallationXmlPath does not refer to all provided modules. Refered modules:\n " . var_export($aDeclaredModules, true));
$this->assertArraySubset(
$this->GetFilteredModulesFromDatamodels(APPROOT.'/datamodels'),
$aDeclaredModules,
false,
"{$sInstallationXmlPath} does not list all modules in /datamodels ! List of modules in installation.xml:\n ".var_export($aDeclaredModules, true)
);
$aModulesFromDatamodels = $this->GetAllModules(APPROOT.'/datamodels');
$this->assertArraySubset($aDeclaredModules, $aModulesFromDatamodels, false, "Not all modules are contained in $sInstallationXmlPath. Refered modules:\n " . var_export($aModulesFromDatamodels, true));
$this->assertArraySubset(
$aDeclaredModules,
$aModulesFromDatamodels,
false,
"Not all modules are contained in {$sInstallationXmlPath}. List of modules in /datamodels:\n ".var_export($aModulesFromDatamodels, true)
);
}
public function GetFilteredModulesFromDatamodels($sFolder)