/** * Copyright since 2007 PrestaShop SA and Contributors * PrestaShop is an International Registered Trademark & Property of PrestaShop SA * * NOTICE OF LICENSE * * This source file is subject to the Open Software License (OSL 3.0) * that is bundled with this package in the file LICENSE.md. * It is also available through the world-wide-web at this URL: * https://opensource.org/licenses/OSL-3.0 * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email * to license@prestashop.com so we can send you a copy immediately. * * DISCLAIMER * * Do not edit or add to this file if you wish to upgrade PrestaShop to newer * versions in the future. If you wish to customize PrestaShop for your * needs please refer to https://devdocs.prestashop.com/ for more information. * * @author PrestaShop SA and Contributors * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ import {EventEmitter} from 'events'; import ConfirmModal from '@components/modal'; import ComponentsMap from './components-map'; const ModuleCardMap = ComponentsMap.moduleCard; const {$} = window; /** * Class is responsible for handling Module Card behavior */ export default class ModuleCard { moduleActionMenuLinkSelector: string; moduleActionMenuInstallLinkSelector: string; moduleActionMenuEnableLinkSelector: string; moduleActionMenuUninstallLinkSelector: string; moduleActionMenuDisableLinkSelector: string; moduleActionMenuEnableMobileLinkSelector: string; moduleActionMenuDisableMobileLinkSelector: string; moduleActionMenuResetLinkSelector: string; moduleActionMenuUpdateLinkSelector: string; moduleActionMenuDeleteLinkSelector: string; moduleItemListSelector: string; moduleItemGridSelector: string; moduleItemActionsSelector: string; moduleActionModalDisableLinkSelector: string; moduleActionModalResetLinkSelector: string; moduleActionModalUninstallLinkSelector: string; forceDeletionOption: string; private pendingRequest: boolean = false; private eventEmitter: EventEmitter; constructor() { /* Selectors for module action links (uninstall, reset, etc...) to add a confirm popin */ this.moduleActionMenuLinkSelector = 'button.module_action_menu_'; this.moduleActionMenuInstallLinkSelector = 'button.module_action_menu_install'; this.moduleActionMenuEnableLinkSelector = 'button.module_action_menu_enable'; this.moduleActionMenuUninstallLinkSelector = 'button.module_action_menu_uninstall'; this.moduleActionMenuDisableLinkSelector = 'button.module_action_menu_disable'; this.moduleActionMenuEnableMobileLinkSelector = 'button.module_action_menu_enableMobile'; this.moduleActionMenuDisableMobileLinkSelector = 'button.module_action_menu_disableMobile'; this.moduleActionMenuResetLinkSelector = 'button.module_action_menu_reset'; this.moduleActionMenuUpdateLinkSelector = 'button.module_action_menu_upgrade'; this.moduleActionMenuDeleteLinkSelector = 'button.module_action_menu_delete'; this.moduleItemListSelector = '.module-item-list'; this.moduleItemGridSelector = '.module-item-grid'; this.moduleItemActionsSelector = '.module-actions'; /* Selectors only for modal buttons */ this.moduleActionModalDisableLinkSelector = 'a.module_action_modal_disable'; this.moduleActionModalResetLinkSelector = 'a.module_action_modal_reset'; this.moduleActionModalUninstallLinkSelector = 'a.module_action_modal_uninstall'; this.forceDeletionOption = '#force_deletion'; this.eventEmitter = window.prestashop.component.EventEmitter; this.initActionButtons(); } initActionButtons(): void { const self = this; $(document).on('click', this.forceDeletionOption, function () { const btn = $( self.moduleActionModalUninstallLinkSelector, $(ModuleCardMap.moduleItemList($(this).attr('data-tech-name'))), ); if ($(this).prop('checked') === true) { btn.attr('data-deletion', 'true'); } else { btn.removeAttr('data-deletion'); } }); $(document).on( 'click', this.moduleActionMenuInstallLinkSelector, function () { return ( self.dispatchPreEvent('install', this) && self.confirmAction('install', this) && self.requestToController('install', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuEnableLinkSelector, function () { return ( self.dispatchPreEvent('enable', this) && self.confirmAction('enable', this) && self.requestToController('enable', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuUninstallLinkSelector, function () { return ( self.dispatchPreEvent('uninstall', this) && self.confirmAction('uninstall', this) && self.requestToController('uninstall', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuDeleteLinkSelector, function () { return ( self.dispatchPreEvent('delete', this) && self.confirmAction('delete', this) && self.requestToController('delete', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuDisableLinkSelector, function () { return ( self.dispatchPreEvent('disable', this) && self.confirmAction('disable', this) && self.requestToController('disable', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuEnableMobileLinkSelector, function () { return ( self.dispatchPreEvent('enableMobile', this) && self.confirmAction('enableMobile', this) && self.requestToController('enableMobile', $(this)) ); }, ); $(document).on( 'click', this.moduleActionMenuDisableMobileLinkSelector, function () { return ( self.dispatchPreEvent('disableMobile', this) && self.confirmAction('disableMobile', this) && self.requestToController('disableMobile', $(this)) ); }, ); $(document).on('click', this.moduleActionMenuResetLinkSelector, function () { return ( self.dispatchPreEvent('reset', this) && self.confirmAction('reset', this) && self.requestToController('reset', $(this)) ); }); $(document).on('click', this.moduleActionMenuUpdateLinkSelector, function ( event, ) { event.preventDefault(); const modal = $(`#${$(this).data('confirm_modal')}`); const isMaintenanceMode = window.isShopMaintenance; if (modal.length !== 1) { // Modal body element const maintenanceLink = document.createElement('a'); maintenanceLink.classList.add('btn', 'btn-primary', 'btn-lg'); maintenanceLink.setAttribute('href', window.moduleURLs.maintenancePage); maintenanceLink.innerHTML = window.moduleTranslations.moduleModalUpdateMaintenance; const updateConfirmModal = new ConfirmModal( { id: 'confirm-module-update-modal', confirmTitle: window.moduleTranslations.singleModuleModalUpdateTitle, closeButtonLabel: window.moduleTranslations.moduleModalUpdateCancel, confirmButtonLabel: isMaintenanceMode ? window.moduleTranslations.moduleModalUpdateUpgrade : window.moduleTranslations.upgradeAnywayButtonText, confirmButtonClass: isMaintenanceMode ? 'btn-primary' : 'btn-secondary', confirmMessage: isMaintenanceMode ? '' : window.moduleTranslations.moduleModalUpdateConfirmMessage, closable: true, customButtons: isMaintenanceMode ? [] : [maintenanceLink], }, () => self.dispatchPreEvent('update', this) && self.confirmAction('update', this) && self.requestToController('update', $(this)), ); updateConfirmModal.show(); } else { return ( self.dispatchPreEvent('update', this) && self.confirmAction('update', this) && self.requestToController('update', $(this)) ); } return false; }); $(document).on( 'click', this.moduleActionModalDisableLinkSelector, function () { return self.requestToController( 'disable', $( self.moduleActionMenuDisableLinkSelector, $( ModuleCardMap.moduleItemList( $(this).attr('data-tech-name'), ), ), ), ); }, ); $(document).on( 'click', this.moduleActionModalResetLinkSelector, function () { return self.requestToController( 'reset', $( self.moduleActionMenuResetLinkSelector, $( ModuleCardMap.moduleItemList( $(this).attr('data-tech-name'), ), ), ), ); }, ); $(document).on( 'click', this.moduleActionModalUninstallLinkSelector, (e) => { $(e.target) .parents('.modal') .on('hidden.bs.modal', () => self.requestToController( 'uninstall', $( self.moduleActionMenuUninstallLinkSelector, $( ModuleCardMap.moduleItemList( $(e.target).attr('data-tech-name'), ), ), ), $(e.target).attr('data-deletion'), ), ); }, ); } getModuleItemSelector(): string { if ($(this.moduleItemListSelector).length) { return this.moduleItemListSelector; } return this.moduleItemGridSelector; } confirmAction(action: string, element: string): boolean { const modal = $( ComponentsMap.confirmModal($(element).data('confirm_modal')), ); if (modal.length !== 1) { return true; } modal.first().modal('show'); return false; // do not allow a.href to reload the page. The confirm modal dialog will do it async if needed. } dispatchPreEvent(action: string, element: string): boolean { const event = jQuery.Event('module_card_action_event'); $(element).trigger(event, [action]); if ( event.isPropagationStopped() !== false || event.isImmediatePropagationStopped() !== false ) { return false; // if all handlers have not been called, then stop propagation of the click event. } // @ts-ignore-next-line return event.result !== false; // explicit false must be set from handlers to stop propagation of the click event. } hasPendingRequest(): boolean { return this.pendingRequest; } requestToController( action: string, element: JQuery, forceDeletion: string | boolean = false, callback = () => true, ): boolean { if (this.pendingRequest) { $.growl.warning({ message: window.translate_javascripts['An action is already in progress. Please wait for it to finish.'], }); return false; } this.pendingRequest = true; const self = this; let jqElementObj = element.closest(this.moduleItemActionsSelector); const form = element.closest('form'); const spinnerObj = $( '', ); const url = `//${window.location.host}${form.attr('action')}`; const actionParams = form.serializeArray(); let refreshNeeded = false; if (forceDeletion === 'true' || forceDeletion === true) { actionParams.push({name: 'actionParams[deletion]', value: 'true'}); } $.ajax({ url, dataType: 'json', method: 'POST', data: actionParams, beforeSend() { jqElementObj.hide(); jqElementObj.after(spinnerObj); }, }) .done((result) => { if (result === undefined) { $.growl.error({ message: 'No answer received from server', fixed: true, }); return; } if (typeof result.status !== 'undefined' && result.status === false) { $.growl.error({message: result.msg, fixed: true}); return; } const moduleTechName = Object.keys(result)[0]; if (result[moduleTechName].status === false) { $.growl.error({message: result[moduleTechName].msg, fixed: true}); return; } $.growl({ message: result[moduleTechName].msg, duration: 6000, }); if (result[moduleTechName].refresh_needed === true) { refreshNeeded = true; return; } const alteredSelector = self.getModuleItemSelector().replace('.', ''); let mainElement = null; if (action === 'delete' && !result[moduleTechName].has_download_url) { mainElement = jqElementObj.closest(`.${alteredSelector}`); this.eventEmitter.emit('Module Delete', mainElement); } else if (action === 'uninstall') { mainElement = jqElementObj.closest(`.${alteredSelector}`); mainElement.attr('data-installed', '0'); mainElement.attr('data-active', '0'); if ((forceDeletion === 'true' || forceDeletion === true) && !result[moduleTechName].has_download_url) { this.eventEmitter.emit('Module Delete', mainElement); } else { this.eventEmitter.emit('Module Uninstalled', mainElement); } } else if (action === 'disable') { mainElement = jqElementObj.closest(`.${alteredSelector}`); mainElement.addClass(`${alteredSelector}-isNotActive`); mainElement.attr('data-active', '0'); this.eventEmitter.emit('Module Disabled', mainElement); } else if (action === 'enable') { mainElement = jqElementObj.closest(`.${alteredSelector}`); mainElement.removeClass(`${alteredSelector}-isNotActive`); mainElement.attr('data-active', '1'); this.eventEmitter.emit('Module Enabled', mainElement); } else if (action === 'install') { mainElement = jqElementObj.closest(`.${alteredSelector}`); mainElement.attr('data-installed', '1'); mainElement.attr('data-active', '1'); mainElement.removeClass(`${alteredSelector}-isNotActive`); this.eventEmitter.emit('Module Installed', mainElement); } else if (action === 'update' || action === 'upgrade') { // because the action is update on ModuleManager button and upgrade on bulk actions mainElement = jqElementObj.closest(`.${alteredSelector}`); this.eventEmitter.emit('Module Upgraded', mainElement); } // Since we replace the DOM content // we need to update the jquery object reference to target the new content, // and we need to hide the new content which is not hidden by default jqElementObj = $(result[moduleTechName].action_menu_html).replaceAll(jqElementObj); jqElementObj.hide(); }) .fail(() => { const moduleItem = jqElementObj.closest('module-item-list'); const techName = moduleItem.data('techName'); $.growl.error({ message: `Could not perform action ${action} for module ${techName}`, fixed: true, }); }) .always(() => { if (refreshNeeded) { document.location.reload(); return; } jqElementObj.fadeIn(); spinnerObj.remove(); this.pendingRequest = false; if (callback) { callback(); } }); return false; } }