diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index c23ec696a..626f27107 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -2429,7 +2429,7 @@ HTML; $sInputType = self::ENUM_INPUT_TYPE_IMAGE; $aEventsList[] = 'validate'; $aEventsList[] = 'change'; - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/edit_image.js'); + $oPage->LinkScriptFromAppRoot('js/edit_image.js'); $oDocument = $value; // Value is an ormDocument objectm $sDefaultUrl = $oAttDef->Get('default_image'); if (is_object($oDocument) && !$oDocument->IsEmpty()) { @@ -2545,11 +2545,11 @@ HTML; $sHTMLValue .= ''; $sHTMLValue .= '
'.$sReloadSpan.'
'; // No validation span for this one: it does handle its own validation! $sHTMLValue .= "\n"; - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_handler.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/console_form_handler.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_set.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_field.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/subform_field.js'); + $oPage->LinkScriptFromAppRoot('js/form_handler.js'); + $oPage->LinkScriptFromAppRoot('js/console_form_handler.js'); + $oPage->LinkScriptFromAppRoot('js/field_set.js'); + $oPage->LinkScriptFromAppRoot('js/form_field.js'); + $oPage->LinkScriptFromAppRoot('js/subform_field.js'); $oPage->add_ready_script( <<add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js'); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.itop-set-widget.js'); + $oPage->LinkScriptFromAppRoot('js/selectize.min.js'); + $oPage->LinkStylesheetFromAppRoot('css/selectize.default.css'); + $oPage->LinkScriptFromAppRoot('js/jquery.itop-set-widget.js'); $oPage->add_dict_entry('Core:AttributeSet:placeholder'); @@ -3452,7 +3452,7 @@ EOF // Dummy collapsible section created in order to get JS files $oCollapsibleSection = new CollapsibleSection(''); foreach ($oCollapsibleSection->GetJsFilesUrlRecursively(true) as $sJSFile) { - $oPage->add_linked_script($sJSFile); + $oPage->LinkScriptFromURI($sJSFile); } } $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, $aArgs); diff --git a/application/dashboard.class.inc.php b/application/dashboard.class.inc.php index 90b1faeb8..7ad64b7f7 100644 --- a/application/dashboard.class.inc.php +++ b/application/dashboard.class.inc.php @@ -563,8 +563,8 @@ JS } if (!$bEditMode) { - $oPage->add_linked_script('../js/dashlet.js'); - $oPage->add_linked_script('../js/dashboard.js'); + $oPage->LinkScriptFromAppRoot('js/dashlet.js'); + $oPage->LinkScriptFromAppRoot('js/dashboard.js'); } return $oDashboard; @@ -1118,8 +1118,8 @@ JS */ protected function RenderEditionTools(WebPage $oPage, DashboardLayoutUIBlock $oDashboard, $aExtraParams) { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/blueimp-file-upload/js/jquery.iframe-transport.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/blueimp-file-upload/js/jquery.fileupload.js'); + $oPage->LinkScriptFromAppRoot('node_modules/blueimp-file-upload/js/jquery.iframe-transport.js'); + $oPage->LinkScriptFromAppRoot('node_modules/blueimp-file-upload/js/jquery.fileupload.js'); $sId = utils::Sanitize($this->GetId(), '', 'element_identifier'); $sMenuTogglerId = "ibo-dashboard-menu-toggler-{$sId}"; diff --git a/application/datatable.class.inc.php b/application/datatable.class.inc.php index 5fa6e1d73..9de50ee04 100644 --- a/application/datatable.class.inc.php +++ b/application/datatable.class.inc.php @@ -326,6 +326,7 @@ class DataTable $sPagesLinks = implode('', $aPagesToDisplay); $sPagesList = '['.implode(',', array_keys($aPagesToDisplay)).']'; + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); $sSelectionMode = ($iNbPages == 1) ? '' : 'positive'; $sHtml = << - - + + - - + + diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index 873a7b4dc..c729092df 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -570,7 +570,7 @@ class DisplayBlock } else { // render it as an Ajax (asynchronous) call $oHtml->AddCSSClass("loading"); - $oHtml->AddHtml("

".Dict::S('UI:Loading').'

'); + $oHtml->AddHtml("

".Dict::S('UI:Loading').'

'); $oPage->add_script(' $.post("ajax.render.php?style='.$this->m_sStyle.'", { operation: "ajax", filter: "'.$sFilter.'", extra_params: "'.$sExtraParams.'" }, diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 5c5cb6eaf..91aba1b12 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -97,8 +97,8 @@ class LoginWebPage extends NiceWebPage public function SetStyleSheet() { - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/login.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); + $this->LinkStylesheetFromAppRoot('css/login.css'); + $this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); } /** diff --git a/application/template.class.inc.php b/application/template.class.inc.php index 7a07f5a65..4964b0d9f 100644 --- a/application/template.class.inc.php +++ b/application/template.class.inc.php @@ -232,12 +232,13 @@ class DisplayTemplate static public function UnitTest() { require_once(APPROOT.'/application/startup.inc.php'); - + + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); $sTemplate = ' - + SELECT Interface AS i WHERE i.device_id = $id$ @@ -350,7 +351,8 @@ class ObjectDetailsTemplate extends DisplayTemplate if ($iFlags & OPT_ATT_SLAVE) { $iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons); - $sSynchroIcon = " "; + $sAppRooturl = utils::GetAbsoluteUrlAppRoot(); + $sSynchroIcon = " "; $sTip = ''; foreach($aReasons as $aRow) { diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php index dcfb067b9..3eb0131cd 100644 --- a/application/ui.extkeywidget.class.inc.php +++ b/application/ui.extkeywidget.class.inc.php @@ -161,8 +161,8 @@ class UIExtKeyWidget public function DisplaySelect(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, DBObjectset $oAllowedValues, $value, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), &$sInputType = '') { $sTitle = addslashes($sTitle); - $oPage->add_linked_script('../js/extkeywidget.js'); - $oPage->add_linked_script('../js/forms-json-utils.js'); + $oPage->LinkScriptFromAppRoot('js/extkeywidget.js'); + $oPage->LinkScriptFromAppRoot('js/forms-json-utils.js'); $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_MODIFY) && $bAllowTargetCreation); $bExtensions = true; @@ -368,7 +368,7 @@ JS */ public function DisplayRadio(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, DBObjectset $oAllowedValues, $value, $sFieldName, $sDisplayStyle) { - $oPage->add_linked_script('../js/forms-json-utils.js'); + $oPage->LinkScriptFromAppRoot('js/forms-json-utils.js'); $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation); $bExtensions = true; @@ -477,8 +477,8 @@ JS $this->bSearchMode = $bSearchMode; } $sTitle = addslashes($sTitle); - $oPage->add_linked_script('../js/extkeywidget.js'); - $oPage->add_linked_script('../js/forms-json-utils.js'); + $oPage->LinkScriptFromAppRoot('js/extkeywidget.js'); + $oPage->LinkScriptFromAppRoot('js/forms-json-utils.js'); $bCreate = (!$this->bSearchMode) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation); $bExtensions = true; diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php index 03ba6c319..ada67eb26 100644 --- a/core/bulkchange.class.inc.php +++ b/core/bulkchange.class.inc.php @@ -1446,7 +1446,7 @@ EOF <<'); + $('#csv_history_reload').html(''); $.get(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?{$sAppContext}', {operation: 'displayCSVHistory', showall: bShowAll}, function(data) { $('#$sAjaxDivId').html(data); diff --git a/core/datamodel.core.xml b/core/datamodel.core.xml index e28ea03bd..7bdfce0ac 100644 --- a/core/datamodel.core.xml +++ b/core/datamodel.core.xml @@ -397,6 +397,34 @@ +
+ + + 10 + + + 20 + + + 30 + + + 40 + + + 50 + + + 60 + + + 70 + + + 80 + + +
diff --git a/core/ormcaselog.class.inc.php b/core/ormcaselog.class.inc.php index 86a93644e..a76fb3c11 100644 --- a/core/ormcaselog.class.inc.php +++ b/core/ormcaselog.class.inc.php @@ -516,7 +516,7 @@ class ormCaseLog { $sScript = ''; $sHtml .= $sScript; } else { - $oP->add_linked_script($sFileAbsUrl); + $oP->LinkScriptFromURI($sFileAbsUrl); } } } diff --git a/core/ormstopwatch.class.inc.php b/core/ormstopwatch.class.inc.php index a1d808da4..c0d13d71e 100644 --- a/core/ormstopwatch.class.inc.php +++ b/core/ormstopwatch.class.inc.php @@ -217,7 +217,7 @@ class ormStopWatch } else { - $aProperties['Elapsed'] = 'running '; + $aProperties['Elapsed'] = 'running '; } $aProperties['Started'] = $oAttDef->SecondsToDate($this->iStarted); diff --git a/css/backoffice/pages/_notifications.scss b/css/backoffice/pages/_notifications.scss index 56a2f5ee3..a968bcec1 100644 --- a/css/backoffice/pages/_notifications.scss +++ b/css/backoffice/pages/_notifications.scss @@ -21,6 +21,9 @@ $ibo-notifications--view-all--empty--svg--max-width: 30% !default; grid-gap: $ibo-notifications--view-all--container--grid-gap; .ibo-object-summary .ibo-panel--title{ font-size: $ibo-font-size-250; + } + .ibo-object-summary .ibo-panel--toolbar{ + min-width: 102px; } .ibo-object-summary > .ibo-panel--body{ box-shadow: none; @@ -53,7 +56,18 @@ $ibo-notifications--view-all--empty--svg--max-width: 30% !default; .ibo-notifications--view-all--item--unread .ibo-panel--body::before{ background-color: $ibo-notifications--view-all--item--unread--highlight--background-color; } + +.ibo-notifications--view-all--read-action, .ibo-notifications--view-all--unread-action { + margin-left: 0 !important; +} +.ibo-notifications--view-all--item--read .ibo-notifications--view-all--read-action { + display: none; +} +.ibo-notifications--view-all--item--unread .ibo-notifications--view-all--unread-action { + display: none; +} + .ibo-notifications--view-all--empty { @extend %ibo-fully-centered-content; flex-direction: column; diff --git a/datamodels/2.x/itop-attachments/renderers.itop-attachments.php b/datamodels/2.x/itop-attachments/renderers.itop-attachments.php index 0b7079d7a..4407d68a8 100644 --- a/datamodels/2.x/itop-attachments/renderers.itop-attachments.php +++ b/datamodels/2.x/itop-attachments/renderers.itop-attachments.php @@ -185,6 +185,7 @@ abstract class AbstractAttachmentsRenderer { $sClass = $this->sObjClass; $sId = $this->iObjKey; + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); $iMaxUploadInBytes = AttachmentPlugIn::GetMaxUploadSize(); $sMaxUploadLabel = AttachmentPlugIn::GetMaxUpload(); $sFileTooBigLabel = Dict::Format('Attachments:Error:FileTooLarge', $sMaxUploadLabel); @@ -195,15 +196,15 @@ abstract class AbstractAttachmentsRenderer $oAddButton = FileSelectUIBlockFactory::MakeStandard('file', 'file'); $oAddButton->SetShowFilename(false); $this->oPage->AddUiBlock($oAddButton); - $this->oPage->add(' '.$sMaxUploadLabel); + $this->oPage->add(' ' . $sMaxUploadLabel); $this->oPage->add(''); $this->oPage->add('
'); $this->oPage->add(file_get_contents(APPROOT.'images/illustrations/undraw_upload.svg')); $this->oPage->add(Dict::S('UI:Attachments:DropYourFileHint').'
'); - $this->oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/blueimp-file-upload/js/jquery.iframe-transport.js'); - $this->oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/blueimp-file-upload/js/jquery.fileupload.js'); + $this->oPage->LinkScriptFromAppRoot('node_modules/blueimp-file-upload/js/jquery.iframe-transport.js'); + $this->oPage->LinkScriptFromAppRoot('node_modules/blueimp-file-upload/js/jquery.fileupload.js'); $this->oPage->add_ready_script( <<'); }); }); - $('.image-in-use-wrapper').append('
'); + $('.image-in-use-wrapper').append('
'); }, 200 ); JS ); diff --git a/datamodels/2.x/itop-config/config.php b/datamodels/2.x/itop-config/config.php index f8e8c7f8b..8175d8b0e 100644 --- a/datamodels/2.x/itop-config/config.php +++ b/datamodels/2.x/itop-config/config.php @@ -110,10 +110,10 @@ ApplicationMenu::CheckMenuIdEnabled('ConfigEditor'); $oP = new iTopWebPage(Dict::S('config-edit-title')); $oP->set_base(utils::GetAbsoluteUrlAppRoot().'pages/'); $sAceDir = 'node_modules/ace-builds/src-min/'; -$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sAceDir.'ace.js'); -$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sAceDir.'mode-php.js'); -$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sAceDir.'theme-eclipse.js'); -$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sAceDir.'ext-searchbox.js'); +$oP->LinkScriptFromAppRoot($sAceDir.'ace.js'); +$oP->LinkScriptFromAppRoot($sAceDir.'mode-php.js'); +$oP->LinkScriptFromAppRoot($sAceDir.'theme-eclipse.js'); +$oP->LinkScriptFromAppRoot($sAceDir.'ext-searchbox.js'); try { $sOperation = utils::ReadParam('operation', ''); diff --git a/datamodels/2.x/itop-hub-connector/hubconnectorpage.class.inc.php b/datamodels/2.x/itop-hub-connector/hubconnectorpage.class.inc.php index 7377dedfe..257080513 100644 --- a/datamodels/2.x/itop-hub-connector/hubconnectorpage.class.inc.php +++ b/datamodels/2.x/itop-hub-connector/hubconnectorpage.class.inc.php @@ -17,7 +17,7 @@ class HubConnectorPage extends NiceWebPage $sModuleImagesDir = utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/images'; $sUserPrefs = appUserPreferences::GetAsJSON(); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js'); + $this->LinkScriptFromAppRoot('js/utils.js'); $this->add_script(<<add_linked_stylesheet(utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/css/hub.css'); + $oPage->LinkStylesheetFromModule('itop-hub-connector/css/hub.css'); $oPage->add('
$sPagesAAAA $sPagesLinks $sPageSizeCombo  
'); $sBannerUrl = utils::GetAbsoluteUrlModulesRoot().'/itop-hub-connector/images/landing-extension.png'; $oPage->add('
'); @@ -138,7 +138,7 @@ function DoInstall(WebPage $oPage) $sUID = hash('sha256', rand()); file_put_contents(utils::GetDataPath().'hub/compile_authent', $sUID); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/css/hub.css'); + $oPage->LinkStylesheetFromModule('itop-hub-connector/css/hub.css'); $oPage->add(''); $sBannerUrl = utils::GetAbsoluteUrlModulesRoot().'/itop-hub-connector/images/landing-extension.png'; $oPage->add('
'); @@ -214,7 +214,7 @@ function DoInstall(WebPage $oPage) $oPage->add(''); // module-selection-body - $oPage->add_linked_stylesheet('../css/font-awesome/css/all.min.css'); + $oPage->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); $oPage->add('
'); @@ -263,8 +263,8 @@ try { } $oPage = new SetupPage(''); // Title will be set later, depending on $sOperation - $oPage->add_linked_script(utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/js/hub.js'); - $oPage->add_linked_stylesheet('../css/font-combodo/font-combodo.css'); + $oPage->LinkScriptFromModule('itop-hub-connector/js/hub.js'); + $oPage->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css'); $oPage->add_style(<<add_linked_script(utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/js/hub.js'); - $oPage->add_linked_stylesheet('../css/font-combodo/font-combodo.css'); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlModulesRoot().'itop-hub-connector/css/hub.css'); + $oPage->LinkScriptFromModule('itop-hub-connector/js/hub.js'); + $oPage->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css'); + $oPage->LinkStylesheetFromModule('itop-hub-connector/css/hub.css'); $aDataToPost = MakeDataToPost($sTargetRoute); diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index ec3ed20b4..6eb17f8de 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -1620,7 +1620,7 @@ Dict::Add('FR FR', 'French', 'Français', array( 'UI:Newsroom:Preferences' => 'Préférences du centre d\'information', 'UI:Newsroom:ConfigurationLink' => 'Configuration', 'UI:Newsroom:ResetCache' => 'Ràz du cache', - 'UI:Newsroom:ResetCache:Success:Message' => 'Le cache a été réinitialisé avec succès', + 'UI:Newsroom:ResetCache:Success:Message' => 'Le cache de la newsroom a été réinitialisé avec succès', 'UI:Newsroom:DisplayMessagesFor_Provider' => 'Afficher les messages de %1$s', 'UI:Newsroom:DisplayAtMost_X_Messages' => 'Afficher au plus %1$s messages dans le menu %2$s.', )); diff --git a/js/components/newsroom-menu.js b/js/components/newsroom-menu.js index b619300ea..8168e7274 100644 --- a/js/components/newsroom-menu.js +++ b/js/components/newsroom-menu.js @@ -363,7 +363,6 @@ $(function() var sKey = this._makeCacheKey(idx); localStorage.removeItem(sKey); } - CombodoToast.OpenSuccessToast(Dict.S('UI:Newsroom:ResetCache:Success:Message')); }, _makeCacheKey: function(idxProvider) { diff --git a/js/pages/backoffice/itop-newsroom.view-all.js b/js/pages/backoffice/itop-newsroom.view-all.js index 210b4e7cc..4f74b0c9d 100644 --- a/js/pages/backoffice/itop-newsroom.view-all.js +++ b/js/pages/backoffice/itop-newsroom.view-all.js @@ -15,6 +15,8 @@ $('body').on('itop.notification.deleted', '.ibo-notifications--view-all--contain $('.ibo-notifications--view-all--unread-action').attr('disabled', 'disabled'); $('.ibo-notifications--view-all--delete-action').attr('disabled', 'disabled'); } + + $('#ibo-navigation-menu--notifications-menu').newsroom_menu("clearCache"); }); let fReadUnreadDisabled = function() { @@ -28,6 +30,8 @@ let fReadUnreadDisabled = function() { $('.ibo-notifications--view-all--read-action').removeAttr('disabled'); $('.ibo-notifications--view-all--unread-action').removeAttr('disabled'); } + + $('#ibo-navigation-menu--notifications-menu').newsroom_menu("clearCache"); } $('body').on('itop.notification.read itop.notification.unread', '.ibo-notifications--view-all--container', fReadUnreadDisabled); diff --git a/pages/UI.php b/pages/UI.php index aef1f3cd0..5eeb47bb4 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -350,7 +350,7 @@ try case 'stimulus': // Form displayed when applying a stimulus (state change) case 'apply_stimulus': // Form displayed when applying a stimulus (state change) foreach (ObjectController::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { - $oP->add_linked_script("../$sJsFileRelPath"); + $oP->LinkScriptFromAppRoot($sJsFileRelPath); } break; } @@ -651,7 +651,7 @@ try $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); $oP->add("
\n"); $oP->add("
\n"); - $oP->add(' '.Dict::Format('UI:Search:Ongoing', utils::EscapeHtml($sFullText)).''); + $oP->add(' '.Dict::Format('UI:Search:Ongoing', utils::EscapeHtml($sFullText)).''); $oP->add("
\n"); $oP->add("
\n"); $oP->add("
 
\n"); @@ -703,7 +703,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 diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index 4c8dc4585..c1b610cd3 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -33,11 +33,11 @@ ApplicationMenu::CheckMenuIdEnabled('UniversalSearchMenu'); $oAppContext = new ApplicationContext(); $oP = new iTopWebPage(Dict::S('UI:UniversalSearchTitle')); -$oP->add_linked_script("../js/forms-json-utils.js"); -$oP->add_linked_script("../js/wizardhelper.js"); -$oP->add_linked_script("../js/wizard.utils.js"); -$oP->add_linked_script("../js/extkeywidget.js"); -$oP->add_linked_script("../js/jquery.blockUI.js"); +$oP->LinkScriptFromAppRoot("js/forms-json-utils.js"); +$oP->LinkScriptFromAppRoot("js/wizardhelper.js"); +$oP->LinkScriptFromAppRoot("js/wizard.utils.js"); +$oP->LinkScriptFromAppRoot("js/extkeywidget.js"); +$oP->LinkScriptFromAppRoot("js/jquery.blockUI.js"); // From now on the context is limited to the the selected organization ?? diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 5612629af..dfcb9bf00 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1375,7 +1375,7 @@ JS <<'); + $('.search-class-$sClassName h2').append(' '); var oParams = {operation: 'full_text_search_enlarge', class: '$sClassName', text: '$sFullTextJS'}; $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', oParams, function(data) { $('.search-class-$sClassName').html(data); @@ -1532,6 +1532,7 @@ EOF case 'xlsx_export_dialog': DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); $oPage->SetContentType('text/html'); $oPage->add( << div.closed { padding-left: 16px; - background: url(../images/plus.gif) 0 2px no-repeat; + background: url({$sAppRootUrl}images/plus.gif) 0 2px no-repeat; } .statistics .closed .stats-data { @@ -1594,7 +1595,7 @@ EOF ); $sJSLabels = json_encode($aLabels); $sFilter = addslashes($sFilter); - $sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'); + $sJSPageUrl = addslashes($sAppRootUrl.'pages/ajax.render.php'); $oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});"); break; @@ -2206,9 +2207,10 @@ EOF case 'cke_browse': $oPage = new NiceWebPage(Dict::S('UI:BrowseInlineImages')); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/magnific-popup/dist/magnific-popup.css'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/magnific-popup/dist/jquery.magnific-popup.min.js'); - $sImgUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL; + $oPage->LinkStylesheetFromAppRoot('node_modules/magnific-popup/dist/magnific-popup.css'); + $oPage->LinkScriptFromAppRoot('node_modules/magnific-popup/dist/jquery.magnific-popup.min.js'); + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); + $sImgUrl = $sAppRootUrl.INLINEIMAGE_DOWNLOAD_URL; /** @noinspection SuspiciousAssignmentsInspection cke_upload_and_browse and cke_browse are chained */ $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); @@ -2236,7 +2238,7 @@ EOF } } - $sPostUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum; + $sPostUrl = $sAppRootUrl.'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum; $oPage->add_style( <<add_ready_script( <<'); + $('#upload_status').html(''); $('#upload_form').submit(); $(this).prop('disabled', true); }); diff --git a/pages/audit.php b/pages/audit.php index 5aed36d67..1f68083eb 100644 --- a/pages/audit.php +++ b/pages/audit.php @@ -281,7 +281,7 @@ try $oAllCategoriesDashlet ->AddCSSClasses(['ibo-dashlet--is-inline', 'ibo-dashlet-badge']) ->AddSubBlock(DashletFactory::MakeForDashletBadge( - '../images/icons/icons8-audit.svg', + utils::GetAbsoluteUrlAppRoot().'images/icons/icons8-audit.svg', utils::GetAbsoluteUrlAppRoot()."pages/audit.php?operation=audit", $iCategoryCount, Dict::S('UI:Audit:Interactive:Selection:BadgeAll') @@ -304,7 +304,7 @@ try /** @var AuditDomain $oAuditDomain */ while($oAuditDomain = $oDomainSet->Fetch()) { $sDomainUrl = utils::GetAbsoluteUrlAppRoot()."pages/audit.php?operation=audit&domain=".$oAuditDomain->GetKey(); - $sIconUrl = '../images/icons/icons8-puzzle.svg'; + $sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/icons/icons8-puzzle.svg'; /** @var \ormDocument $oImage */ $oImage = $oAuditDomain->Get('icon'); if (!$oImage->IsEmpty()) { @@ -357,9 +357,9 @@ try $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage($sTitle)); $oP->AddUiBlock(new Text($sSubTitle)); - $oTotalBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-audit.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsAudited')); - $oErrorBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-delete.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsInError')); - $oWorkingBlock = DashletFactory::MakeForDashletBadge('../images/icons/icons8-checkmark.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsValidated')); + $oTotalBlock = DashletFactory::MakeForDashletBadge(utils::GetAbsoluteUrlAppRoot().'images/icons/icons8-audit.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsAudited')); + $oErrorBlock = DashletFactory::MakeForDashletBadge(utils::GetAbsoluteUrlAppRoot().'images/icons/icons8-delete.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsInError')); + $oWorkingBlock = DashletFactory::MakeForDashletBadge(utils::GetAbsoluteUrlAppRoot().'images/icons/icons8-checkmark.svg', '#', 0, Dict::S('UI:Audit:Dashboard:ObjectsValidated')); $aCSSClasses = ['ibo-dashlet--is-inline', 'ibo-dashlet-badge']; @@ -436,7 +436,7 @@ try foreach ($aErrors as $aErrorRow) { $aObjectsWithErrors[$aErrorRow['id']] = true; } - $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCount GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">"; + $aRow['nb_errors'] = ($iErrorsCount == 0) ? '0' : "GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">$iErrorsCount GetKey()."&rule=".$oAuditRule->GetKey()."&".$oAppContext->GetForLink()."\">"; $aRow['percent_ok'] = sprintf('%.2f', 100.0 * (($iCount - $iErrorsCount) / $iCount)); $aRow['class'] = $oAuditCategory->GetReportColor($iCount, $iErrorsCount); } diff --git a/pages/csvimport.php b/pages/csvimport.php index ff790f07c..d064b7291 100644 --- a/pages/csvimport.php +++ b/pages/csvimport.php @@ -400,6 +400,7 @@ try { $iUnchanged = 0; $aTableData = []; + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); foreach ($aRes as $iLine => $aResRow) { $aTableRow = []; @@ -414,7 +415,7 @@ try { $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); - $sStatus = ''; + $sStatus = ''; $sCSSRowClass = 'ibo-csv-import--row-unchanged'; break; @@ -423,7 +424,7 @@ try { $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); - $sStatus = ''; + $sStatus = ''; $sCSSRowClass = 'ibo-csv-import--row-modified'; break; @@ -432,7 +433,7 @@ try { $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); - $sStatus = ''; + $sStatus = ''; $sCSSRowClass = 'ibo-csv-import--row-modified'; if ($bSimulate) { $sMessage = Dict::S('UI:CSVReport-Object-MissingToUpdate'); @@ -443,7 +444,7 @@ try { case 'RowStatus_NewObj': $iCreated++; - $sStatus = ''; + $sStatus = ''; $sCSSRowClass = 'ibo-csv-import--row-added'; if ($bSimulate) { $sMessage = Dict::S('UI:CSVReport-Object-ToCreate'); @@ -585,21 +586,21 @@ HTML; $oMulticolumn->AddCSSClass('ml-1'); $oForm->AddSubBlock($oMulticolumn); - $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['unchanged'], $iUnchanged), '', "1", "show_unchanged", "checkbox"); + $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['unchanged'], $iUnchanged), '', "1", "show_unchanged", "checkbox"); $oCheckBoxUnchanged->GetInput()->SetIsChecked(true); $oCheckBoxUnchanged->SetBeforeInput(false); $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox'); $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged)); $oPage->add_ready_script("$('#show_unchanged').on('click', function(){ToggleRows('ibo-csv-import--row-unchanged')})"); - $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['modified'], $iModified), '', "1", "show_modified", "checkbox"); + $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['modified'], $iModified), '', "1", "show_modified", "checkbox"); $oCheckBoxUnchanged->GetInput()->SetIsChecked(true); $oCheckBoxUnchanged->SetBeforeInput(false); $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox'); $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged)); $oPage->add_ready_script("$('#show_modified').on('click', function(){ToggleRows('ibo-csv-import--row-modified')})"); - $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['added'], $iCreated), '', "1", "show_created", "checkbox"); + $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['added'], $iCreated), '', "1", "show_created", "checkbox"); $oCheckBoxUnchanged->GetInput()->SetIsChecked(true); $oCheckBoxUnchanged->SetBeforeInput(false); $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox'); diff --git a/pages/preferences.php b/pages/preferences.php index 0eeb4a8af..597f95415 100644 --- a/pages/preferences.php +++ b/pages/preferences.php @@ -133,7 +133,7 @@ function ValidateOtherSettings() } else { - $('#v_default_page_size').html(''); + $('#v_default_page_size').html(''); $('#ibo-misc-settings-submit').prop('disabled', true); return false; } @@ -315,7 +315,11 @@ JS // - Reset button $oNewsroomResetCacheButton = ButtonUIBlockFactory::MakeForAlternativeDestructiveAction(Dict::S('UI:Newsroom:ResetCache')); - $oNewsroomResetCacheButton->SetOnClickJsCode("$('#ibo-navigation-menu--notifications-menu').newsroom_menu('clearCache')"); + $oNewsroomResetCacheButton->SetOnClickJsCode(<<AddSubBlock($oNewsroomResetCacheButton); // - Cancel button $oNewsroomCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel')); @@ -417,7 +421,7 @@ JS $oUserPicturePlaceHolderBlock = new Panel(Dict::S('UI:Preferences:ChooseAPlaceholder'), array(), 'grey', 'ibo-user-picture-placeholder'); - $sUserPicturesFolder = '../images/user-pictures/'; + $sUserPicturesFolder = utils::GetAbsoluteUrlAppRoot() . 'images/user-pictures/'; $sUserDefaultPicture = appUserPreferences::GetPref('user_picture_placeholder', 'default-placeholder.png'); $sUserPicturePlaceHolderHtml = ''; $sUserPicturePlaceHolderHtml .= '

'.Dict::S('UI:Preferences:ChooseAPlaceholder+').'

'; @@ -916,7 +920,12 @@ try { } if ($bProvidersModified) { - $oPage->add_ready_script('$(".itop-newsroom_menu").newsroom_menu("clearCache");'); + $oPage->add_ready_script( + <<add_linked_script("../js/forms-json-utils.js"); - $oP->add_linked_script("../js/wizardhelper.js"); - $oP->add_linked_script("../js/wizard.utils.js"); - $oP->add_linked_script("../js/extkeywidget.js"); - $oP->add_linked_script("../js/jquery.blockUI.js"); + $oP->LinkScriptFromAppRoot("js/forms-json-utils.js"); + $oP->LinkScriptFromAppRoot("js/wizardhelper.js"); + $oP->LinkScriptFromAppRoot("js/wizard.utils.js"); + $oP->LinkScriptFromAppRoot("js/extkeywidget.js"); + $oP->LinkScriptFromAppRoot("js/jquery.blockUI.js"); $sBaseClass = 'TagSetFieldData'; $sClass = utils::ReadParam('class', '', false, 'class'); diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index e856d5722..1a145098a 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -39,14 +39,14 @@ class SetupPage extends NiceWebPage public function __construct($sTitle) { parent::__construct($sTitle); - $this->add_linked_script("../js/jquery.blockUI.js"); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.js'); - $this->add_linked_script("../setup/setup.js"); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-combodo/font-combodo.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/animations/shift-away-subtle.css'); + $this->LinkScriptFromAppRoot("js/jquery.blockUI.js"); + $this->LinkScriptFromAppRoot('node_modules/@popperjs/core/dist/umd/popper.js'); + $this->LinkScriptFromAppRoot('node_modules/tippy.js/dist/tippy-bundle.umd.js'); + $this->LinkScriptFromAppRoot("setup/setup.js"); + $this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $this->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css'); + $this->LinkStylesheetFromAppRoot('node_modules/tippy.js/dist/tippy.css'); + $this->LinkStylesheetFromAppRoot('node_modules/tippy.js/animations/shift-away-subtle.css'); $this->LoadTheme(); $this->add_saas("css/setup.scss"); diff --git a/setup/wizardcontroller.class.inc.php b/setup/wizardcontroller.class.inc.php index 1f0846f34..dff6c6e35 100644 --- a/setup/wizardcontroller.class.inc.php +++ b/setup/wizardcontroller.class.inc.php @@ -217,7 +217,7 @@ HTML; } } } - $oPage->add_linked_script('../setup/setup.js'); + $oPage->LinkScriptFromAppRoot('setup/setup.js'); $oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n"); $oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n"); $oPage->add('
'); diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index 25baef6f3..c6401ca7c 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -2307,7 +2307,7 @@ class WizStepSummary extends WizardStep $oPage->add('
Progress of the installation'); $oPage->add('
'); - $oPage->add_linked_script('../setup/jquery.progression.js'); + $oPage->LinkScriptFromAppRoot('setup/jquery.progression.js'); $oPage->add('

Ready to start...

0%
'); $oPage->add('
'); // progress_content $oPage->add('
'); diff --git a/sources/Application/Helper/FormHelper.php b/sources/Application/Helper/FormHelper.php index c4425360d..02f40266e 100644 --- a/sources/Application/Helper/FormHelper.php +++ b/sources/Application/Helper/FormHelper.php @@ -55,7 +55,6 @@ class FormHelper */ public static function DisableAttributeBlobInputs(string $sClassName, array &$aExtraParams): void { - // Initialize extra params array if (!array_key_exists('fieldsFlags', $aExtraParams)) { $aExtraParams['fieldsFlags'] = []; @@ -64,13 +63,13 @@ class FormHelper $aExtraParams['fieldsComments'] = []; } - // Iterate throw class attributes... + // Iterate through class attributes... + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); foreach (MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef) { - // Set attribute blobs in read only if ($oAttDef instanceof AttributeBlob) { $aExtraParams['fieldsFlags'][$sAttCode] = OPT_ATT_READONLY; - $aExtraParams['fieldsComments'][$sAttCode] = ' '; + $aExtraParams['fieldsComments'][$sAttCode] = ' '; } } } diff --git a/sources/Application/Helper/WebResourcesHelper.php b/sources/Application/Helper/WebResourcesHelper.php index f0228a39f..299a97a5a 100644 --- a/sources/Application/Helper/WebResourcesHelper.php +++ b/sources/Application/Helper/WebResourcesHelper.php @@ -62,7 +62,7 @@ class WebResourcesHelper //when ckeditor is loaded in ajax, CKEDITOR_BASEPATH is not well defined (this constant is used to load additional js) $oPage->add_script("if (! window.CKEDITOR_BASEPATH) { var CKEDITOR_BASEPATH = '".utils::GetAbsoluteUrlAppRoot()."js/ckeditor/';}"); foreach (static::GetJSFilesRelPathsForCKEditor() as $sFile) { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sFile); + $oPage->LinkScriptFromAppRoot($sFile); } } @@ -93,11 +93,11 @@ class WebResourcesHelper public static function EnableC3JSToWebPage(WebPage &$oPage): void { foreach (static::GetCSSFilesRelPathsForC3JS() as $sFile) { - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().$sFile); + $oPage->LinkStylesheetFromAppRoot($sFile); } foreach (static::GetJSFilesRelPathsForC3JS() as $sFile) { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sFile); + $oPage->LinkScriptFromAppRoot($sFile); } } @@ -135,13 +135,13 @@ class WebResourcesHelper */ public static function EnableSimpleGraphInWebPage(WebPage &$oPage): void { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/raphael-min.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/fraphael.js'); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/jquery-contextmenu/src/jquery.contextMenu.css'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/jquery-contextmenu/src/jquery.contextMenu.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.positionBy.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.popupmenu.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.mousewheel.js'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/simple_graph.js'); + $oPage->LinkScriptFromAppRoot('js/raphael-min.js'); + $oPage->LinkScriptFromAppRoot('js/fraphael.js'); + $oPage->LinkStylesheetFromAppRoot('node_modules/jquery-contextmenu/src/jquery.contextMenu.css'); + $oPage->LinkScriptFromAppRoot('node_modules/jquery-contextmenu/src/jquery.contextMenu.js'); + $oPage->LinkScriptFromAppRoot('js/jquery.positionBy.js'); + $oPage->LinkScriptFromAppRoot('js/jquery.popupmenu.js'); + $oPage->LinkScriptFromAppRoot('js/jquery.mousewheel.js'); + $oPage->LinkScriptFromAppRoot('js/simple_graph.js'); } } \ No newline at end of file diff --git a/sources/Application/WebPage/ErrorPage.php b/sources/Application/WebPage/ErrorPage.php index a3d978bab..0df171dd5 100644 --- a/sources/Application/WebPage/ErrorPage.php +++ b/sources/Application/WebPage/ErrorPage.php @@ -26,10 +26,10 @@ class ErrorPage extends NiceWebPage { $oKpi = new ExecutionKPI(); parent::__construct($sTitle); - $this->add_linked_script("../js/jquery.blockUI.js"); - $this->add_linked_script("../setup/setup.js"); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-combodo/font-combodo.css'); + $this->LinkScriptFromAppRoot("js/jquery.blockUI.js"); + $this->LinkScriptFromAppRoot("setup/setup.js"); + $this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $this->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css'); $this->add_saas("css/setup.scss"); $oKpi->ComputeStats(get_class($this).' creation', 'ErrorPage'); } @@ -58,7 +58,7 @@ class ErrorPage extends NiceWebPage if(utils::IsEasterEggAllowed()) { $this->add('
'.Dict::S('UI:ErrorPage:UnstableVersion').'
'); - $this->add(''); + $this->add(''); $this->add('
'.nl2br(Dict::S('UI:ErrorPage:KittyDisclaimer')).'
'); } $this->log_error($sText); diff --git a/sources/Application/WebPage/NiceWebPage.php b/sources/Application/WebPage/NiceWebPage.php index b66ab61ba..a5eba310c 100644 --- a/sources/Application/WebPage/NiceWebPage.php +++ b/sources/Application/WebPage/NiceWebPage.php @@ -152,20 +152,20 @@ JS parent::InitializeLinkedScripts(); // Used throughout the app. - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/jquery/dist/jquery.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.blockUI.js'); + $this->LinkScriptFromAppRoot('node_modules/jquery/dist/jquery.min.js'); + $this->LinkScriptFromAppRoot('js/jquery.blockUI.js'); if (utils::IsDevelopmentEnvironment()) // Needed since many other plugins still rely on oldies like $.browser { - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.dev-params.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.dev.js'); + $this->LinkScriptFromAppRoot('js/jquery-migrate.dev-params.js'); + $this->LinkScriptFromAppRoot('js/jquery-migrate.dev.js'); } else { - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-migrate.prod.min.js'); + $this->LinkScriptFromAppRoot('js/jquery-migrate.prod.min.js'); } - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/jquery-ui-dist/jquery-ui.min.js'); + $this->LinkScriptFromAppRoot('node_modules/jquery-ui-dist/jquery-ui.min.js'); // Used throughout the app. - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/utils.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/latinise/latinise.min.js'); + $this->LinkScriptFromAppRoot('js/utils.js'); + $this->LinkScriptFromAppRoot('js/latinise/latinise.min.js'); } /** @@ -250,7 +250,7 @@ JS // TODO 3.0.0: Remove light-grey when development of Full Moon is done. // TODO 3.0.0: Reuse theming mechanism for Full Moon $sCssThemeUrl = ThemeHandler::GetCurrentThemeUrl(); - $this->add_linked_stylesheet($sCssThemeUrl); + $this->LinkStylesheetFromURI($sCssThemeUrl); } protected function GetReadyScriptsStartedTrigger(): ?string diff --git a/sources/Application/WebPage/UnauthenticatedWebPage.php b/sources/Application/WebPage/UnauthenticatedWebPage.php index d81b0df7a..693ea8953 100644 --- a/sources/Application/WebPage/UnauthenticatedWebPage.php +++ b/sources/Application/WebPage/UnauthenticatedWebPage.php @@ -77,18 +77,17 @@ class UnauthenticatedWebPage extends NiceWebPage $this->SetContentType('text/html'); // - bootstrap - $this->add_linked_script(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap/js/bootstrap.min.js'); + $this->LinkScriptFromURI(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap/js/bootstrap.min.js'); // Note: Since 2.6.0 moment was moved from portal to iTop core - $sMomentURL = utils::GetAbsoluteUrlAppRoot().'/js/moment-with-locales.min.js'; - $this->add_linked_script($sMomentURL); + $this->LinkScriptFromAppRoot('js/moment-with-locales.min.js'); - $this->add_linked_script(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js'); + $this->LinkScriptFromURI(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js'); // CSS files - $this->add_linked_stylesheet(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap/css/bootstrap.min.css'); + $this->LinkStylesheetFromURI(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap/css/bootstrap.min.css'); $this->add_saas(UAWP_PORTAL_PUBLIC_FOLDER_RELATIVE_PATH . 'css/bootstrap-theme-combodo.scss'); - $this->add_linked_stylesheet(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css'); + $this->LinkStylesheetFromURI(UAWP_PORTAL_PUBLIC_FOLDER_ABSOLUTE_URL . 'lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css'); // Default theme $this->add_saas('css/unauthenticated.scss'); @@ -279,13 +278,13 @@ class UnauthenticatedWebPage extends NiceWebPage */ protected function LoadTheme() { - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); + $this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); // Default theme $this->add_saas('css/unauthenticated.scss'); // Custom theme to allow admin to override the default one. if(!empty($this->sCustomThemeUrl)) { - $this->add_linked_stylesheet($this->sCustomThemeUrl); + $this->LinkStylesheetFromURI($this->sCustomThemeUrl); } } diff --git a/sources/Application/WebPage/WebPage.php b/sources/Application/WebPage/WebPage.php index 263a4d902..b0c576f47 100644 --- a/sources/Application/WebPage/WebPage.php +++ b/sources/Application/WebPage/WebPage.php @@ -25,10 +25,13 @@ use DeprecatedCallsLog; use Dict; use ExecutionKPI; use IssueLog; +use LogChannels; use MetaModel; use Symfony\Component\HttpFoundation\Response; use UserRights; use utils; +use const APPROOT; +use const MODULESROOT; /** @@ -73,6 +76,17 @@ class WebPage implements Page */ public const ENUM_SESSION_MESSAGE_SEVERITY_ERROR = 'error'; + /** + * @var string + * @since 3.2.0 + */ + protected const ENUM_RESOURCE_TYPE_JS = "js"; + /** + * @var string + * @since 3.2.0 + */ + protected const ENUM_RESOURCE_TYPE_CSS = "css"; + /** * @var string * @since 3.0.0 @@ -512,6 +526,109 @@ class WebPage implements Page return $oBlock; } + /** + * Internal helper to link a resource from the iTop package (e.g. ` $sFileRelPath, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + $sAppRootUrl = utils::GetAbsoluteUrlAppRoot(); + + // Ensure app root url ends with a slash as it is not guaranteed by the API + if (strcmp(substr($sFileRelPath, -1), '/') !== 0) { + $sAppRootUrl .= '/'; + } + + $this->LinkResourceFromURI($sAppRootUrl . $sFileRelPath, $sResourceType); + } + + /** + * Internal helper to link a resource from any module + * + * @param string $sFileRelPath Rel. path from current environment (e.g. `/env-production/`) of the resource to link (e.g. `some-module/asset/css/some-file.css`) + * @param string $sResourceType {@see static::ENUM_RESOURCE_TYPE_JS}, {@see static::ENUM_RESOURCE_TYPE_CSS} + * + * @return void + * @throws \Exception + * @internal + * @since 3.2.0 N°7315 + */ + private function LinkResourceFromModule(string $sFileRelPath, string $sResourceType): void + { + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sFileRelPath))) { + return; + } + + if (false === utils::RealPath(MODULESROOT . $sFileRelPath, MODULESROOT)) { + IssueLog::Warning("Linked resource added to page with a path from outside current env. directory, it will be ignored.", LogChannels::CONSOLE, [ + "linked_resource_url" => $sFileRelPath, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + $this->LinkResourceFromURI(utils::GetAbsoluteUrlModulesRoot() . $sFileRelPath, $sResourceType); + } + + /** + * Internal helper to link a resource from any URI, may it be on iTop server or an external one + * + * @param string $sFileAbsURI Abs. URI of the resource to link (e.g. `https://external.server.com/some-file.css`) + * @param string $sResourceType {@see static::ENUM_RESOURCE_TYPE_JS}, {@see static::ENUM_RESOURCE_TYPE_CSS} + * + * @return void + * @throws \Exception + * @internal + * @since 3.2.0 N°7315 + */ + private function LinkResourceFromURI(string $sFileAbsURI, string $sResourceType): void + { + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sFileAbsURI))) { + return; + } + + // Check if URI is absolute ("://" do allow any protocol), otherwise ignore the file + if (false === stripos($sFileAbsURI, "://")) { + IssueLog::Warning("Linked resource added to page with a non absolute URI, it will be ignored.", LogChannels::CONSOLE, [ + "linked_resource_url" => $sFileAbsURI, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + return; + } + + switch ($sResourceType) { + case static::ENUM_RESOURCE_TYPE_JS: + $this->a_linked_scripts[$sFileAbsURI] = $sFileAbsURI; + break; + + case static::ENUM_RESOURCE_TYPE_CSS: + $this->a_linked_stylesheets[$sFileAbsURI] = $sFileAbsURI; + break; + } + } + /** * Empty all base JS in the page's header * @@ -735,15 +852,77 @@ class WebPage implements Page * Add a script (as an include, i.e. link) to the header of the page.
* Handles duplicates : calling twice with the same script will add the script only once * - * @uses WebPage::$a_linked_scripts - * @param string $s_linked_script + * @param string $sLinkedScriptAbsUrl + * * @return void + * @uses WebPage::$a_linked_scripts + * @since 3.2.0 N°6935 $sLinkedScriptAbsUrl MUST be an absolute URL + * @deprecated 3.2.0 N°7315 Use {@see static::LinkScriptFromXXX()} instead */ - public function add_linked_script($s_linked_script) + public function add_linked_script($sLinkedScriptAbsUrl) { - if (!empty(trim($s_linked_script))) { - $this->a_linked_scripts[$s_linked_script] = $s_linked_script; + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); + + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sLinkedScriptAbsUrl))) { + return; } + + // Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior + if (false === stripos($sLinkedScriptAbsUrl, "://")) { + IssueLog::Warning("Linked script added to page via deprecated API with a non absolute URL, it may lead to it not being loaded and causing javascript errors.", null, [ + "linked_script_url" => $sLinkedScriptAbsUrl, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + } + + $this->a_linked_scripts[$sLinkedScriptAbsUrl] = $sLinkedScriptAbsUrl; + } + + /** + * Use to link JS files from the iTop package (e.g. `/js/*`) + * + * @param string $sFileRelPath Rel. path from iTop app. root of the JS file to link (e.g. `js/utils.js`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromAppRoot(string $sFileRelPath): void + { + $this->LinkResourceFromAppRoot($sFileRelPath, static::ENUM_RESOURCE_TYPE_JS); + } + + /** + * Use to link JS files from any module + * + * @param string $sFileRelPath Rel. path from current environment (e.g. `/env-production/`) of the JS file to link (e.g. `some-module/asset/js/some-file.js`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromModule(string $sFileRelPath): void + { + $this->LinkResourceFromModule($sFileRelPath, static::ENUM_RESOURCE_TYPE_JS); + } + + /** + * Use to link JS files from any URI, typically an external server + * + * @param string $sFileAbsURI Abs. URI of the JS file to link (e.g. `https://external.server.com/some-file.js`) + * Note: Any non-absolute URI will be ignored. + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkScriptFromURI(string $sFileAbsURI): void + { + $this->LinkResourceFromURI($sFileAbsURI, static::ENUM_RESOURCE_TYPE_JS); } /** @@ -925,13 +1104,76 @@ JS; * Add a CSS stylesheet (as an include, i.e. link) to the header of the page * Handles duplicates since 3.0.0 : calling twig with the same stylesheet will add the stylesheet only once * - * @param string $s_linked_stylesheet - * @param string $s_condition + * @param string $sLinkedStylesheet + * @param string $sCondition * @return void + * @since 3.2.0 N°6935 $sLinkedStylesheet MUST be an absolute URL + * @deprecated 3.2.0 N°7315 Use {@see static::LinkStylesheetFromXXX()} instead */ - public function add_linked_stylesheet($s_linked_stylesheet, $s_condition = "") + public function add_linked_stylesheet($sLinkedStylesheet, $sCondition = "") { - $this->a_linked_stylesheets[$s_linked_stylesheet] = array('link' => $s_linked_stylesheet, 'condition' => $s_condition); + DeprecatedCallsLog::NotifyDeprecatedPhpMethod(); + + // Ensure there is actually a URI + if (utils::IsNullOrEmptyString(trim($sLinkedStylesheet))) { + return; + } + + // Check if URI is absolute ("://" do allow any protocol), otherwise warn that it's a deprecated behavior + if (false === stripos($sLinkedStylesheet, "://")) { + IssueLog::Warning("Linked stylesheet added to page via deprecated API with a non absolute URL, it may lead to it not being loaded and causing visual glitches.", null, [ + "linked_stylesheet_url" => $sLinkedStylesheet, + "request_uri" => $_SERVER['REQUEST_URI'] ?? '' /* CLI */, + ]); + } + + $this->a_linked_stylesheets[$sLinkedStylesheet] = array('link' => $sLinkedStylesheet, 'condition' => $sCondition); + } + + /** + * Use to link CSS files from the iTop package (e.g. `/css/*`) + * + * @param string $sFileRelPath Rel. path from iTop app. root of the CSS file to link (e.g. `css/login.css`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkStylesheetFromAppRoot(string $sFileRelPath): void + { + $this->LinkResourceFromAppRoot($sFileRelPath, static::ENUM_RESOURCE_TYPE_CSS); + } + + /** + * Use to link CSS files from any module + * + * @param string $sFileRelPath Rel. path from current environment (e.g. `/env-production/`) of the CSS file to link (e.g. `some-module/asset/css/some-file.css`) + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkStylesheetFromModule(string $sFileRelPath): void + { + $this->LinkResourceFromModule($sFileRelPath, static::ENUM_RESOURCE_TYPE_CSS); + } + + /** + * Use to link CSS files from any URI, typically an external server + * + * @param string $sFileAbsURI Abs. URI of the CSS file to link (e.g. `https://external.server.com/some-file.css`) + * Note: Any non-absolute URI will be ignored. + * + * @return void + * @throws \Exception + * @since 3.2.0 N°7315 + * @api + */ + public function LinkStylesheetFromURI(string $sFileAbsURI): void + { + $this->LinkResourceFromURI($sFileAbsURI, static::ENUM_RESOURCE_TYPE_CSS); } /** @@ -1001,7 +1243,7 @@ JS; $sRootUrl = '../'; } $sCSSUrl = $sRootUrl.$sCssRelPath; - $this->add_linked_stylesheet($sCSSUrl); + $this->LinkStylesheetFromURI($sCSSUrl); } /** diff --git a/sources/Application/WebPage/iTopWebPage.php b/sources/Application/WebPage/iTopWebPage.php index d37aa108e..74eac0b0f 100644 --- a/sources/Application/WebPage/iTopWebPage.php +++ b/sources/Application/WebPage/iTopWebPage.php @@ -172,49 +172,49 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage parent::InitializeLinkedScripts(); // Used by forms - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/leave_handler.js'); + $this->LinkScriptFromAppRoot('js/leave_handler.js'); // Used by external keys, DM viewer - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.treeview.min.js'); + $this->LinkScriptFromAppRoot('js/jquery.treeview.min.js'); // Used by advanced search, date(time) attributes. Coupled to the PrepareWidgets() JS function. - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon-i18n.min.js'); + $this->LinkScriptFromAppRoot('js/jquery-ui-timepicker-addon.min.js'); + $this->LinkScriptFromAppRoot('js/jquery-ui-timepicker-addon-i18n.min.js'); // Used by external keys and other drop down lists - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/selectize-plugin-a11y/selectize-plugin-a11y.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.multiselect.js'); + $this->LinkScriptFromAppRoot('js/selectize.min.js'); + $this->LinkScriptFromAppRoot('node_modules/selectize-plugin-a11y/selectize-plugin-a11y.js'); + $this->LinkScriptFromAppRoot('js/jquery.multiselect.js'); // Used by inline image, CKEditor and other places - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/magnific-popup/dist/jquery.magnific-popup.min.js'); + $this->LinkScriptFromAppRoot('node_modules/magnific-popup/dist/jquery.magnific-popup.min.js'); // Used by date(time) attibutes, activity panel, ... - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/moment-with-locales.min.js'); + $this->LinkScriptFromAppRoot('js/moment-with-locales.min.js'); // Used by the newsroom - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/showdown/dist/showdown.min.js'); + $this->LinkScriptFromAppRoot('node_modules/showdown/dist/showdown.min.js'); // Tooltips - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.min.js'); + $this->LinkScriptFromAppRoot('node_modules/@popperjs/core/dist/umd/popper.min.js'); + $this->LinkScriptFromAppRoot('node_modules/tippy.js/dist/tippy-bundle.umd.min.js'); // Toasts - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/toastify-js/src/toastify.js'); + $this->LinkScriptFromAppRoot('node_modules/toastify-js/src/toastify.js'); // Keyboard shortcuts - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/mousetrap/mousetrap.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/mousetrap/plugins/record/mousetrap-record.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice/keyboard-shortcuts.js'); + $this->LinkScriptFromAppRoot('node_modules/mousetrap/mousetrap.min.js'); + $this->LinkScriptFromAppRoot('node_modules/mousetrap/plugins/record/mousetrap-record.min.js'); + $this->LinkScriptFromAppRoot('js/pages/backoffice/keyboard-shortcuts.js'); // Used throughout the app. - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice/toolbox.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice/on-ready.js'); + $this->LinkScriptFromAppRoot('js/pages/backoffice/toolbox.js'); + $this->LinkScriptFromAppRoot('js/pages/backoffice/on-ready.js'); // Used by dashboard editor - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/property_field.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/icon_select.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ajaxfileupload.js'); + $this->LinkScriptFromAppRoot('js/property_field.js'); + $this->LinkScriptFromAppRoot('js/icon_select.js'); + $this->LinkScriptFromAppRoot('js/ajaxfileupload.js'); } /** @@ -257,24 +257,24 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage parent::InitializeLinkedStylesheets(); // Used by advanced search, date(time) attributes. Coupled to the PrepareWidgets() JS function. - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/jquery-ui-timepicker-addon.css'); + $this->LinkStylesheetFromAppRoot('css/jquery-ui-timepicker-addon.css'); // Used by inline image, CKEditor and other places - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/magnific-popup/dist/magnific-popup.css'); + $this->LinkStylesheetFromAppRoot('node_modules/magnific-popup/dist/magnific-popup.css'); // Tooltips - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/animations/shift-away-subtle.css'); + $this->LinkStylesheetFromAppRoot('node_modules/tippy.js/dist/tippy.css'); + $this->LinkStylesheetFromAppRoot('node_modules/tippy.js/animations/shift-away-subtle.css'); // Icons - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-combodo/font-combodo.css'); + $this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $this->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css'); // Note: CKEditor files can't be moved easily as we need to find a way to init the "disabler" plugin, {@see js/toolbox.js} - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css'); + $this->LinkStylesheetFromAppRoot('js/ckeditor/plugins/codesnippet/lib/highlight/styles/obsidian.css'); // Used by external keys and other drop down lists - $this->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/selectize.default.css'); + $this->LinkStylesheetFromAppRoot('css/selectize.default.css'); } /** @@ -871,7 +871,7 @@ HTML; /** @var \iBackofficeLinkedScriptsExtension $oExtensionInstance */ foreach (MetaModel::EnumPlugins('iBackofficeLinkedScriptsExtension') as $oExtensionInstance) { foreach ($oExtensionInstance->GetLinkedScriptsAbsUrls() as $sScriptUrl) { - $this->add_linked_script($sScriptUrl); + $this->LinkScriptFromURI($sScriptUrl); } } // - API: Early inline scripts @@ -898,7 +898,7 @@ HTML; /** @var \iBackofficeLinkedStylesheetsExtension $oExtensionInstance */ foreach (MetaModel::EnumPlugins('iBackofficeLinkedStylesheetsExtension') as $oExtensionInstance) { foreach ($oExtensionInstance->GetLinkedStylesheetsAbsUrls() as $sStylesheetUrl) { - $this->add_linked_stylesheet($sStylesheetUrl); + $this->LinkStylesheetFromURI($sStylesheetUrl); } } // - API: Inline style diff --git a/sources/Application/WebPage/iTopWizardWebPage.php b/sources/Application/WebPage/iTopWizardWebPage.php index 9b2de9116..c5486fb00 100644 --- a/sources/Application/WebPage/iTopWizardWebPage.php +++ b/sources/Application/WebPage/iTopWizardWebPage.php @@ -54,7 +54,7 @@ class iTopWizardWebPage extends iTopWebPage $sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep'; $aSteps[] = "
$sStepTitle
"; } - $sWizardHeader = "

".utils::EscapeHtml($this->s_title)."

\n".implode("
", $aSteps)."
\n"; + $sWizardHeader = "

".utils::EscapeHtml($this->s_title)."

\n".implode("
", $aSteps)."
\n"; $this->s_content = "$sWizardHeader
".$this->s_content."
"; parent::output(); } diff --git a/sources/Controller/AjaxRenderController.php b/sources/Controller/AjaxRenderController.php index e02a084a7..794c19d84 100644 --- a/sources/Controller/AjaxRenderController.php +++ b/sources/Controller/AjaxRenderController.php @@ -845,7 +845,7 @@ EOF ); $sVersionString = Dict::Format('UI:iTopVersion:Short', ITOP_APPLICATION, ITOP_VERSION); $oPage->add('
'); - $oPage->add('
'); + $oPage->add('
'); $oPage->add('
'.$sVersionString.'
'); $oPage->add("
"); self::DisplayAboutLicenses($oPage); @@ -927,7 +927,7 @@ EOF // Display // $oPage->add('
'); - $oPage->add('
'); + $oPage->add('
'); $oPage->add('
'.$sVersionString.'
'.'MySQL: '.$sMySQLVersion.'
'.'PHP: '.$sPHPVersion.'
'); $oPage->add("
"); diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php index c8c557b54..bd230de5f 100644 --- a/sources/Controller/Base/Layout/ObjectController.php +++ b/sources/Controller/Base/Layout/ObjectController.php @@ -315,7 +315,7 @@ JS; } // - JS files foreach (static::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sJsFileRelPath); + $oPage->LinkScriptFromAppRoot($sJsFileRelPath); } // Note: Code duplicated to the case 'apply_modify' in UI.php when a data integrity issue has been found @@ -771,7 +771,7 @@ JS; protected function AddRequiredForModificationJsFilesToPage(iTopWebPage &$oPage): void { foreach (static::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { - $oPage->add_linked_script("../$sJsFileRelPath"); + $oPage->LinkScriptFromAppRoot($sJsFileRelPath); } } diff --git a/sources/Controller/Newsroom/iTopNewsroomController.php b/sources/Controller/Newsroom/iTopNewsroomController.php index ed58fea94..4a061f657 100644 --- a/sources/Controller/Newsroom/iTopNewsroomController.php +++ b/sources/Controller/Newsroom/iTopNewsroomController.php @@ -56,7 +56,7 @@ class iTopNewsroomController extends Controller public function OperationViewAll() { $oPage = new iTopWebPage(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title')); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/pages/backoffice/itop-newsroom.view-all.js'); + $oPage->LinkScriptFromAppRoot('js/pages/backoffice/itop-newsroom.view-all.js'); // Add title block // Make bulk actions block $oBulkActionsBlock = PanelUIBlockFactory::MakeForInformation(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Title')); @@ -233,10 +233,6 @@ $.ajax({ type: 'POST', success: function(data) { if (data.status === 'success') { - let MarkAsReadButton = oNotificationToMarkAsRead.find('.ibo-button-group:not(.ibo-is-hidden)'); - let MarkAsUnreadButton = oNotificationToMarkAsRead.find('.ibo-button-group.ibo-is-hidden'); - MarkAsReadButton.addClass('ibo-is-hidden'); - MarkAsUnreadButton.removeClass('ibo-is-hidden'); oNotificationToMarkAsRead.removeClass('ibo-notifications--view-all--item--unread').addClass('ibo-notifications--view-all--item--read'); CombodoToast.OpenSuccessToast(data.message); $('.ibo-notifications--view-all--container').trigger('itop.notification.read'); @@ -273,12 +269,6 @@ $.ajax({ type: 'POST', success: function(data) { if (data.status === 'success') { - let MarkAsUnreadButton = oNotificationToMarkAsUnread.find('.ibo-button-group:not(.ibo-is-hidden)'); - let MarkAsReadButton = oNotificationToMarkAsUnread.find('.ibo-button-group.ibo-is-hidden'); - - MarkAsReadButton.removeClass('ibo-is-hidden'); - MarkAsUnreadButton.addClass('ibo-is-hidden'); - oNotificationToMarkAsUnread.removeClass('ibo-notifications--view-all--item--read').addClass('ibo-notifications--view-all--item--unread'); CombodoToast.OpenSuccessToast(data.message); $('.ibo-notifications--view-all--container').trigger('itop.notification.unread'); @@ -384,15 +374,15 @@ JS $oEventBlock->SetIcon($sIconUrl, Panel::ENUM_ICON_COVER_METHOD_COVER,true); } - // Prepare Event actions - $oMarkAsReadPopoverMenu = new PopoverMenu(); - $oMarkAsUnreadPopoverMenu = new PopoverMenu(); // Common actions $sDeleteUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.delete_event', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]); - $oDeleteButton = new JSPopupMenuItem( + $oDeleteButton = ButtonUIBlockFactory::MakeForAlternativeDestructiveAction( + '', 'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label', - Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label'), + 'UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label' + ); + $oDeleteButton->SetOnClickJsCode( <<SetIconClass('fas fa-trash-alt') + ->SetTooltip(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:Delete:Label')); + + $oViewButton = ButtonUIBlockFactory::MakeIconLink('fas fa-external-link-alt', Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:ViewObject:Label'), - Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.view_event', ['event_id' => $oEvent->GetKey()]), + Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.view_event', ['event_id' => $iEventId]), '_blank' ); // Mark as read action $oMarkAsReadButton = ButtonUIBlockFactory::MakeForAlternativeSecondaryAction( - Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label'), - 'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label', + '', 'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label', + 'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label' ); - - // Mark as read action - $oMarkAsReadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oViewButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT); - $oMarkAsReadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oDeleteButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT); - - // Mark as unread action - $oMarkAsUnreadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oViewButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT); - $oMarkAsUnreadPopoverMenu->AddItem('more-actions', PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem($oDeleteButton))->SetContainer(PopoverMenu::ENUM_CONTAINER_PARENT); - + $oMarkAsReadButton->AddCSSClass('ibo-notifications--view-all--read-action') + ->SetIconClass('far fa-envelope-open') + ->SetTooltip(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsRead:Label')); // Mark as unread action $sMarkAsReadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_as_read', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]); @@ -446,8 +431,6 @@ JS, type: 'POST', success: function(data) { if (data.status === 'success') { - $(oSelf).parent('.ibo-button-group').addClass('ibo-is-hidden'); - $(oSelf).parent('.ibo-button-group').siblings('.ibo-button-group').removeClass('ibo-is-hidden'); $(oSelf).parents('.ibo-object-summary').removeClass('ibo-notifications--view-all--item--unread').addClass('ibo-notifications--view-all--item--read'); CombodoToast.OpenSuccessToast(data.message); $('.ibo-notifications--view-all--container').trigger('itop.notification.read'); @@ -460,13 +443,14 @@ JS, JS ); - $oMarkAsReadButtonGroup = ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu($oMarkAsReadButton, $oMarkAsReadPopoverMenu); - $oMarkAsUnreadButton = ButtonUIBlockFactory::MakeForAlternativeSecondaryAction( - Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label'), + '', 'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label', 'UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label' ); + $oMarkAsUnreadButton->AddCSSClass('ibo-notifications--view-all--unread-action') + ->SetIconClass('far fa-envelope') + ->SetTooltip(Dict::S('UI:Newsroom:iTopNotification:ViewAllPage:Action:MarkAsUnread:Label')); $sMarkAsUnreadUrl = Router::GetInstance()->GenerateUrl(self::ROUTE_NAMESPACE.'.mark_as_unread', ['notification_id' => $oEvent->GetKey(), 'token' => $sCSRFToken]); $oMarkAsUnreadButton->SetOnClickJsCode( <<GetActions()->GetId(); $oEventBlock->RemoveSubBlock($oOldButtonId); - $oEventBlock->SetToolBlocks([$oMarkAsReadButtonGroup, $oMarkAsUnreadButtonGroup]); + $oEventBlock->SetToolBlocks([$oMarkAsUnreadButton, $oMarkAsReadButton, $oViewButton, $oDeleteButton]); $oActionsBlock = new UIContentBlock(); - $oActionsBlock->AddSubBlock($oMarkAsReadButtonGroup); - $oActionsBlock->AddSubBlock($oMarkAsUnreadButtonGroup); + $oActionsBlock->AddSubBlock($oMarkAsUnreadButton); + $oActionsBlock->AddSubBlock($oMarkAsReadButton); + $oActionsBlock->AddSubBlock($oViewButton); + $oActionsBlock->AddSubBlock($oDeleteButton); $oEventBlock->SetActions($oActionsBlock); - // Display the right button depending on the read status - if($oEvent->Get('read') === 'no'){ - $oMarkAsUnreadButtonGroup->SetCSSClasses(['ibo-is-hidden']); - } - else{ - $oMarkAsReadButtonGroup->SetCSSClasses(['ibo-is-hidden']); - } $oMainContentBlock->AddSubBlock($oEventBlock); } diff --git a/sources/Controller/Notifications/NotificationsCenterController.php b/sources/Controller/Notifications/NotificationsCenterController.php index aaa129ea2..d121af6bf 100644 --- a/sources/Controller/Notifications/NotificationsCenterController.php +++ b/sources/Controller/Notifications/NotificationsCenterController.php @@ -255,7 +255,7 @@ JS // Add Set JS files to the page as we used a renderer ourselves, they are not added automatically by the page foreach (Set::DEFAULT_JS_FILES_REL_PATH as $sJsFile) { - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().$sJsFile); + $oPage->LinkScriptFromAppRoot($sJsFile); } $oPage->AddSubBlock($oNotificationsPanel); diff --git a/sources/Renderer/Console/ConsoleBlockRenderer.php b/sources/Renderer/Console/ConsoleBlockRenderer.php index a82b2c629..f5f484376 100644 --- a/sources/Renderer/Console/ConsoleBlockRenderer.php +++ b/sources/Renderer/Console/ConsoleBlockRenderer.php @@ -80,11 +80,11 @@ class ConsoleBlockRenderer extends BlockRenderer { // CSS files foreach ($oBlock->GetCssFilesUrlRecursively(true) as $sFileAbsUrl) { - $oPage->add_linked_stylesheet($sFileAbsUrl); + $oPage->LinkStylesheetFromURI($sFileAbsUrl); } // JS files foreach ($oBlock->GetJsFilesUrlRecursively(true) as $sFileAbsUrl) { - $oPage->add_linked_script($sFileAbsUrl); + $oPage->LinkScriptFromURI($sFileAbsUrl); } static::AddCssJsTemplatesToPageRecursively($oPage, $oBlock, $aContextParams); } diff --git a/templates/application/display-block/block-chart/layout.html.twig b/templates/application/display-block/block-chart/layout.html.twig index c3c0b233c..cbafd0f76 100644 --- a/templates/application/display-block/block-chart/layout.html.twig +++ b/templates/application/display-block/block-chart/layout.html.twig @@ -3,7 +3,7 @@ {% apply spaceless %}
- +
{% endapply %} \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php b/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php new file mode 100644 index 000000000..659d41a02 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/sources/Application/WebPage/WebPageMock.php @@ -0,0 +1,20 @@ +RequireOnceUnitTestFile("./WebPageMock.php"); + } + + /** + * @dataProvider LinkScriptMethodsProvider + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromAppRoot() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromModule() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkScriptFromURI() + * + * @param string $sMethodName + * @param string $sInputURI + * @param int $iExpectedCount + * + * @return void + * @throws \ReflectionException + */ + public function testLinkScriptMethods(string $sMethodName, string $sInputURI, int $iExpectedCount): void + { + $oPage = new WebPageMock(''); + + $this->InvokeNonPublicMethod(WebPage::class, "EmptyLinkedScripts", $oPage); + $this->InvokeNonPublicMethod(WebPage::class, $sMethodName, $oPage, [$sInputURI]); + + $aLinkedScripts = $this->GetNonPublicProperty($oPage, "a_linked_scripts"); + $this->assertEquals($iExpectedCount, count($aLinkedScripts), "Linked scripts count should be $iExpectedCount"); + } + + public function LinkScriptMethodsProvider(): array + { + return [ + // LinkScriptFromAppRoot + "LinkScriptFromAppRoot: Empty URI should be ignored" => [ + "LinkScriptFromAppRoot", + "", + 0, + ], + "LinkScriptFromAppRoot: Relative URI of existing file should be completed / added" => [ + "LinkScriptFromAppRoot", + "js/utils.js", + 1, + ], + "LinkScriptFromAppRoot: Relative URI of NON existing file should be ignored" => [ + "LinkScriptFromAppRoot", + "js/some-file.js", + 0, + ], + "LinkScriptFromAppRoot: Absolute URI should be ignored" => [ + "LinkScriptFromAppRoot", + "https://external.server/file.js", + 0, + ], + + // LinkScriptFromModule + "LinkScriptFromModule: Empty URI should be ignored" => [ + "LinkScriptFromModule", + "", + 0, + ], + "LinkScriptFromModule: Relative URI of existing file should be completed / added" => [ + "LinkScriptFromModule", + "itop-portal-base/portal/public/js/toolbox.js", + 1, + ], + "LinkScriptFromModule: Relative URI of NON existing file should be completed / added" => [ + "LinkScriptFromModule", + "some-module/asset/js/some-file.js", + 0, + ], + "LinkScriptFromModule: Absolute URI should be ignored" => [ + "LinkScriptFromModule", + "https://external.server/file.js", + 0, + ], + + // LinkScriptFromURI + "LinkScriptFromURI: Empty URI should be ignored" => [ + "LinkScriptFromURI", + "", + 0, + ], + "LinkScriptFromURI: Relative URI should be ignored" => [ + "LinkScriptFromURI", + "js/utils.js", + 0, + ], + "LinkScriptFromURI: Absolute URI should be added" => [ + "LinkScriptFromURI", + "https://external.server/file.js", + 1, + ], + ]; + } + + /** + * @dataProvider LinkStylesheetMethodsProvider + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkStylesheetFromAppRoot() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkStylesheetFromModule() + * @covers \Combodo\iTop\Application\WebPage\WebPage::LinkStylesheetFromURI() + * + * @param string $sMethodName + * @param string $sInputURI + * @param int $iExpectedCount + * + * @return void + * @throws \ReflectionException + */ + public function testLinkStylesheetMethods(string $sMethodName, string $sInputURI, int $iExpectedCount): void + { + $oPage = new WebPageMock(''); + + $this->InvokeNonPublicMethod(WebPage::class, "EmptyLinkedStylesheets", $oPage); + $this->InvokeNonPublicMethod(WebPage::class, $sMethodName, $oPage, [$sInputURI]); + + $aLinkedStylesheets = $this->GetNonPublicProperty($oPage, "a_linked_stylesheets"); + $this->assertEquals($iExpectedCount, count($aLinkedStylesheets), "Linked stylesheets count should be $iExpectedCount"); + } + + public function LinkStylesheetMethodsProvider(): array + { + return [ + // LinkStylesheetFromAppRoot + "LinkStylesheetFromAppRoot: Empty URI should be ignored" => [ + "LinkStylesheetFromAppRoot", + "", + 0, + ], + "LinkStylesheetFromAppRoot: Relative URI of existing file should be completed / added" => [ + "LinkStylesheetFromAppRoot", + "css/login.css", + 1, + ], + "LinkStylesheetFromAppRoot: Relative URI of NON existing file should be ignored" => [ + "LinkStylesheetFromAppRoot", + "css/some-file.css", + 0, + ], + "LinkStylesheetFromAppRoot: Absolute URI should be ignored" => [ + "LinkStylesheetFromAppRoot", + "https://external.server/file.css", + 0, + ], + + // LinkStylesheetFromModule + "LinkStylesheetFromModule: Empty URI should be ignored" => [ + "LinkStylesheetFromModule", + "", + 0, + ], + "LinkStylesheetFromModule: Relative URI of existing file should be completed / added" => [ + "LinkStylesheetFromModule", + "itop-portal-base/portal/public/css/portal.css", + 1, + ], + "LinkStylesheetFromModule: Relative URI of NON existing file should be completed / added" => [ + "LinkStylesheetFromModule", + "some-module/asset/js/some-file.js", + 0, + ], + "LinkStylesheetFromModule: Absolute URI should be ignored" => [ + "LinkStylesheetFromModule", + "https://external.server/file.js", + 0, + ], + + // LinkStylesheetFromURI + "LinkStylesheetFromURI: Empty URI should be ignored" => [ + "LinkStylesheetFromURI", + "", + 0, + ], + "LinkStylesheetFromURI: Relative URI should be ignored" => [ + "LinkStylesheetFromURI", + "js/login.css", + 0, + ], + "LinkStylesheetFromURI: Absolute URI should be added" => [ + "LinkStylesheetFromURI", + "https://external.server/file.css", + 1, + ], + ]; + } +} \ No newline at end of file diff --git a/webservices/export-v2.php b/webservices/export-v2.php index bba14e0ce..356ee3c67 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -235,9 +235,9 @@ function FormatDatesInPreview(sRadioSelector, sPreviewSelector) } EOF ); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/tabularfieldsselector.js'); - $oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.dragtable.js'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/dragtable.css'); + $oP->LinkScriptFromAppRoot('js/tabularfieldsselector.js'); + $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); + $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); $oForm = FormUIBlockFactory::MakeStandard("export-form"); $oForm->SetAction($sAction); @@ -707,8 +707,8 @@ try $oP = new NiceWebPage('iTop export'); $oP->add_http_headers(); $oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});"); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); } else { $oP = new WebPage('iTop export'); $oP->add_http_headers(); diff --git a/webservices/export.php b/webservices/export.php index 578a4c83e..6b60361e5 100644 --- a/webservices/export.php +++ b/webservices/export.php @@ -219,8 +219,8 @@ if (!empty($sExpression)) case 'html': $oP = new NiceWebPage("iTop - Export"); $oP->add_style('body { overflow: auto; }'); // Show scroll bars if needed - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/all.min.css'); - $oP->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/font-awesome/css/v4-shims.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css'); + $oP->LinkStylesheetFromAppRoot('css/font-awesome/css/v4-shims.min.css'); // Integration within MS-Excel web queries + HTTPS + IIS: // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS