From 04467c6cb84880dd24fdcb2c5313062eb6be9526 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 27 Jan 2023 23:54:50 -0500 Subject: [PATCH 01/47] Progress round 2, manual conflict resolution on base grid and tabs classes --- .eslintrc.js | 57 ++-- .../assets/modext/widgets/core/modx.grid.js | 322 ++++++++++++++++-- .../assets/modext/widgets/core/modx.tabs.js | 71 ++-- 3 files changed, 362 insertions(+), 88 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 36ad477a907..d0cfd62f6cf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,41 +1,58 @@ module.exports = { env: { browser: true, - es2021: true, + es2021: true }, extends: [ 'eslint:recommended', - 'airbnb-base', + 'airbnb-base' ], globals: { MODx: 'readonly', Ext: 'readonly', - _: 'readonly', + _: 'readonly' }, ignorePatterns: [ - 'manager/assets/modext/workspace/workspace.panel.js', 'manager/assets/ext3/**/*.js', 'manager/assets/fileapi/**/*.js', 'manager/assets/lib/**/*.js', 'manager/assets/modext/modx.jsgrps-min.js', - 'setup/assets/js/ext-core.js', - 'setup/assets/js/ext-core-debug.js', - ], - overrides: [ + 'setup/assets/js/ext-core-debug.js' ], + overrides: [], parserOptions: { - ecmaVersion: 'latest', + ecmaVersion: 'latest' }, rules: { - // TODO Enable rules gradually - indent: 0, - quotes: ['error', 'single'], - semi: 0, - 'space-before-function-paren': 0, - 'comma-dangle': 0, - 'prefer-arrow-callback': 0, - 'space-before-blocks': 0, - 'object-shorthand': 0, - }, -} + 'arrow-parens': ['error', 'as-needed'], + 'comma-dangle': ['error', 'never'], + 'consistent-return': 0, + curly: ['error', 'all'], + eqeqeq: ['error', 'smart'], + 'func-names': ['warn', 'as-needed'], + indent: ['error', 4, { + VariableDeclarator: 'first', + SwitchCase: 1 + }], + 'max-len': ['warn', { + code: 140, + ignoreComments: true + }], + 'no-continue': 'warn', + 'no-new': 'warn', + 'no-param-reassign': 'warn', + 'no-plusplus': ['error', { + allowForLoopAfterthoughts: true + }], + 'no-underscore-dangle': 'warn', + 'no-unused-vars': ['error', { args: 'none' }], + 'no-use-before-define': ['error', 'nofunc'], + 'object-shorthand': ['error', 'consistent'], + 'one-var': ['error', 'consecutive'], + 'prefer-arrow-callback': 'warn', + 'prefer-rest-params': 'warn', + 'semi-style': ['warn', 'last'], + 'space-before-function-paren': ['error', 'never'] + } +}; \ No newline at end of file diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index e754c5e1d8f..a204f267679 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,8 +143,21 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - - this.on('rowcontextmenu',this._showMenu,this); + this.on({ + render: { + fn: function() { + const topToolbar = this.getTopToolbar(); + if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { + this.hasNestedFilters = true; + } + }, + scope: this + }, + rowcontextmenu: { + fn: this._showMenu, + scope: this + } + }); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -787,9 +800,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - }, + } - actionContextMenu: function(record, recordIndex, e) { + ,actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -826,6 +839,35 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } + /** + * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel + * + * @param {Object} referenceCmp - A child component of the TabPanel we're looking for + * @return Ext.TabPanel + */ + ,findTabPanel: function(referenceCmp) { + if (!referenceCmp.hasOwnProperty('ownerCt')) { + console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); + return false; + } + const container = referenceCmp.ownerCt, + isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') + ; + if (isTabPanel) { + return container; + } + return this.findTabPanel(container); + } + + /** + * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested + * within a secondary container; they will be nested when they have labels and those labels are + * positioned above the filter's input. + */ + ,hasNestedFilters: false + + ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language + /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -837,42 +879,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, + tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let tabPanel = this.ownerCt.ownerCt, - hasParentTabPanel = false, + let hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; + } else { + MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } - /* - If there is an additional container in the structure, - we need to search further for the tabs object... - NOTE: This may be able to be removed once all grid panels have been - updated and their structures have been made consistent with one another - */ - if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { - if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { - tabPanel = tabPanel.ownerCt; - } - } - // Make sure we've retrieved a tab panel before working on/with it - if (tabPanel && tabPanel.xtype.includes('tabs')) { + if (tabPanel) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = tabPanel.ownerCt.ownerCt; - if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { + const parentTabPanel = this.findTabPanel(tabPanel); + if (parentTabPanel) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -897,6 +929,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } } + // console.log('store baseParams: ', store.baseParams); store.load(); MODx.util.url.setParams(urlParams) if (bottomToolbar) { @@ -907,32 +940,57 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared + * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value + * may also be specified. The expected format for each item in the list is: + * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR + * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) + * */ - ,clearGridFilters: function(itemIds) { + ,clearGridFilters: function(items) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar() + bottomToolbar = this.getBottomToolbar(), + data = Array.isArray(items) ? items : items.split(',') ; - itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); - /* - Note that param below relies on the following naming convention being followed for each filter's config: - itemId: 'filter-category', where 'category' is the record index to filter on - */ - itemIds.forEach(itemId => { - const id = itemId.trim(), - cmp = this.getFilterComponent(id) + data.forEach(item => { + itemData = item.replace(/\s+/g, '').split(':'); + // console.log('nested ct: ', topToolbar.find('itemId', `${item[0]}-container`)); + const itemId = itemData[0], + itemDefaultVal = itemData.length == 2 ? itemData[1] : null , + cmp = this.getFilterComponent(itemId), + param = MODx.util.url.getParamNameFromCmp(cmp) ; - let param = id.split('-')[1]; - param = param == 'ns' ? 'namespace' : param ; + // console.log('filter cmp: ', cmp); if (cmp.xtype.includes('combo')) { - cmp.setValue(null); + cmp.setValue(itemDefaultVal); } else { cmp.setValue(''); } - store.baseParams[param] = ''; + if (!Ext.isEmpty(itemDefaultVal)) { + // console.log('clear filters, cmp', cmp); + const paramsList = Object.keys(cmp.baseParams); + // console.log('filter param list: ', paramsList); + paramsList.forEach(param => { + switch(param) { + case 'namespace': + console.log(`namespace -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); + cmp.baseParams[param] = 'core'; + break; + case 'topic': + console.log(`topic -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); + cmp.baseParams[param] = 'default'; + break; + } + }); + cmp.getStore().load(); + // store.baseParams[param] = itemDefaultVal; + } else { + // store.baseParams[param] = ''; + } + store.baseParams[param] = itemDefaultVal; + // store.baseParams[param] = !Ext.isEmpty(itemDefaultVal) ? itemDefaultVal : '' ; }); store.load(); - MODx.util.url.clearParams(); + MODx.util.url.clearAllParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1013,6 +1071,196 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } + + /** + * @property {Function} resetDependentFilters - Desc + */ + ,resetDependentFilters: function(gridId, cmp, targets) { + const store = this.getStore(), + toolbar = cmp.ownerCt, + callerId = cmp.itemId, + callerDataIndex = callerId.replace('filter-', ''), + callerValue = cmp.getValue(), + targetCmps = {} + ; + targets = targets.replace(/\s+/g, '').split(','); + + // get all target filter component objects + targets.forEach(target => { + target = target.replace(/\s+/g, '').split(':')[0]; + const targetItemId = target.includes('filter-') ? target : `filter-${target}`; + targetCmps[target] = this.getFilterComponent(targetItemId); + + }); + // console.log('resetDependentFilters, gridId: ', gridId); + // console.log('resetDependentFilters, targetCmps', targetCmps); + + switch(gridId) { + case 'modx-grid-lexicon': + targets.forEach(target => { + target = target.replace(/\s+/g, '').split(':'); + const filterId = target[0], + filterDefault = target.length > 1 ? target[1] : null , + targetCurrentValue = targetCmps[filterId].getValue() + ; + + if (filterId == 'query') { + // probably do nothing ??? + } else { + // console.log('resetDependentFilters, callerDataIndex', callerDataIndex); + // console.log('resetDependentFilters, callerValue', callerValue); + /* + callerDataIndex is the data index of the filter whose value has changed, prompting + the update of this iteration's target (dependent filter) + */ + const targetStore = targetCmps[filterId].getStore(), + targetStoreLastOpts = targetStore.lastOptions + ; + switch(callerDataIndex) { + case 'ns': + // targetCmps[filterId].store.baseParams['namespace'] = callerValue; + // targetCmps[filterId].store.load(); + Ext.apply(targetStoreLastOpts.params, { + namespace: callerValue + }); + targetStore.reload(targetStoreLastOpts); + + if (filterId == 'language') { + const filterStore = targetCmps[filterId].getStore(), + currentValueFound = filterStore.findExact('name', targetCurrentValue) + ; + console.log(`${filterId} cmp: `, targetCmps[filterId]); + console.log(`${filterId} current val: `, targetCurrentValue); + console.log(`${filterId} default val: `, filterDefault); + console.log(`${filterId} current val found at: `, currentValueFound); + } + // targetCmps[filterId].setValue(filterDefault); + // targetCmps[filterId].store.load(); + break; + + case 'topic': + + break; + + case 'language': + // const targetCurrentValue = targetCmps[filterId].getValue(); + + targetCmps[filterId].store.baseParams['language'] = callerValue; + // console.log('resetDependentFilters, language > targetCmps:', targetCmps); + // console.log('resetDependentFilters, language > topic:', targetCmps[filterId]); + // console.log('resetDependentFilters, language > ns:', targetCmps['ns']); + + targetCmps[filterId].store.load(); + + const filterStore = targetCmps[filterId].getStore(), + currentValueFound = filterStore.findExact('name', targetCurrentValue) + ; + // console.log(`${targetCmps[filterId]} store: `, filterStore); + console.log(`${filterId} current val: `, targetCurrentValue); + console.log(`${filterId} default val: `, filterDefault); + console.log(`${filterId} current val found at: `, currentValueFound); + // Only reset to the default if the currently-selected topic is not found for this language + if (currentValueFound === -1) { + targetCmps[filterId].setValue(filterDefault); + targetCmps[filterId].store.load(); + } + break; + } + } + }); + + break; + } + store.load(); + } + + /** + * @property {Function} getQueryFilterField - Creates the query field component configuration + * + * @param {String} implementation - An optional identifier used to assign grid-specific behavior + * @param {Number} index - Optional explicitly-set tab index for this field + * @return {Object} + */ + ,getQueryFilterField: function(implementation = 'default', index = 1) { + // console.log('query fld implementation: ', implementation); + return { + xtype: 'textfield', + itemId: 'filter-query', + emptyText: _('search'), + value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', + // tabIndex: index, + cls: 'filter-query', + listeners: { + change: { + fn: function(cmp, newValue, oldValue) { + this.applyGridFilter(cmp); + if (implementation == 'user-group-users') { + // console.log('doing extra stuff for ug users grid...'); + const usergroupTree = Ext.getCmp('modx-tree-usergroup'), + selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), + groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) + ; + MODx.util.url.setParams({group: groupId}); + // console.log('ug tree: ', usergroupTree); + // console.log('ug tree selected: ', selectedNode); + // console.log('ugu query change, this: ', this); + } + }, + scope: this + }, + afterrender: { + fn: function(cmp) { + if (MODx.request.query) { + this.applyGridFilter(cmp); + } + }, + scope: this + }, + render: { + fn: function(cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER, + fn: this.blur, + scope: cmp + }); + } + ,scope: this + } + } + } + } + + /** + * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration + * + * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared + * @param {Number} index - Optional explicitly-set tab index for this field + * @return {Object} + */ + ,getClearFiltersButton: function(filters = 'filter-query', index = 2) { + if (Ext.isEmpty(filters)) { + console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); + return {}; + } + return { + text: _('filter_clear'), + itemId: 'filter-clear', + // tabIndex: index, + listeners: { + click: { + fn: function(cmp) { + this.clearGridFilters(filters); + }, + scope: this + }, + mouseout: { + fn: function(evt) { + this.removeClass('x-btn-focus'); + } + } + } + } + } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 350908b00fe..5df4a849f75 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,25 +1,25 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - enableTabScroll: true - ,layoutOnTabChange: true - ,plain: true - ,deferredRender: true - ,hideMode: 'offsets' - ,defaults: { - autoHeight: true - ,hideMode: 'offsets' - ,border: true - ,autoWidth: true - ,bodyCssClass: 'tab-panel-wrapper' - } - ,activeTab: 0 - ,border: false - ,autoScroll: true - ,autoHeight: true - ,cls: 'modx-tabs' + Ext.applyIf(config, { + enableTabScroll: true, + layoutOnTabChange: true, + plain: true, + deferredRender: true, + hideMode: 'offsets', + defaults: { + autoHeight: true, + hideMode: 'offsets', + border: true, + autoWidth: true, + bodyCssClass: 'tab-panel-wrapper' + }, + activeTab: 0, + border: false, + autoScroll: true, + autoHeight: true, + cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this,config); + MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ afterrender: function(tabPanel) { @@ -40,10 +40,15 @@ MODx.Tabs = function(config) { NOTE: The currentTab is the previous one being navigated away from */ + // console.log('request: ', MODx.request); + console.log('tabs, beforetabchange, currentTab (prev): ', currentTab.id); + console.log('tabs, beforetabchange, newTab: ', newTab?.id); + if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; + // console.log('tab changed...'); let itemsSource, gridObj = null ; @@ -53,6 +58,8 @@ MODx.Tabs = function(config) { : currentTab.items.items[0].activeTab.items; } else { itemsSource = currentTab.items; + // console.log('current tab:', currentTab); + // console.log('current tab items: ', itemsSource); } if (itemsSource.length > 0) { gridObj = this.findGridObject(itemsSource); @@ -69,6 +76,7 @@ MODx.Tabs = function(config) { const toolbar = gridObj.getTopToolbar(), filterIds = [] ; + // console.log('tabs, toolbar:', toolbar); if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { @@ -77,6 +85,7 @@ MODx.Tabs = function(config) { }); } if (filterIds.length > 0) { + // console.log('clearing filters: ', filterIds); gridObj.clearGridFilters(filterIds); } } @@ -109,19 +118,19 @@ Ext.reg('modx-tabs', MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - cls: 'vertical-tabs-panel' - ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } - ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } - ,defaults: { - bodyCssClass: 'vertical-tabs-body' - ,autoScroll: true - ,autoHeight: true - ,autoWidth: true - ,layout: 'form' + Ext.applyIf(config, { + cls: 'vertical-tabs-panel', + headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, + bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, + defaults: { + bodyCssClass: 'vertical-tabs-body', + autoScroll: true, + autoHeight: true, + autoWidth: true, + layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this,config); + MODx.VerticalTabs.superclass.constructor.call(this, config); this.config = config; this.on('afterrender', function() { if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { @@ -131,4 +140,4 @@ MODx.VerticalTabs = function(config) { }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs',MODx.VerticalTabs); +Ext.reg('modx-vtabs', MODx.VerticalTabs); From 293e587560739ea8d2e5108e7c2e96d132ef3b0b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 27 Jan 2023 23:57:34 -0500 Subject: [PATCH 02/47] Progress round 2, manual conflict resolution for usergroup acls panels --- .../modext/widgets/security/modx.grid.user.group.category.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.context.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.namespace.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.resource.js | 2 ++ .../modext/widgets/security/modx.grid.user.group.source.js | 2 ++ 5 files changed, 10 insertions(+) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index 5db825fbaa9..cb4a86edb95 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -147,6 +147,8 @@ MODx.grid.UserGroupCategory = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-category, filter-policy-category'); ] }); MODx.grid.UserGroupCategory.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js index ab7f4896455..62cc7e60328 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js @@ -141,6 +141,8 @@ MODx.grid.UserGroupContext = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-context, filter-policy-context'); ] }); MODx.grid.UserGroupContext.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index 6569ff5f7fd..a349c91bcb9 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -146,6 +146,8 @@ MODx.grid.UserGroupNamespace = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-namespace, filter-policy-namespace'); ] }); MODx.grid.UserGroupNamespace.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js index c552e928a4a..e6fb3a989fd 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js @@ -149,6 +149,8 @@ MODx.grid.UserGroupResourceGroup = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-resourceGroup, filter-policy-resourceGroup'); ] }); MODx.grid.UserGroupResourceGroup.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js index 6ad924fb7d7..4dd74a2d90a 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js @@ -142,6 +142,8 @@ MODx.grid.UserGroupSource = function(config) { } ,scope: this } + // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account + // this.getClearFiltersButton('filter-source, filter-policy-source'); ] }); MODx.grid.UserGroupSource.superclass.constructor.call(this,config); From a99c4768554c20a3a44c2732db52d985db4e6b18 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sat, 28 Jan 2023 00:08:55 -0500 Subject: [PATCH 03/47] Revert "Progress round 2, manual conflict resolution on base grid and tabs classes" This reverts commit d70885cc3ecfcf97f80d39300aad2df84fcf1fdd. --- .eslintrc.js | 57 ++-- .../assets/modext/widgets/core/modx.grid.js | 322 ++---------------- .../assets/modext/widgets/core/modx.tabs.js | 83 ++--- 3 files changed, 93 insertions(+), 369 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index d0cfd62f6cf..36ad477a907 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,58 +1,41 @@ module.exports = { env: { browser: true, - es2021: true + es2021: true, }, extends: [ 'eslint:recommended', - 'airbnb-base' + 'airbnb-base', ], globals: { MODx: 'readonly', Ext: 'readonly', - _: 'readonly' + _: 'readonly', }, ignorePatterns: [ + 'manager/assets/modext/workspace/workspace.panel.js', 'manager/assets/ext3/**/*.js', 'manager/assets/fileapi/**/*.js', 'manager/assets/lib/**/*.js', 'manager/assets/modext/modx.jsgrps-min.js', + 'setup/assets/js/ext-core.js', - 'setup/assets/js/ext-core-debug.js' + 'setup/assets/js/ext-core-debug.js', + ], + overrides: [ ], - overrides: [], parserOptions: { - ecmaVersion: 'latest' + ecmaVersion: 'latest', }, rules: { - 'arrow-parens': ['error', 'as-needed'], - 'comma-dangle': ['error', 'never'], - 'consistent-return': 0, - curly: ['error', 'all'], - eqeqeq: ['error', 'smart'], - 'func-names': ['warn', 'as-needed'], - indent: ['error', 4, { - VariableDeclarator: 'first', - SwitchCase: 1 - }], - 'max-len': ['warn', { - code: 140, - ignoreComments: true - }], - 'no-continue': 'warn', - 'no-new': 'warn', - 'no-param-reassign': 'warn', - 'no-plusplus': ['error', { - allowForLoopAfterthoughts: true - }], - 'no-underscore-dangle': 'warn', - 'no-unused-vars': ['error', { args: 'none' }], - 'no-use-before-define': ['error', 'nofunc'], - 'object-shorthand': ['error', 'consistent'], - 'one-var': ['error', 'consecutive'], - 'prefer-arrow-callback': 'warn', - 'prefer-rest-params': 'warn', - 'semi-style': ['warn', 'last'], - 'space-before-function-paren': ['error', 'never'] - } -}; \ No newline at end of file + // TODO Enable rules gradually + indent: 0, + quotes: ['error', 'single'], + semi: 0, + 'space-before-function-paren': 0, + 'comma-dangle': 0, + 'prefer-arrow-callback': 0, + 'space-before-blocks': 0, + 'object-shorthand': 0, + }, +} diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index a204f267679..e754c5e1d8f 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,21 +143,8 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - this.on({ - render: { - fn: function() { - const topToolbar = this.getTopToolbar(); - if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { - this.hasNestedFilters = true; - } - }, - scope: this - }, - rowcontextmenu: { - fn: this._showMenu, - scope: this - } - }); + + this.on('rowcontextmenu',this._showMenu,this); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -800,9 +787,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - } + }, - ,actionContextMenu: function(record, recordIndex, e) { + actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -839,35 +826,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } - /** - * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel - * - * @param {Object} referenceCmp - A child component of the TabPanel we're looking for - * @return Ext.TabPanel - */ - ,findTabPanel: function(referenceCmp) { - if (!referenceCmp.hasOwnProperty('ownerCt')) { - console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); - return false; - } - const container = referenceCmp.ownerCt, - isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') - ; - if (isTabPanel) { - return container; - } - return this.findTabPanel(container); - } - - /** - * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested - * within a secondary container; they will be nested when they have labels and those labels are - * positioned above the filter's input. - */ - ,hasNestedFilters: false - - ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language - /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -879,32 +837,42 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, - tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let hasParentTabPanel = false, + let tabPanel = this.ownerCt.ownerCt, + hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; - } else { - MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } + /* + If there is an additional container in the structure, + we need to search further for the tabs object... - if (tabPanel) { + NOTE: This may be able to be removed once all grid panels have been + updated and their structures have been made consistent with one another + */ + if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { + if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { + tabPanel = tabPanel.ownerCt; + } + } + // Make sure we've retrieved a tab panel before working on/with it + if (tabPanel && tabPanel.xtype.includes('tabs')) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = this.findTabPanel(tabPanel); - if (parentTabPanel) { + const parentTabPanel = tabPanel.ownerCt.ownerCt; + if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -929,7 +897,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } } - // console.log('store baseParams: ', store.baseParams); store.load(); MODx.util.url.setParams(urlParams) if (bottomToolbar) { @@ -940,57 +907,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value - * may also be specified. The expected format for each item in the list is: - * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR - * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) - * + * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared */ - ,clearGridFilters: function(items) { + ,clearGridFilters: function(itemIds) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar(), - data = Array.isArray(items) ? items : items.split(',') + bottomToolbar = this.getBottomToolbar() ; - data.forEach(item => { - itemData = item.replace(/\s+/g, '').split(':'); - // console.log('nested ct: ', topToolbar.find('itemId', `${item[0]}-container`)); - const itemId = itemData[0], - itemDefaultVal = itemData.length == 2 ? itemData[1] : null , - cmp = this.getFilterComponent(itemId), - param = MODx.util.url.getParamNameFromCmp(cmp) + itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); + /* + Note that param below relies on the following naming convention being followed for each filter's config: + itemId: 'filter-category', where 'category' is the record index to filter on + */ + itemIds.forEach(itemId => { + const id = itemId.trim(), + cmp = this.getFilterComponent(id) ; - // console.log('filter cmp: ', cmp); + let param = id.split('-')[1]; + param = param == 'ns' ? 'namespace' : param ; if (cmp.xtype.includes('combo')) { - cmp.setValue(itemDefaultVal); + cmp.setValue(null); } else { cmp.setValue(''); } - if (!Ext.isEmpty(itemDefaultVal)) { - // console.log('clear filters, cmp', cmp); - const paramsList = Object.keys(cmp.baseParams); - // console.log('filter param list: ', paramsList); - paramsList.forEach(param => { - switch(param) { - case 'namespace': - console.log(`namespace -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); - cmp.baseParams[param] = 'core'; - break; - case 'topic': - console.log(`topic -- reset ${param} baseParam for ${itemId}, filterDefault: ${itemDefaultVal}`); - cmp.baseParams[param] = 'default'; - break; - } - }); - cmp.getStore().load(); - // store.baseParams[param] = itemDefaultVal; - } else { - // store.baseParams[param] = ''; - } - store.baseParams[param] = itemDefaultVal; - // store.baseParams[param] = !Ext.isEmpty(itemDefaultVal) ? itemDefaultVal : '' ; + store.baseParams[param] = ''; }); store.load(); - MODx.util.url.clearAllParams(); + MODx.util.url.clearParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1071,196 +1013,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } - - /** - * @property {Function} resetDependentFilters - Desc - */ - ,resetDependentFilters: function(gridId, cmp, targets) { - const store = this.getStore(), - toolbar = cmp.ownerCt, - callerId = cmp.itemId, - callerDataIndex = callerId.replace('filter-', ''), - callerValue = cmp.getValue(), - targetCmps = {} - ; - targets = targets.replace(/\s+/g, '').split(','); - - // get all target filter component objects - targets.forEach(target => { - target = target.replace(/\s+/g, '').split(':')[0]; - const targetItemId = target.includes('filter-') ? target : `filter-${target}`; - targetCmps[target] = this.getFilterComponent(targetItemId); - - }); - // console.log('resetDependentFilters, gridId: ', gridId); - // console.log('resetDependentFilters, targetCmps', targetCmps); - - switch(gridId) { - case 'modx-grid-lexicon': - targets.forEach(target => { - target = target.replace(/\s+/g, '').split(':'); - const filterId = target[0], - filterDefault = target.length > 1 ? target[1] : null , - targetCurrentValue = targetCmps[filterId].getValue() - ; - - if (filterId == 'query') { - // probably do nothing ??? - } else { - // console.log('resetDependentFilters, callerDataIndex', callerDataIndex); - // console.log('resetDependentFilters, callerValue', callerValue); - /* - callerDataIndex is the data index of the filter whose value has changed, prompting - the update of this iteration's target (dependent filter) - */ - const targetStore = targetCmps[filterId].getStore(), - targetStoreLastOpts = targetStore.lastOptions - ; - switch(callerDataIndex) { - case 'ns': - // targetCmps[filterId].store.baseParams['namespace'] = callerValue; - // targetCmps[filterId].store.load(); - Ext.apply(targetStoreLastOpts.params, { - namespace: callerValue - }); - targetStore.reload(targetStoreLastOpts); - - if (filterId == 'language') { - const filterStore = targetCmps[filterId].getStore(), - currentValueFound = filterStore.findExact('name', targetCurrentValue) - ; - console.log(`${filterId} cmp: `, targetCmps[filterId]); - console.log(`${filterId} current val: `, targetCurrentValue); - console.log(`${filterId} default val: `, filterDefault); - console.log(`${filterId} current val found at: `, currentValueFound); - } - // targetCmps[filterId].setValue(filterDefault); - // targetCmps[filterId].store.load(); - break; - - case 'topic': - - break; - - case 'language': - // const targetCurrentValue = targetCmps[filterId].getValue(); - - targetCmps[filterId].store.baseParams['language'] = callerValue; - // console.log('resetDependentFilters, language > targetCmps:', targetCmps); - // console.log('resetDependentFilters, language > topic:', targetCmps[filterId]); - // console.log('resetDependentFilters, language > ns:', targetCmps['ns']); - - targetCmps[filterId].store.load(); - - const filterStore = targetCmps[filterId].getStore(), - currentValueFound = filterStore.findExact('name', targetCurrentValue) - ; - // console.log(`${targetCmps[filterId]} store: `, filterStore); - console.log(`${filterId} current val: `, targetCurrentValue); - console.log(`${filterId} default val: `, filterDefault); - console.log(`${filterId} current val found at: `, currentValueFound); - // Only reset to the default if the currently-selected topic is not found for this language - if (currentValueFound === -1) { - targetCmps[filterId].setValue(filterDefault); - targetCmps[filterId].store.load(); - } - break; - } - } - }); - - break; - } - store.load(); - } - - /** - * @property {Function} getQueryFilterField - Creates the query field component configuration - * - * @param {String} implementation - An optional identifier used to assign grid-specific behavior - * @param {Number} index - Optional explicitly-set tab index for this field - * @return {Object} - */ - ,getQueryFilterField: function(implementation = 'default', index = 1) { - // console.log('query fld implementation: ', implementation); - return { - xtype: 'textfield', - itemId: 'filter-query', - emptyText: _('search'), - value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', - // tabIndex: index, - cls: 'filter-query', - listeners: { - change: { - fn: function(cmp, newValue, oldValue) { - this.applyGridFilter(cmp); - if (implementation == 'user-group-users') { - // console.log('doing extra stuff for ug users grid...'); - const usergroupTree = Ext.getCmp('modx-tree-usergroup'), - selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), - groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) - ; - MODx.util.url.setParams({group: groupId}); - // console.log('ug tree: ', usergroupTree); - // console.log('ug tree selected: ', selectedNode); - // console.log('ugu query change, this: ', this); - } - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - } - } - - /** - * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration - * - * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared - * @param {Number} index - Optional explicitly-set tab index for this field - * @return {Object} - */ - ,getClearFiltersButton: function(filters = 'filter-query', index = 2) { - if (Ext.isEmpty(filters)) { - console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); - return {}; - } - return { - text: _('filter_clear'), - itemId: 'filter-clear', - // tabIndex: index, - listeners: { - click: { - fn: function(cmp) { - this.clearGridFilters(filters); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - } - } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 5df4a849f75..269c16273f2 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,25 +1,25 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config, { - enableTabScroll: true, - layoutOnTabChange: true, - plain: true, - deferredRender: true, - hideMode: 'offsets', - defaults: { - autoHeight: true, - hideMode: 'offsets', - border: true, - autoWidth: true, - bodyCssClass: 'tab-panel-wrapper' - }, - activeTab: 0, - border: false, - autoScroll: true, - autoHeight: true, - cls: 'modx-tabs' + Ext.applyIf(config,{ + enableTabScroll: true + ,layoutOnTabChange: true + ,plain: true + ,deferredRender: true + ,hideMode: 'offsets' + ,defaults: { + autoHeight: true + ,hideMode: 'offsets' + ,border: true + ,autoWidth: true + ,bodyCssClass: 'tab-panel-wrapper' + } + ,activeTab: 0 + ,border: false + ,autoScroll: true + ,autoHeight: true + ,cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this, config); + MODx.Tabs.superclass.constructor.call(this,config); this.config = config; this.on({ afterrender: function(tabPanel) { @@ -33,33 +33,24 @@ MODx.Tabs = function(config) { /* Placing listener here because we only want to listen after the initial panel has loaded */ tabPanel.on({ - beforetabchange: function(tabPanelCmp, newTab, currentTab) { + beforetabchange: function(tabPanel, newTab, currentTab) { /* Only proceed with the clearing process if the tab has changed. This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ - // console.log('request: ', MODx.request); - console.log('tabs, beforetabchange, currentTab (prev): ', currentTab.id); - console.log('tabs, beforetabchange, newTab: ', newTab?.id); - if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; - // console.log('tab changed...'); let itemsSource, gridObj = null ; if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs - ? currentTab.items - : currentTab.items.items[0].activeTab.items; + itemsSource = changedBetweenVtabs ? currentTab.items : currentTab.items.items[0].activeTab.items ; } else { itemsSource = currentTab.items; - // console.log('current tab:', currentTab); - // console.log('current tab items: ', itemsSource); } if (itemsSource.length > 0) { gridObj = this.findGridObject(itemsSource); @@ -76,16 +67,14 @@ MODx.Tabs = function(config) { const toolbar = gridObj.getTopToolbar(), filterIds = [] ; - // console.log('tabs, toolbar:', toolbar); if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype == 'textfield') && cmp.itemId) { filterIds.push(cmp.itemId); } }); } if (filterIds.length > 0) { - // console.log('clearing filters: ', filterIds); gridObj.clearGridFilters(filterIds); } } @@ -118,26 +107,26 @@ Ext.reg('modx-tabs', MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config, { - cls: 'vertical-tabs-panel', - headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, - bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, - defaults: { - bodyCssClass: 'vertical-tabs-body', - autoScroll: true, - autoHeight: true, - autoWidth: true, - layout: 'form' + Ext.applyIf(config,{ + cls: 'vertical-tabs-panel' + ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } + ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } + ,defaults: { + bodyCssClass: 'vertical-tabs-body' + ,autoScroll: true + ,autoHeight: true + ,autoWidth: true + ,layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this, config); + MODx.VerticalTabs.superclass.constructor.call(this,config); this.config = config; this.on('afterrender', function() { - if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { - const tabId = parseInt(MODx.request.vtab, 10); + if (MODx.request && MODx.request.hasOwnProperty('vtab')){ + const tabId = parseInt(MODx.request.vtab); this.setActiveTab(tabId); } }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs', MODx.VerticalTabs); +Ext.reg('modx-vtabs',MODx.VerticalTabs); From 302ec13e8af711ba3ed7d548bf3a247d8019b2b3 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 00:19:36 -0500 Subject: [PATCH 04/47] Base class updates required for grid enhancements Contains new required methods and tweaks for grid updates to follow --- manager/assets/modext/core/modx.js | 1 - manager/assets/modext/util/utilities.js | 130 ++++++++--- .../assets/modext/widgets/core/modx.grid.js | 220 +++++++++++++++--- .../assets/modext/widgets/core/modx.tabs.js | 85 ++++--- .../modext/widgets/core/tree/modx.tree.js | 3 +- 5 files changed, 326 insertions(+), 113 deletions(-) diff --git a/manager/assets/modext/core/modx.js b/manager/assets/modext/core/modx.js index 17204dcbe73..b8c36182b6a 100644 --- a/manager/assets/modext/core/modx.js +++ b/manager/assets/modext/core/modx.js @@ -551,7 +551,6 @@ Ext.extend(MODx,Ext.Component,{ }); tabPanel.add(newTabConfig); tabPanel.doLayout(); - tabPanel.setActiveTab(0); } } ,hiddenTabs: [] diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index 980127ffc02..74f6fd49d54 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -69,12 +69,12 @@ Ext.override(Ext.form.NumberField, { fixPrecision : function(value){ var nan = isNaN(value); if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){ - return nan ? '' : value; + return nan ? '' : value; } return this.allowDecimals && this.strictDecimalPrecision ? parseFloat(value).toFixed(this.decimalPrecision) : parseFloat(parseFloat(value).toFixed(this.decimalPrecision)) - ; + ; } }); @@ -234,23 +234,23 @@ Ext.form.getCheckboxMask = function(cbgroup) { Ext.form.BasicForm.prototype.append = function() { - var layout = new Ext.form.Layout(); - var fields = []; - layout.stack.push.apply(layout.stack, arguments); - for(var i = 0; i < arguments.length; i=i+1) { - if(arguments[i].isFormField) { - fields.push(arguments[i]); - } - } - layout.render(this.el); - - if(fields.length > 0) { - this.items.addAll(fields); - for(var f=0;f 0) { + this.items.addAll(fields); + for(var f=0;f', buf = ['
  • ', - '',this.indentMarkup,"", - elbowMarkup, - iconMarkup, - cb ? ('' : '/>')) : '', - '',renderer(a),"
    ", - '', - "
  • "].join(''); + '',this.indentMarkup,"", + elbowMarkup, + iconMarkup, + cb ? ('' : '/>')) : '', + '',renderer(a),"", + '', + ""].join(''); if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){ this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf); @@ -535,7 +535,7 @@ MODx.util.url = { * @param {Object} filterData - A set of filter parameter name/value pairs to be added to (or changed in) the URL * @param {Object} stateData - Optional data to be used in subsequent url processing */ - setParams: function(filterData, stateData = {}){ + setParams: function(filterData, stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { const url = new URL(window.location.href), params = url.searchParams @@ -548,12 +548,12 @@ MODx.util.url = { } }, /** - * @property {Function} clearParams - Clears all dynamically set url parameters, + * @property {Function} clearAllParams - Clears all dynamically set url parameters, * while preserving those in a pre-defined list. * * @param {Object} stateData - Optional data to be used in subsequent url processing */ - clearParams: function(stateData = {}) { + clearAllParams: function(stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { const preserve = ['a', 'id', 'key', 'namespace'], preserved = [], @@ -570,8 +570,62 @@ MODx.util.url = { newUrl = newUrl.toString().replace(/%2F/g, '/'); window.history.replaceState(stateData, document.title, newUrl); } + }, + /** + * @property {Function} clearParam - Clears a single url parameter; + * the param name is derived from the calling filter's component itemId. + * + * @param {Object|String} reference - The filter's Ext component or the parameter name + * @param {Boolean} referenceIsComponent - Set to true if deriving parameter from a filter's Ext component, false + * @param {Object} stateData - Optional data to be used in subsequent url processing + */ + clearParam: function(reference, referenceIsComponent = true, stateData = {}) { + if (typeof window.history.replaceState !== 'undefined') { + const urlParts = window.location.href.split('?'), + removeParamName = referenceIsComponent ? this.getParamNameFromCmp(reference) : reference.trim(), + regex = new RegExp(`${removeParamName}=[^&]+`, 'i') + ; + let params = urlParts[1].replace(regex, '').replace('&&', '&'); + if (params.endsWith('&')) { + params = params.substr(0, params.length - 1); + } + let newUrl = new URL(`${urlParts[0]}?${params}`); + newUrl = newUrl.toString().replace(/%2F/g, '/'); + window.history.replaceState(stateData, document.title, newUrl); + } + }, + /** + * @property {Function} getParamNameFromCmp - Parses a filter component's + * itemId to get the url parameter name, based on the following naming convention: + * itemId: 'filter-[filterName]-[optionalAdditionalIdentifiers]' + * + * @param {Object} cmp - The filter's Ext component + * @return {String} + */ + getParamNameFromCmp: function(cmp) { + const param = cmp.itemId.split('-')[1]; + return param === 'ns' ? 'namespace' : param ; + }, + /** + * @property {Function} decodeParamValue - Decodes a given parameter's value + * + * @param {String} value + * @return {String} + */ + decodeParamValue: function(value) { + value = value.replace(/\+/g, ' '); + return decodeURIComponent(value); } -} +}; + +/** + * Utility methods for tree objects + */ +MODx.util.tree = { + getGroupIdFromNode: function(node) { + return node.id ? node.id.split('_').pop() : 0 ; + } +}; Ext.util.Format.trimCommas = function(s) { s = s.replace(',,',','); @@ -699,12 +753,12 @@ Ext.namespace('Ext.ux.dd');Ext.ux.dd.GridDragDropRowOrder=Ext.extend(Ext.util.Ob /** selectability in Ext grids */ if (!Ext.grid.GridView.prototype.templates) { - Ext.grid.GridView.prototype.templates = {}; + Ext.grid.GridView.prototype.templates = {}; } Ext.grid.GridView.prototype.templates.cell = new Ext.Template( - '', - '
    {value}
    ', - '' + '', + '
    {value}
    ', + '' ); /* combocolumn */ @@ -762,8 +816,8 @@ Ext.Button.buttonTemplate = new Ext.Template( Ext.Button.buttonTemplate.compile(); Ext.TabPanel.prototype.itemTpl = new Ext.Template( - '
  • ', - '{text}
  • ' + '
  • ', + '{text}
  • ' ); Ext.TabPanel.prototype.itemTpl.disableFormats = true; Ext.TabPanel.prototype.itemTpl.compile(); @@ -782,7 +836,7 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { initComponent: function() { const me = this, ct = this.ownerCt - ; + ; if (typeof this.name === 'string' && this.name.length > 0) { this.aggregateSubmitField = new Ext.form.Hidden({ name: this.name diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index e754c5e1d8f..1e8cd0fa91a 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -143,8 +143,21 @@ MODx.grid.Grid = function(config) { this.on('afterAutoSave', this.onAfterAutoSave, this); } if (!config.preventRender) { this.render(); } - - this.on('rowcontextmenu',this._showMenu,this); + this.on({ + render: { + fn: function() { + const topToolbar = this.getTopToolbar(); + if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { + this.hasNestedFilters = true; + } + }, + scope: this + }, + rowcontextmenu: { + fn: this._showMenu, + scope: this + } + }); if (config.autosave) { this.on('afteredit',this.saveRecord,this); } @@ -787,9 +800,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ this.menu.record = record.data; this[actionHandler](record, recordIndex, e); - }, + } - actionContextMenu: function(record, recordIndex, e) { + ,actionContextMenu: function(record, recordIndex, e) { this._showMenu(this, recordIndex, e); } @@ -826,6 +839,35 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } + /** + * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel + * + * @param {Object} referenceCmp - A child component of the TabPanel we're looking for + * @return Ext.TabPanel + */ + ,findTabPanel: function(referenceCmp) { + if (!referenceCmp.hasOwnProperty('ownerCt')) { + console.error('MODx.grid.Grid::findTabPanel: This component must have an ownerCt to find its tab panel.'); + return false; + } + const container = referenceCmp.ownerCt, + isTabPanel = container.hasOwnProperty('xtype') && container.xtype.includes('tabs') + ; + if (isTabPanel) { + return container; + } + return this.findTabPanel(container); + } + + /** + * @property {Boolean} hasNestedFilters - Indicates whether the top toolbar filter(s) are nested + * within a secondary container; they will be nested when they have labels and those labels are + * positioned above the filter's input. + */ + ,hasNestedFilters: false + + ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language + /** * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) * @@ -837,42 +879,32 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, + tabPanel = this.findTabPanel(this), bottomToolbar = this.getBottomToolbar() ; - let tabPanel = this.ownerCt.ownerCt, - hasParentTabPanel = false, + let hasParentTabPanel = false, parentTabItems, activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { urlParams[param] = filterValue; + } else { + MODx.util.url.clearParam(cmp); } if (param == 'ns') { store.baseParams['namespace'] = filterValue; } else { store.baseParams[param] = filterValue; } - /* - If there is an additional container in the structure, - we need to search further for the tabs object... - NOTE: This may be able to be removed once all grid panels have been - updated and their structures have been made consistent with one another - */ - if (!tabPanel.hasOwnProperty('xtype') || !tabPanel.xtype.includes('tabs')) { - if (tabPanel.ownerCt && tabPanel.ownerCt.xtype && tabPanel.ownerCt.xtype.includes('tabs')) { - tabPanel = tabPanel.ownerCt; - } - } - // Make sure we've retrieved a tab panel before working on/with it - if (tabPanel && tabPanel.xtype.includes('tabs')) { + if (tabPanel) { /* Determine if this is a vertical tab panel; if so there will also be a horizontal parent tab panel that needs to be accounted for */ if (tabPanel.xtype == 'modx-vtabs') { - const parentTabPanel = tabPanel.ownerCt.ownerCt; - if (parentTabPanel && parentTabPanel.xtype.includes('tabs')) { + const parentTabPanel = this.findTabPanel(tabPanel); + if (parentTabPanel) { const activeParentTab = parentTabPanel.getActiveTab(); hasParentTabPanel = true; parentTabItems = parentTabPanel.items; @@ -907,32 +939,47 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value * - * @param {String} itemIds - A comma-separated list of the Ext component ids to be cleared + * @param {String|Array} items - A comma-separated list (or array) of items to be cleared. An optional default value + * may also be specified. The expected format for each item in the list is: + * 'filter-category', where 'filter-category' matches the Ext component's itemId and 'category' is the record index to filter on OR + * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) + * */ - ,clearGridFilters: function(itemIds) { + ,clearGridFilters: function(items) { const store = this.getStore(), - bottomToolbar = this.getBottomToolbar() + bottomToolbar = this.getBottomToolbar(), + data = Array.isArray(items) ? items : items.split(',') ; - itemIds = Array.isArray(itemIds) ? itemIds : itemIds.split(','); - /* - Note that param below relies on the following naming convention being followed for each filter's config: - itemId: 'filter-category', where 'category' is the record index to filter on - */ - itemIds.forEach(itemId => { - const id = itemId.trim(), - cmp = this.getFilterComponent(id) + data.forEach(item => { + itemData = item.replace(/\s+/g, '').split(':'); + const itemId = itemData[0], + itemDefaultVal = itemData.length == 2 ? itemData[1] : null , + cmp = this.getFilterComponent(itemId), + param = MODx.util.url.getParamNameFromCmp(cmp) ; - let param = id.split('-')[1]; - param = param == 'ns' ? 'namespace' : param ; if (cmp.xtype.includes('combo')) { - cmp.setValue(null); + cmp.setValue(itemDefaultVal); } else { cmp.setValue(''); } - store.baseParams[param] = ''; + if (!Ext.isEmpty(itemDefaultVal)) { + const paramsList = Object.keys(cmp.baseParams); + paramsList.forEach(param => { + switch(param) { + case 'namespace': + cmp.baseParams[param] = 'core'; + break; + case 'topic': + cmp.baseParams[param] = 'default'; + break; + } + }); + cmp.getStore().load(); + } + store.baseParams[param] = itemDefaultVal; }); store.load(); - MODx.util.url.clearParams(); + MODx.util.url.clearAllParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } @@ -1013,6 +1060,105 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.load(); } } + + /** + * @property {Function} getQueryFilterField - Creates the query field component configuration + * + * @param {String} filterId - Optional, itemId for the query filter; specify a unique id to avoid conflicts + * when multiple query fields may be present (e.g., when multiple tabs have a grid and query filter) + * @param {String} implementation - Optional, an identifier used to assign grid-specific behavior + * @return {Object} + */ + ,getQueryFilterField: function(filterId = 'filter-query', implementation = 'default') { + return { + xtype: 'textfield', + itemId: filterId.trim(), + emptyText: _('search'), + value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', + cls: 'filter-query', + listeners: { + change: { + fn: function(cmp, newValue, oldValue) { + this.applyGridFilter(cmp); + const usergroupTree = Ext.getCmp('modx-tree-usergroup') + if (implementation == 'user-group-users' && usergroupTree) { + /* + When the user group users grid is shown in the primary ACLs panel, + having the user groups tree along with a corresponding grid, the + group id must be fetched from the tree + */ + const selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), + groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) + ; + MODx.util.url.setParams({group: groupId}); + } + }, + scope: this + }, + afterrender: { + fn: function(cmp) { + if (MODx.request.query) { + this.applyGridFilter(cmp); + } + }, + scope: this + }, + render: { + fn: function(cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER, + fn: this.blur, + scope: cmp + }); + } + ,scope: this + } + } + } + } + + /** + * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration + * + * @param {String} filters - A comma-separated list of filter component ids (itemId) specifying those that should be cleared + * @param {String} dependentFilterResets - Optional, specification for reset of dependent filter stores to their pre-filtered state + * in the following format: 'filterItemId:relatedBaseParam, [filterItemId:relatedBaseParam,] ...' + * @return {Object} + */ + ,getClearFiltersButton: function(filters = 'filter-query', dependentFilterResets = null) { + if (Ext.isEmpty(filters)) { + console.error('MODx.grid.Grid::getClearFiltersButton: There was a problem creating the Clear Filter button because the supplied filters list is invalid.'); + return {}; + } + const config = { + text: _('filter_clear'), + itemId: 'filter-clear', + listeners: { + click: { + fn: function(cmp) { + if (cmp.dependentResets) { + const resets = cmp.dependentResets.split(','); + resets.forEach(reset => { + const [filterId, filterDataIndex] = reset.split(':').map(item => item.trim()); + this.updateDependentFilter(filterId, filterDataIndex, '', true); + }); + } + this.clearGridFilters(filters); + }, + scope: this + }, + mouseout: { + fn: function(evt) { + this.removeClass('x-btn-focus'); + } + } + } + } + if (dependentFilterResets) { + config.dependentResets = dependentFilterResets; + } + return config; + } }); /* local grid */ diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 269c16273f2..72f65e397cc 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,27 +1,37 @@ MODx.Tabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - enableTabScroll: true - ,layoutOnTabChange: true - ,plain: true - ,deferredRender: true - ,hideMode: 'offsets' - ,defaults: { - autoHeight: true - ,hideMode: 'offsets' - ,border: true - ,autoWidth: true - ,bodyCssClass: 'tab-panel-wrapper' - } - ,activeTab: 0 - ,border: false - ,autoScroll: true - ,autoHeight: true - ,cls: 'modx-tabs' + Ext.applyIf(config, { + enableTabScroll: true, + layoutOnTabChange: true, + plain: true, + deferredRender: true, + hideMode: 'offsets', + defaults: { + autoHeight: true, + hideMode: 'offsets', + border: true, + autoWidth: true, + bodyCssClass: 'tab-panel-wrapper' + }, + activeTab: 0, + border: false, + autoScroll: true, + autoHeight: true, + cls: 'modx-tabs' }); - MODx.Tabs.superclass.constructor.call(this,config); + MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ + tabchange: function(tabPanel, tab) { + /* + In certain scenarios, such as when form customization and/or a plugin adds a tab, + the state of the Resource tab panel can become uncertain and no tab will be initially + selected. This workaround ensures the first tab is selected. + */ + if (this.id === 'modx-resource-tabs' && MODx.request.tab === undefined && !this.getActiveTab()) { + this.setActiveTab(0); + } + }, afterrender: function(tabPanel) { if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { const tabId = parseInt(MODx.request.tab, 10); @@ -33,13 +43,14 @@ MODx.Tabs = function(config) { /* Placing listener here because we only want to listen after the initial panel has loaded */ tabPanel.on({ - beforetabchange: function(tabPanel, newTab, currentTab) { + beforetabchange: function(tabPanelCmp, newTab, currentTab) { /* Only proceed with the clearing process if the tab has changed. This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ + if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' @@ -48,7 +59,9 @@ MODx.Tabs = function(config) { gridObj = null ; if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs ? currentTab.items : currentTab.items.items[0].activeTab.items ; + itemsSource = changedBetweenVtabs + ? currentTab.items + : currentTab.items.items[0].activeTab.items; } else { itemsSource = currentTab.items; } @@ -69,7 +82,7 @@ MODx.Tabs = function(config) { ; if (toolbar && toolbar.items.items.length > 0) { toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype == 'textfield') && cmp.itemId) { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { filterIds.push(cmp.itemId); } }); @@ -107,26 +120,26 @@ Ext.reg('modx-tabs', MODx.Tabs); MODx.VerticalTabs = function(config) { config = config || {}; - Ext.applyIf(config,{ - cls: 'vertical-tabs-panel' - ,headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' } - ,bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' } - ,defaults: { - bodyCssClass: 'vertical-tabs-body' - ,autoScroll: true - ,autoHeight: true - ,autoWidth: true - ,layout: 'form' + Ext.applyIf(config, { + cls: 'vertical-tabs-panel', + headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, + bwrapCfg: { tag: 'div', cls: 'x-tab-panel-bwrap vertical-tabs-bwrap' }, + defaults: { + bodyCssClass: 'vertical-tabs-body', + autoScroll: true, + autoHeight: true, + autoWidth: true, + layout: 'form' } }); - MODx.VerticalTabs.superclass.constructor.call(this,config); + MODx.VerticalTabs.superclass.constructor.call(this, config); this.config = config; this.on('afterrender', function() { - if (MODx.request && MODx.request.hasOwnProperty('vtab')){ - const tabId = parseInt(MODx.request.vtab); + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { + const tabId = parseInt(MODx.request.vtab, 10); this.setActiveTab(tabId); } }); }; Ext.extend(MODx.VerticalTabs, MODx.Tabs); -Ext.reg('modx-vtabs',MODx.VerticalTabs); +Ext.reg('modx-vtabs', MODx.VerticalTabs); diff --git a/manager/assets/modext/widgets/core/tree/modx.tree.js b/manager/assets/modext/widgets/core/tree/modx.tree.js index 1a50c69cf27..6501a6a496d 100644 --- a/manager/assets/modext/widgets/core/tree/modx.tree.js +++ b/manager/assets/modext/widgets/core/tree/modx.tree.js @@ -193,7 +193,7 @@ Ext.extend(MODx.tree.Tree,Ext.tree.TreePanel,{ ,scope: this }; MODx.tree.Tree.superclass.constructor.call(this,config); - this.addEvents('afterSort','beforeSort'); + this.addEvents('afterSort','beforeSort','refresh'); this.cm = new Ext.menu.Menu(config.menuConfig); this.on('contextmenu',this._showContextMenu,this); this.on('beforenodedrop',this._handleDrop,this); @@ -378,6 +378,7 @@ Ext.extend(MODx.tree.Tree,Ext.tree.TreePanel,{ ,refresh: function (func,scope,args) { var treeState = Ext.state.Manager.get(this.treestate_id); this.root.reload(); + this.fireEvent('refresh', {}); if (treeState === undefined) { this.root.expand(); } else { From 2604a771e8352d1421103cba5ea67e46cfaf7925 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 00:27:44 -0500 Subject: [PATCH 05/47] Filter Persistence: Policies and Policy Templates --- .../Security/Access/Policy/GetList.php | 3 +- .../Access/Policy/Template/GetList.php | 2 + .../security/modx.grid.access.policy.js | 118 +++++++----------- .../modx.grid.access.policy.template.js | 107 ++++++---------- 4 files changed, 84 insertions(+), 146 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php index 8fc4c9a1d6b..94c420f6b5b 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php @@ -84,7 +84,8 @@ public function prepareQueryBeforeCount(xPDOQuery $c) if (!empty($query)) { $c->where([ 'modAccessPolicy.name:LIKE' => '%' . $query . '%', - 'OR:modAccessPolicy.description:LIKE' => '%' . $query . '%' + 'OR:modAccessPolicy.description:LIKE' => '%' . $query . '%', + 'OR:Template.name:LIKE' => '%' . $query . '%' ]); } diff --git a/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php index c00c0c75174..877a7b5f32f 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php @@ -1,4 +1,5 @@ where([ 'modAccessPolicyTemplate.name:LIKE' => '%' . $query . '%', 'OR:modAccessPolicyTemplate.description:LIKE' => '%' . $query . '%', + 'OR:TemplateGroup.name:LIKE' => '%' . $query . '%' ]); } diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.js index 1eb7583f58f..16fe5c2899e 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.js @@ -6,12 +6,11 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-access-policies */ -MODx.panel.AccessPolicies = function(config) { - config = config || {}; +MODx.panel.AccessPolicies = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-access-policies' ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } + ,defaults: { collapsible: false, autoHeight: true } ,items: [{ html: _('policies') ,id: 'modx-policies-header' @@ -41,8 +40,7 @@ Ext.reg('modx-panel-access-policies',MODx.panel.AccessPolicies); * @param {Object} config An object of options. * @xtype modx-grid-access-policy */ -MODx.grid.AccessPolicy = function(config) { - config = config || {}; +MODx.grid.AccessPolicy = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy' @@ -50,7 +48,21 @@ MODx.grid.AccessPolicy = function(config) { ,baseParams: { action: 'Security/Access/Policy/GetList' } - ,fields: ['id','name','description', 'description_trans', 'class','data','parent','template','template_name','active_permissions','total_permissions','active_of','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'description_trans', + 'class', + 'data', + 'parent', + 'template', + 'template_name', + 'active_permissions', + 'total_permissions', + 'active_of', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/Access/Policy/UpdateFromGrid' @@ -91,73 +103,33 @@ MODx.grid.AccessPolicy = function(config) { ,width: 100 ,editable: false }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,scope: this - ,handler: this.createPolicy - },{ - text: _('import') - ,scope: this - ,handler: this.importPolicy - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-policy-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-sacpol-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,handler: this.createPolicy + },{ + text: _('import') + ,scope: this + ,handler: this.importPolicy + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-policy'), + this.getClearFiltersButton() + ] }); MODx.grid.AccessPolicy.superclass.constructor.call(this,config); }; Ext.extend(MODx.grid.AccessPolicy,MODx.grid.Grid,{ - search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Access/Policy/GetList' - }; - Ext.getCmp('modx-policy-search').reset(); - this.getBottomToolbar().changePage(1); - } - - ,editPolicy: function(itm,e) { + editPolicy: function(itm,e) { MODx.loadPage('security/access/policy/update', 'id='+this.menu.record.id); } @@ -178,6 +150,7 @@ Ext.extend(MODx.grid.AccessPolicy,MODx.grid.Grid,{ this.windows.apc.reset(); this.windows.apc.show(e.target); } + ,exportPolicy: function(btn,e) { var id = this.menu.record.id; MODx.Ajax.request({ @@ -284,8 +257,7 @@ Ext.reg('modx-grid-access-policy',MODx.grid.AccessPolicy); * @param {Object} config An object of options. * @xtype modx-window-access-policy-create */ -MODx.window.CreateAccessPolicy = function(config) { - config = config || {}; +MODx.window.CreateAccessPolicy = function(config = {}) { this.ident = config.ident || 'cacp'+Ext.id(); Ext.applyIf(config,{ title: _('create') @@ -352,8 +324,7 @@ Ext.reg('modx-window-access-policy-create',MODx.window.CreateAccessPolicy); * @param {Object} config An object of options. * @xtype modx-combo-access-policy-template */ -MODx.combo.AccessPolicyTemplate = function(config) { - config = config || {}; +MODx.combo.AccessPolicyTemplate = function(config = {}) { Ext.applyIf(config,{ name: 'template' ,hiddenName: 'template' @@ -381,8 +352,7 @@ Ext.reg('modx-combo-access-policy-template',MODx.combo.AccessPolicyTemplate); * @param {Object} config An object of options. * @xtype modx-window-policy-import */ -MODx.window.ImportPolicy = function(config) { - config = config || {}; +MODx.window.ImportPolicy = function(config = {}) { this.ident = config.ident || 'imppol-'+Ext.id(); Ext.applyIf(config,{ title: _('import') diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js index f80b7be2e94..190034c52a0 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-access-policy-templates */ -MODx.panel.AccessPolicyTemplates = function(config) { - config = config || {}; +MODx.panel.AccessPolicyTemplates = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-access-policy-templates' ,bodyStyle: '' @@ -41,8 +40,7 @@ Ext.reg('modx-panel-access-policy-templates',MODx.panel.AccessPolicyTemplates); * @param {Object} config An object of options. * @xtype modx-grid-access-policy */ -MODx.grid.AccessPolicyTemplate = function(config) { - config = config || {}; +MODx.grid.AccessPolicyTemplate = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy-template' @@ -50,7 +48,17 @@ MODx.grid.AccessPolicyTemplate = function(config) { ,baseParams: { action: 'Security/Access/Policy/Template/GetList' } - ,fields: ['id','name','description','description_trans','template_group','template_group_name','total_permissions','policy_count','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'description_trans', + 'template_group', + 'template_group_name', + 'total_permissions', + 'policy_count', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/Access/Policy/Template/UpdateFromGrid' @@ -94,54 +102,28 @@ MODx.grid.AccessPolicyTemplate = function(config) { ,editable: false ,sortable: true }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,scope: this - ,handler: this.createPolicyTemplate - },{ - text: _('import') - ,scope: this - ,handler: this.importPolicyTemplate - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-policy-template-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-sacpoltemp-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,handler: this.createPolicyTemplate + },{ + text: _('import') + ,scope: this + ,handler: this.importPolicyTemplate + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-policy-template'), + this.getClearFiltersButton() + ] }); MODx.grid.AccessPolicyTemplate.superclass.constructor.call(this,config); }; @@ -296,21 +278,6 @@ Ext.extend(MODx.grid.AccessPolicyTemplate,MODx.grid.Grid,{ } }); } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Access/Policy/Template/GetList' - }; - Ext.getCmp('modx-policy-template-search').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); @@ -322,8 +289,7 @@ Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); * @param {Object} config An object of options. * @xtype modx-window-access-policy-create */ -MODx.window.CreateAccessPolicyTemplate = function(config) { - config = config || {}; +MODx.window.CreateAccessPolicyTemplate = function(config = {}) { this.ident = config.ident || 'cacpt'+Ext.id(); Ext.applyIf(config,{ title: _('create') @@ -378,8 +344,7 @@ Ext.reg('modx-window-access-policy-template-create',MODx.window.CreateAccessPoli * @param {Object} config An object of options. * @xtype modx-window-policy-template-import */ -MODx.window.ImportPolicyTemplate = function(config) { - config = config || {}; +MODx.window.ImportPolicyTemplate = function(config = {}) { this.ident = config.ident || 'imppt-'+Ext.id(); Ext.applyIf(config,{ title: _('import') From 06af747b5fa0d6e2240d56c25a8711122f6d272a Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:52:37 -0500 Subject: [PATCH 06/47] Filter Persistence: Deleted Resources Manager (Trash) Also applied refinement to combo filter (showing only relevant Contexts) and fixed query so only deleted Resources are returned (fix from Ruslan's closed PR). --- core/lexicon/en/default.inc.php | 3 +- .../Revolution/Processors/Context/GetList.php | 59 ++++--- .../Processors/Resource/Trash/GetList.php | 13 ++ .../widgets/resource/modx.grid.trash.js | 148 +++++++----------- 4 files changed, 113 insertions(+), 110 deletions(-) diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php index 0f4ea099721..fce1dd78c23 100644 --- a/core/lexicon/en/default.inc.php +++ b/core/lexicon/en/default.inc.php @@ -208,6 +208,7 @@ $_lang['filter_clear'] = 'Clear Filter'; $_lang['filter_by_key'] = 'Filter by Key...'; $_lang['filter_by_name'] = 'Filter by name...'; +$_lang['filter_by_event_group'] = 'Filter by event group...'; $_lang['filter_by_username'] = 'Filter by user name...'; $_lang['finish'] = 'Finish'; $_lang['folder'] = 'Folder'; @@ -314,7 +315,7 @@ $_lang['orm_container_rename'] = 'Rename Container'; $_lang['orm_container_remove'] = 'Delete Container'; $_lang['orm_container_remove_confirm'] = 'Are you sure you want to delete this container and all attributes below it? This is irreversible.'; -$_lang['pagetitle'] = 'Resource\'s title'; +$_lang['pagetitle'] = 'Resource‘s Title'; $_lang['page_title'] = 'Resource Title'; $_lang['parameter'] = 'Parameter'; $_lang['parameters'] = 'Parameters'; diff --git a/core/src/Revolution/Processors/Context/GetList.php b/core/src/Revolution/Processors/Context/GetList.php index 84d96221df0..1fbc0c97249 100644 --- a/core/src/Revolution/Processors/Context/GetList.php +++ b/core/src/Revolution/Processors/Context/GetList.php @@ -13,6 +13,7 @@ use MODX\Revolution\modContext; use MODX\Revolution\modAccessContext; +use MODX\Revolution\modResource; use MODX\Revolution\modUserGroup; use MODX\Revolution\Processors\Model\GetListProcessor; use xPDO\Om\xPDOObject; @@ -53,7 +54,7 @@ public function initialize() { $initialized = parent::initialize(); $this->setDefaultProperties([ - 'search' => '', + 'query' => '', 'exclude' => '', ]); $this->canCreate = $this->modx->hasPermission('new_context'); @@ -71,11 +72,11 @@ public function initialize() */ public function prepareQueryBeforeCount(xPDOQuery $c) { - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $c->where([ - 'key:LIKE' => '%' . $search . '%', - 'OR:description:LIKE' => '%' . $search . '%', + 'key:LIKE' => '%' . $query . '%', + 'OR:description:LIKE' => '%' . $query . '%', ]); } $exclude = $this->getProperty('exclude'); @@ -89,21 +90,39 @@ public function prepareQueryBeforeCount(xPDOQuery $c) limit results to only those contexts present in the current grid. */ if ($this->isGridFilter) { - if ($userGroup = $this->getProperty('usergroup', false)) { - $c->innerJoin( - modAccessContext::class, - 'modAccessContext', - [ - '`modAccessContext`.`target` = `modContext`.`key`', - '`modAccessContext`.`principal` = ' . (int)$userGroup, - '`modAccessContext`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) - ] - ); - if ($policy = $this->getProperty('policy', false)) { - $c->where([ - '`modAccessContext`.`policy`' => (int)$policy - ]); - } + $targetGrid = $this->getProperty('targetGrid', ''); + switch ($targetGrid) { + case 'MODx.grid.UserGroupContext': + if ($userGroup = $this->getProperty('usergroup', false)) { + $c->innerJoin( + modAccessContext::class, + 'modAccessContext', + [ + '`modAccessContext`.`target` = `modContext`.`key`', + '`modAccessContext`.`principal` = ' . (int)$userGroup, + '`modAccessContext`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) + ] + ); + if ($policy = $this->getProperty('policy', false)) { + $c->where([ + '`modAccessContext`.`policy`' => (int)$policy + ]); + } + } + break; + + case 'MODx.grid.Trash': + $c->innerJoin( + modResource::class, + 'modResource', + [ + '`modResource`.`context_key` = `modContext`.`key`', + '`modResource`.`deleted` = 1' + ] + ); + break; + + // no default case } } return $c; diff --git a/core/src/Revolution/Processors/Resource/Trash/GetList.php b/core/src/Revolution/Processors/Resource/Trash/GetList.php index 3755895ddb9..b53838d3da7 100644 --- a/core/src/Revolution/Processors/Resource/Trash/GetList.php +++ b/core/src/Revolution/Processors/Resource/Trash/GetList.php @@ -64,6 +64,19 @@ public function prepareQueryBeforeCount(xPDOQuery $c) $c->leftJoin(modUser::class, 'User', 'modResource.deletedby = User.id'); $c->leftJoin(modContext::class, 'Context', 'modResource.context_key = Context.key'); + /* + TODO: + Add only resources if we have the save permission here (on the context!!) + we need the following permissions: + undelete_document - to restore the document + delete_document - that's perhaps not necessary, because all documents are already deleted + but we need the purge_deleted permission - for every single file + */ + if ($deleted = $this->getDeleted()) { + $c->where(['modResource.id:IN' => $deleted]); + } else { + $c->where(['modResource.id:IN' => 0]); + } if (!empty($query)) { $c->where([ 'modResource.pagetitle:LIKE' => '%' . $query . '%', diff --git a/manager/assets/modext/widgets/resource/modx.grid.trash.js b/manager/assets/modext/widgets/resource/modx.grid.trash.js index 38cef48fd59..ad50e5e8085 100644 --- a/manager/assets/modext/widgets/resource/modx.grid.trash.js +++ b/manager/assets/modext/widgets/resource/modx.grid.trash.js @@ -1,12 +1,10 @@ -MODx.grid.Trash = function (config) { - config = config || {}; - +MODx.grid.Trash = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config, { url: MODx.config.connector_url, baseParams: { - action: 'Resource/Trash/GetList' + action: 'Resource/Trash/GetList', + context: MODx.request.context || null }, fields: [ 'id', @@ -59,72 +57,68 @@ MODx.grid.Trash = function (config) { dataIndex: 'deletedby', width: 75, sortable: true, - renderer: function (value, metaData, record) { + renderer: function(value, metaData, record) { return record.data.deletedby_name; } }], - tbar: [{ - text: _('bulk_actions'), - menu: [{ - text: _('trash.selected_purge'), - handler: this.purgeSelected, - scope: this + tbar: [ + { + text: _('bulk_actions'), + menu: [{ + text: _('trash.selected_purge'), + handler: this.purgeSelected, + scope: this + }, { + text: _('trash.selected_restore'), + handler: this.restoreSelected, + scope: this + }] }, { - text: _('trash.selected_restore'), - handler: this.restoreSelected, - scope: this - }] - }, { - xtype: 'button', - text: _('trash.purge_all'), - id: 'modx-purge-all', - cls: 'x-btn-purge-all', - listeners: { - 'click': {fn: this.purgeAll, scope: this} - } - }, { - xtype: 'button', - text: _('trash.restore_all'), - id: 'modx-restore-all', - cls: 'x-btn-restore-all', - listeners: { - 'click': {fn: this.restoreAll, scope: this} - } - }, '->', { - xtype: 'modx-combo-context', - id: 'modx-trash-context', - emptyText: _('context'), - exclude: 'mgr', - listeners: { - 'select': {fn: this.searchContext, scope: this} - } - },{ - xtype: 'textfield', - id: 'modx-trash-search', - cls: 'x-form-filter', - emptyText: _('search'), - listeners: { - 'change': {fn: this.search, scope: this}, - 'render': { - fn: function (cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - }, scope: this + text: _('trash.purge_all'), + id: 'modx-purge-all', + cls: 'x-btn-purge-all', + listeners: { + click: { + fn: this.purgeAll, + scope: this + } } - } - }, { - xtype: 'button', - text: _('filter_clear'), - id: 'modx-filter-clear', - cls: 'x-form-filter-clear', - listeners: { - 'click': {fn: this.clearFilter, scope: this} - } - }] + }, { + text: _('trash.restore_all'), + id: 'modx-restore-all', + cls: 'x-btn-restore-all', + listeners: { + click: { + fn: this.restoreAll, + scope: this + } + } + }, + '->', + { + xtype: 'modx-combo-context', + itemId: 'filter-context', + emptyText: _('context'), + value: MODx.request.context || null, + baseParams: { + action: 'Context/GetList', + exclude: 'mgr', + isGridFilter: true, + targetGrid: 'MODx.grid.Trash' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'context'); + }, + scope: this + } + } + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-context, filter-query') + ] }); MODx.grid.Trash.superclass.constructor.call(this, config); @@ -167,30 +161,6 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { } }, - search: function (tf, newValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - }, - - searchContext: function (tf) { - this.getStore().baseParams.context = !Ext.isEmpty(tf) ? tf.value : ''; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - }, - - clearFilter: function () { - this.getStore().baseParams.query = ''; - this.getStore().baseParams.context = ''; - Ext.getCmp('modx-trash-search').reset(); - Ext.getCmp('modx-trash-context').reset(); - this.getBottomToolbar().changePage(1); - this.refresh(); - }, - purgeResource: function () { MODx.msg.confirm({ minWidth: 500, From 265e845ac1ec7ea980b7e74df1aec001e4471080 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:57:59 -0500 Subject: [PATCH 07/47] Update user group permissions, category grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.category.js | 57 ++++++------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index cb4a86edb95..a21d6ad2808 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -4,8 +4,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-user-group-categories */ -MODx.grid.UserGroupCategory = function(config) { - config = config || {}; +MODx.grid.UserGroupCategory = function(config = {}) { this.exp = new Ext.grid.RowExpander({ tpl: new Ext.Template('

    {permissions}

    '), lazyRender: false, @@ -22,16 +21,16 @@ MODx.grid.UserGroupCategory = function(config) { } ,fields: [ 'id', - 'target', - 'name', - 'principal', - 'authority', - 'authority_name', - 'policy', - 'policy_name', - 'context_key', - 'permissions', - 'cls' + 'target', + 'name', + 'principal', + 'authority', + 'authority_name', + 'policy', + 'policy_name', + 'context_key', + 'permissions', + 'cls' ] ,paging: true ,hideMode: 'offsets' @@ -127,28 +126,11 @@ MODx.grid.UserGroupCategory = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-category', 'category', '', true); - this.updateDependentFilter('filter-category', 'policy', '', true); - this.clearGridFilters('filter-category, filter-policy-category'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-category, filter-policy-category'); + }, + this.getClearFiltersButton( + 'filter-category, filter-policy-category', + 'filter-policy-category:category, filter-category:policy' + ) ] }); MODx.grid.UserGroupCategory.superclass.constructor.call(this,config); @@ -257,8 +239,7 @@ Ext.reg('modx-grid-user-group-category',MODx.grid.UserGroupCategory); * @param {Object} config An object of options. * @xtype modx-window-user-group-category-create */ -MODx.window.CreateUGCat = function(config) { - config = config || {}; +MODx.window.CreateUGCat = function(config = {}) { this.ident = config.ident || 'cugcat'+Ext.id(); Ext.applyIf(config,{ title: _('category_add') @@ -388,10 +369,8 @@ Ext.reg('modx-window-user-group-category-create',MODx.window.CreateUGCat); * @param {Object} config An object of options. * @xtype modx-window-user-group-category-update */ -MODx.window.UpdateUGCat = function(config) { - config = config || {}; +MODx.window.UpdateUGCat = function(config = {}) { this.ident = config.ident || 'updugcat'+Ext.id(); - Ext.applyIf(config,{ title: _('access_category_update') ,action: 'Security/Access/UserGroup/Category/Update' From b7df565203e3b54dacb03715d68f77be2fe53fa0 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:59:00 -0500 Subject: [PATCH 08/47] Update user group permissions, contexts grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.context.js | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js index 62cc7e60328..9adffa56c0b 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js @@ -87,6 +87,7 @@ MODx.grid.UserGroupContext = function(config) { ,baseParams: { action: 'Context/GetList', isGridFilter: true, + targetGrid: 'MODx.grid.UserGroupContext', usergroup: config.usergroup } ,listeners: { @@ -121,28 +122,11 @@ MODx.grid.UserGroupContext = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-context', 'context', '', true); - this.updateDependentFilter('filter-context', 'policy', '', true); - this.clearGridFilters('filter-context, filter-policy-context'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-context, filter-policy-context'); + }, + this.getClearFiltersButton( + 'filter-context, filter-policy-context', + 'filter-policy-context:context, filter-context:policy' + ) ] }); MODx.grid.UserGroupContext.superclass.constructor.call(this,config); From 62203a7aafe4141751a79cd4ddfaa21a22cd4528 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 15:59:32 -0500 Subject: [PATCH 09/47] Update user group permissions, namespace grid Applies new shared method to create the clear filters button --- .../modx.grid.user.group.namespace.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index a349c91bcb9..1d086dc2c24 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -126,28 +126,11 @@ MODx.grid.UserGroupNamespace = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-namespace', 'namespace', '', true); - this.updateDependentFilter('filter-namespace', 'policy', '', true); - this.clearGridFilters('filter-namespace, filter-policy-namespace'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-namespace, filter-policy-namespace'); + }, + this.getClearFiltersButton( + 'filter-namespace, filter-policy-namespace', + 'filter-policy-namespace:namespace, filter-namespace:policy' + ) ] }); MODx.grid.UserGroupNamespace.superclass.constructor.call(this,config); From df21190a3176010a8b9b19e6c14f77b4ed7d4699 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:00:33 -0500 Subject: [PATCH 10/47] Update user group permissions, resource groups grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.resource.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js index e6fb3a989fd..788048fbb23 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js @@ -129,28 +129,11 @@ MODx.grid.UserGroupResourceGroup = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-resourceGroup', 'resourceGroup', '', true); - this.updateDependentFilter('filter-resourceGroup', 'policy', '', true); - this.clearGridFilters('filter-resourceGroup, filter-policy-resourceGroup'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-resourceGroup, filter-policy-resourceGroup'); + }, + this.getClearFiltersButton( + 'filter-resourceGroup, filter-policy-resourceGroup', + 'filter-policy-resourceGroup:resourceGroup, filter-resourceGroup:policy' + ) ] }); MODx.grid.UserGroupResourceGroup.superclass.constructor.call(this,config); From dbd98a6b429c74ebac0567358b62e0ccfdcd891b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:01:10 -0500 Subject: [PATCH 11/47] Update user group permissions, media sources grid Applies new shared method to create the clear filters button --- .../security/modx.grid.user.group.source.js | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js index 4dd74a2d90a..46efb9bdcdc 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js @@ -122,28 +122,11 @@ MODx.grid.UserGroupSource = function(config) { scope: this } } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.updateDependentFilter('filter-policy-source', 'source', '', true); - this.updateDependentFilter('filter-source', 'policy', '', true); - this.clearGridFilters('filter-source, filter-policy-source'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - ,scope: this - } - // TBD - have to refactor getClearFiltersButton to take updateDependentFilter into account - // this.getClearFiltersButton('filter-source, filter-policy-source'); + }, + this.getClearFiltersButton( + 'filter-source, filter-policy-source', + 'filter-policy-source:source, filter-source:policy' + ) ] }); MODx.grid.UserGroupSource.superclass.constructor.call(this,config); From 1131e9c81b92884029dfdc3fa03158ba42fc2b46 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 16:06:30 -0500 Subject: [PATCH 12/47] Filter Persistence: Plugin Events Grid --- .../Element/Plugin/Event/GetList.php | 9 +- .../widgets/element/modx.grid.plugin.event.js | 102 ++++++++---------- 2 files changed, 43 insertions(+), 68 deletions(-) diff --git a/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php b/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php index 872175ce02d..a78b87a8a34 100644 --- a/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php +++ b/core/src/Revolution/Processors/Element/Plugin/Event/GetList.php @@ -1,4 +1,5 @@ toArray(); $eventArray['enabled'] = $event->get('enabled') ? 1 : 0; - - $eventArray['menu'] = [ - [ - 'text' => $this->modx->lexicon('edit'), - 'handler' => 'this.updateEvent', - ], - ]; $list[] = $eventArray; } diff --git a/manager/assets/modext/widgets/element/modx.grid.plugin.event.js b/manager/assets/modext/widgets/element/modx.grid.plugin.event.js index 9dcae1e9ccb..af3de76eaef 100644 --- a/manager/assets/modext/widgets/element/modx.grid.plugin.event.js +++ b/manager/assets/modext/widgets/element/modx.grid.plugin.event.js @@ -6,10 +6,9 @@ * @param {Object} config An object of options. * @xtype modx-grid-plugin-event */ -MODx.grid.PluginEvent = function(config) { - config = config || {}; +MODx.grid.PluginEvent = function(config = {}) { this.ident = config.ident || 'grid-pluge'+Ext.id(); - var ec = new Ext.ux.grid.CheckColumn({ + const ec = new Ext.ux.grid.CheckColumn({ header: _('enabled') ,dataIndex: 'enabled' ,editable: true @@ -23,6 +22,7 @@ MODx.grid.PluginEvent = function(config) { ,baseParams: { action: 'Element/Plugin/Event/GetList' ,plugin: config.plugin + ,group: MODx.request.group ? MODx.util.url.decodeParamValue(MODx.request.group) : null ,limit: 0 } ,saveParams: { @@ -31,7 +31,14 @@ MODx.grid.PluginEvent = function(config) { ,enableColumnResize: true ,enableColumnMove: true ,primaryKey: 'name' - ,fields: ['name','service','groupname','enabled','priority','propertyset','menu'] + ,fields: [ + 'name', + 'service', + 'groupname', + 'enabled', + 'priority', + 'propertyset' + ] ,paging: false ,pageSize: 0 ,remoteSort: false @@ -76,42 +83,26 @@ MODx.grid.PluginEvent = function(config) { ,editor: { xtype: 'textfield' ,allowBlank: false } ,sortable: true }] - ,tbar: ['->',{ - xtype: 'modx-combo-eventgroup' - ,name: 'group' - ,id: 'modx-plugin-event-filter-group' - ,itemId: 'group' - ,emptyText: _('group')+'...' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterGroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-plugin-event-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': {fn: function () {this.removeClass('x-btn-focus')}} - } - }] + ,tbar: [ + '->', + { + xtype: 'modx-combo-eventgroup' + ,itemId: 'filter-group' + ,emptyText: _('filter_by_event_group') + ,width: 200 + ,value: MODx.request.group ? MODx.util.url.decodeParamValue(MODx.request.group) : null + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'group'); + }, + scope: this + } + } + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-group, filter-query') + ] }); MODx.grid.PluginEvent.superclass.constructor.call(this,config); @@ -121,26 +112,17 @@ MODx.grid.PluginEvent = function(config) { }; this.addEvents('updateEvent'); }; -Ext.extend(MODx.grid.PluginEvent,MODx.grid.Grid,{ - search: function(tf,newValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getStore().load(); - return true; - } - ,filterGroup: function (cb,nv,ov) { - this.getStore().baseParams.group = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getStore().load(); - return true; - } - ,clearFilter: function() { - delete this.getStore().baseParams.query; - delete this.getStore().baseParams.group; - Ext.getCmp('modx-plugin-event-search').reset(); - Ext.getCmp('modx-plugin-event-filter-group').reset(); - this.getStore().load(); - } - ,updateEvent: function(btn,e) { +Ext.extend(MODx.grid.PluginEvent, MODx.grid.Grid, { + getMenu: function() { + const menu = []; + menu.push({ + text: _('edit'), + handler: this.updateEvent + }); + this.addContextMenuItem(menu); + }, + + updateEvent: function(btn,e) { var r = this.menu.record; if (!this.windows.peu) { this.windows.peu = MODx.load({ From b9c913155640b909c92feafba7f5e0caa740686b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:06:52 -0500 Subject: [PATCH 13/47] Move resource panel-specific tabchange listener Since the fix is only relevant to one panel, moved it out of the Tabs base class --- manager/assets/modext/widgets/core/modx.tabs.js | 10 ---------- .../modext/widgets/resource/modx.panel.resource.js | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 72f65e397cc..c8f1ee1a1a4 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -22,16 +22,6 @@ MODx.Tabs = function(config) { MODx.Tabs.superclass.constructor.call(this, config); this.config = config; this.on({ - tabchange: function(tabPanel, tab) { - /* - In certain scenarios, such as when form customization and/or a plugin adds a tab, - the state of the Resource tab panel can become uncertain and no tab will be initially - selected. This workaround ensures the first tab is selected. - */ - if (this.id === 'modx-resource-tabs' && MODx.request.tab === undefined && !this.getActiveTab()) { - this.setActiveTab(0); - } - }, afterrender: function(tabPanel) { if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'tab')) { const tabId = parseInt(MODx.request.tab, 10); diff --git a/manager/assets/modext/widgets/resource/modx.panel.resource.js b/manager/assets/modext/widgets/resource/modx.panel.resource.js index 81bd0e9046c..7fe99bed179 100644 --- a/manager/assets/modext/widgets/resource/modx.panel.resource.js +++ b/manager/assets/modext/widgets/resource/modx.panel.resource.js @@ -378,6 +378,18 @@ Ext.extend(MODx.panel.Resource,MODx.FormPanel,{ ,animCollapse: false ,itemId: 'tabs' ,items: it + ,listeners: { + tabchange: function(tabPanel, tab) { + /* + In certain scenarios, such as when form customization and/or a plugin adds a tab, + the state of the Resource tab panel can become uncertain and no tab will be initially + selected. This workaround ensures the first tab is selected. + */ + if (MODx.request.tab === undefined && !this.getActiveTab()) { + this.setActiveTab(0); + } + } + } }); if (MODx.config.tvs_below_content == 1) { var tvs = this.getTemplateVariablesPanel(config); From a4bf921aef7e412ca4eaf1c72811dd7e233106be Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:23:12 -0500 Subject: [PATCH 14/47] Filter Persistence: Namespaces Grid --- .../Workspace/PackageNamespace/GetList.php | 12 ++- .../namespace/modx.namespace.panel.js | 91 ++++--------------- 2 files changed, 25 insertions(+), 78 deletions(-) diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index c6c0d9fd7df..1ae9c9216c0 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -43,7 +43,9 @@ class GetList extends GetListProcessor public function initialize() { $initialized = parent::initialize(); - $this->setDefaultProperties(['search' => false]); + $this->setDefaultProperties([ + 'query' => '' + ]); $this->isGridFilter = $this->getProperty('isGridFilter', false); return $initialized; } @@ -55,11 +57,11 @@ public function initialize() */ public function prepareQueryBeforeCount(xPDOQuery $c) { - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->where([ - 'name:LIKE' => '%' . $search . '%', - 'OR:path:LIKE' => '%' . $search . '%', + 'name:LIKE' => '%' . $query . '%', + 'OR:path:LIKE' => '%' . $query . '%', ]); } /* diff --git a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js index 19befc2300b..90aaaba74e7 100644 --- a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js +++ b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js @@ -25,7 +25,6 @@ MODx.panel.Namespaces = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-namespace' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,preventRender: true }] @@ -52,7 +51,13 @@ MODx.grid.Namespace = function(config) { ,baseParams: { action: 'Workspace/PackageNamespace/GetList' } - ,fields: ['id','name','path','assets_path','perm'] + ,fields: [ + 'id', + 'name', + 'path', + 'assets_path', + 'perm' + ] ,anchor: '100%' ,paging: true ,autosave: true @@ -78,58 +83,17 @@ MODx.grid.Namespace = function(config) { ,sortable: false ,editor: { xtype: 'textfield' } }] - ,tbar: [{ - text: _('create') - ,handler: { xtype: 'modx-window-namespace-create' ,blankValues: true } - ,cls:'primary-button' - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-namespace-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.namespaceSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.namespaceSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,handler: { xtype: 'modx-window-namespace-create' ,blankValues: true } + ,cls:'primary-button' + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Namespace.superclass.constructor.call(this,config); }; @@ -174,25 +138,6 @@ Ext.extend(MODx.grid.Namespace,MODx.grid.Grid,{ win.show(vent.target); } - ,namespaceSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var namespaceSearch = Ext.getCmp('modx-namespace-search'); - s.baseParams = { - action: 'Workspace/PackageNamespace/GetList' - }; - MODx.request.search = ''; - namespaceSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,removeSelected: function() { var cs = this.getSelectedAsList(); if (cs === false) return false; From 010f2a798f9b0cc90930151d6c6fc4774959d774 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 17:27:01 -0500 Subject: [PATCH 15/47] Filter Persistence: Contexts Grid --- .../widgets/system/modx.grid.context.js | 114 +++--------------- 1 file changed, 18 insertions(+), 96 deletions(-) diff --git a/manager/assets/modext/widgets/system/modx.grid.context.js b/manager/assets/modext/widgets/system/modx.grid.context.js index b9a24b9417f..2532e5bfd78 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.js @@ -25,7 +25,6 @@ MODx.panel.Contexts = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-contexts' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,preventRender: true }] @@ -53,7 +52,13 @@ MODx.grid.Context = function(config) { ,baseParams: { action: 'Context/GetList' } - ,fields: ['key','name','description','perm', 'rank'] + ,fields: [ + 'key', + 'name', + 'description', + 'perm', + 'rank' + ] ,paging: true ,autosave: true ,save_action: 'Context/UpdateFromGrid' @@ -88,58 +93,17 @@ MODx.grid.Context = function(config) { ,sortable: true ,editor: { xtype: 'numberfield' } }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.create - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-ctx-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.ctxSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.ctxSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.create + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Context.superclass.constructor.call(this,config); }; @@ -238,25 +202,6 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ }); } - ,ctxSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var ctxSearch = Ext.getCmp('modx-ctx-search'); - s.baseParams = { - action: 'Context/GetList' - }; - MODx.request.search = ''; - ctxSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,afterAction: function() { var cmp = Ext.getCmp('modx-resource-tree'); if (cmp) { @@ -265,29 +210,6 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ this.getSelectionModel().clearSelections(true); this.refresh(); } - - ,getActions: function(record, rowIndex, colIndex, store) { - var permissions = record.data.perm; - var actions = []; - - if (~permissions.indexOf('pedit')) { - actions.push({ - action: 'updateContext', - icon: 'pencil-square-o', - text: _('edit') - }); - } - - if (~permissions.indexOf('premove')) { - actions.push({ - action: 'remove', - icon: 'trash-o', - text: _('delete') - }); - } - - return actions; - } }); Ext.reg('modx-grid-contexts',MODx.grid.Context); From 6da4c350497d3326e95ca031f21728ad610b6a5b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 18:56:46 -0500 Subject: [PATCH 16/47] Filter Persistence: Form Customization Profiles Grid --- .../Security/Forms/Profile/GetList.php | 11 +- .../modext/widgets/fc/modx.grid.fcprofile.js | 114 +++++------------- .../modext/widgets/fc/modx.panel.fcprofile.js | 1 - 3 files changed, 37 insertions(+), 89 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php index 03918c135c9..907f2106a03 100644 --- a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties(['search' => '']); + $this->setDefaultProperties(['query' => '']); $this->canEdit = $this->modx->hasPermission('save'); $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); @@ -47,11 +48,11 @@ public function initialize() public function getData() { $criteria = []; - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $criteria[] = [ - 'modFormCustomizationProfile.description:LIKE' => '%' . $search . '%', - 'OR:modFormCustomizationProfile.name:LIKE' => '%' . $search . '%', + 'modFormCustomizationProfile.description:LIKE' => '%' . $query . '%', + 'OR:modFormCustomizationProfile.name:LIKE' => '%' . $query . '%', ]; } $profileResult = $this->modx->call(modFormCustomizationProfile::class, 'listProfiles', [ diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js index dc45c6a106e..d397e471b2d 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js @@ -25,7 +25,6 @@ MODx.panel.FCProfiles = function(config) { title: '' ,preventRender: true ,xtype: 'modx-grid-fc-profile' - ,urlFilters: ['search'] ,cls:'main-wrapper' }] }],{ @@ -52,7 +51,16 @@ MODx.grid.FCProfile = function(config) { ,baseParams: { action: 'Security/Forms/Profile/GetList' } - ,fields: ['id','name','description','usergroups','active','rank','sets','perm'] + ,fields: [ + 'id', + 'name', + 'description', + 'usergroups', + 'active', + 'rank', + 'sets', + 'perm' + ] ,paging: true ,autosave: true ,save_action: 'Security/Forms/Profile/UpdateFromGrid' @@ -95,73 +103,32 @@ MODx.grid.FCProfile = function(config) { return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; } } - ,tbar: [{ - text: _('create') - ,scope: this - ,handler: this.createProfile - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected - ,scope: this - },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected + ,tbar: [ + { + text: _('create') ,scope: this + ,handler: this.createProfile + ,cls:'primary-button' },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-fcp-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.fcpSearch(cb, rec, ri); - } + text: _('bulk_actions') + ,menu: [{ + text: _('selected_activate') + ,handler: this.activateSelected ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.fcpSearch(cb, cb.value); - MODx.request.search = ''; - } - } + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + },{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.FCProfile.superclass.constructor.call(this,config); this.on('render',function() { this.getStore().reload(); },this); @@ -340,25 +307,6 @@ Ext.extend(MODx.grid.FCProfile,MODx.grid.Grid,{ }); return true; } - - ,fcpSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var fcpSearch = Ext.getCmp('modx-fcp-search'); - s.baseParams = { - action: 'Security/Forms/Profile/GetList' - }; - MODx.request.search = ''; - fcpSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-fc-profile',MODx.grid.FCProfile); diff --git a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js index c5ba53c17a7..002b5e6ed8d 100644 --- a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js @@ -71,7 +71,6 @@ MODx.panel.FCProfile = function(config) { }] },{ xtype: 'modx-grid-fc-set' - ,urlFilters: ['search'] ,cls:'main-wrapper' ,baseParams: { action: 'Security/Forms/Set/GetList' From da11cf97f170bcf48a4fb83e72ad4e13136cff50 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 18:59:49 -0500 Subject: [PATCH 17/47] Filter Persistence: Form Customization Sets Grid --- .../Processors/Security/Forms/Set/GetList.php | 12 +- .../modext/widgets/fc/modx.grid.fcset.js | 127 ++++++------------ 2 files changed, 46 insertions(+), 93 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php index cf25ab5d43e..1fcd77a4cf9 100644 --- a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php @@ -38,7 +38,7 @@ class GetList extends GetListProcessor */ public function initialize() { - $this->setDefaultProperties(['profile' => 0, 'search' => '']); + $this->setDefaultProperties(['profile' => 0, 'query' => '']); $this->canEdit = $this->modx->hasPermission('save'); $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); @@ -55,12 +55,12 @@ public function prepareQueryBeforeCount(xPDOQuery $c) if (!empty($profile)) { $c->where(['profile' => $profile]); } - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $c->where([ - 'modFormCustomizationSet.description:LIKE' => '%' . $search . '%', - 'OR:Template.templatename:LIKE' => '%' . $search . '%', - 'OR:modFormCustomizationSet.constraint_field:LIKE' => '%' . $search . '%', + 'modFormCustomizationSet.description:LIKE' => '%' . $query . '%', + 'OR:Template.templatename:LIKE' => '%' . $query . '%', + 'OR:modFormCustomizationSet.constraint_field:LIKE' => '%' . $query . '%', ], null, 2); } return $c; diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcset.js b/manager/assets/modext/widgets/fc/modx.grid.fcset.js index 5bb0ddae51c..c07878db9c2 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcset.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcset.js @@ -7,7 +7,21 @@ MODx.grid.FCSet = function(config) { ,baseParams: { action: 'Security/Forms/Set/GetList' } - ,fields: ['id','profile','action','description','active','template','templatename','constraint_data','constraint','constraint_field','constraint_class','rules','perm'] + ,fields: [ + 'id', + 'profile', + 'action', + 'description', + 'active', + 'template', + 'templatename', + 'constraint_data', + 'constraint', + 'constraint_field', + 'constraint_class', + 'rules', + 'perm' + ] ,paging: true ,autosave: true ,save_action: 'Security/Forms/Set/UpdateFromGrid' @@ -80,77 +94,36 @@ MODx.grid.FCSet = function(config) { return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; } } - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,scope: this - ,handler: this.createSet - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected + ,tbar: [ + { + text: _('create') + ,cls: 'primary-button' ,scope: this + ,handler: this.createSet },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected - ,scope: this - },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },{ - text: _('import') - ,handler: this.importSet - ,scope: this - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-fcs-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.fcsSearch(cb, rec, ri); - } + text: _('bulk_actions') + ,menu: [{ + text: _('selected_activate') + ,handler: this.activateSelected ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.fcsSearch(cb, cb.value); - MODx.request.search = ''; - } - } + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + },{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + },{ + text: _('import') + ,handler: this.importSet + ,scope: this + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.FCSet.superclass.constructor.call(this,config); }; @@ -214,26 +187,6 @@ Ext.extend(MODx.grid.FCSet,MODx.grid.Grid,{ } } - ,fcsSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var fcsSearch = Ext.getCmp('modx-fcs-search'); - s.baseParams = { - action: 'Security/Forms/Set/GetList' - ,profile: MODx.request.id - }; - MODx.request.search = ''; - fcsSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,exportSet: function(btn,e) { var id = this.menu.record.id; MODx.Ajax.request({ From b41b6987ba705a6b6567e56243b36dcf1b71aa91 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:03:22 -0500 Subject: [PATCH 18/47] Filter Persistence: Dashboards Grid --- .../Processors/System/Dashboard/GetList.php | 6 +- .../widgets/system/modx.panel.dashboards.js | 128 ++++++------------ 2 files changed, 45 insertions(+), 89 deletions(-) diff --git a/core/src/Revolution/Processors/System/Dashboard/GetList.php b/core/src/Revolution/Processors/System/Dashboard/GetList.php index 6a5afb3bd19..199b1b00913 100644 --- a/core/src/Revolution/Processors/System/Dashboard/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/GetList.php @@ -39,8 +39,10 @@ public function prepareQueryAfterCount(xPDOQuery $c) { $query = $this->getProperty('query'); if (!empty($query)) { - $c->where(['modDashboard.name:LIKE' => '%' . $query . '%']); - $c->orCondition(['modDashboard.description:LIKE' => '%' . $query . '%']); + $c->where([ + 'modDashboard.name:LIKE' => '%' . $query . '%', + 'OR:modDashboard.description:LIKE' => '%' . $query . '%', + ]); } $userGroup = $this->getProperty('usergroup', false); if (!empty($userGroup)) { diff --git a/manager/assets/modext/widgets/system/modx.panel.dashboards.js b/manager/assets/modext/widgets/system/modx.panel.dashboards.js index 604e8f7913a..940f47487d6 100644 --- a/manager/assets/modext/widgets/system/modx.panel.dashboards.js +++ b/manager/assets/modext/widgets/system/modx.panel.dashboards.js @@ -36,14 +36,7 @@ MODx.panel.Dashboards = function(config) { ,cls: 'main-wrapper' ,preventRender: true }] - }],{ - stateful: true - ,stateId: 'modx-dashboards-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + }])] }); MODx.panel.Dashboards.superclass.constructor.call(this,config); }; @@ -63,9 +56,15 @@ MODx.grid.Dashboards = function(config) { Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { - action: 'System/Dashboard/GetList' + action: 'System/Dashboard/GetList', + usergroup: MODx.request.usergroup || null } - ,fields: ['id','name','description','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'System/Dashboard/UpdateFromGrid' @@ -94,65 +93,41 @@ MODx.grid.Dashboards = function(config) { ,sortable: false ,editor: { xtype: 'textarea' } }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.createDashboard - ,scope: this - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.createDashboard ,scope: this - }] - },'->',{ - xtype: 'modx-combo-usergroup' - ,name: 'usergroup' - ,id: 'modx-user-filter-usergroup' - ,itemId: 'usergroup' - ,emptyText: _('user_group_filter')+'...' - ,baseParams: { - action: 'Security/Group/GetList' - ,addAll: true - } - ,value: '' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterUsergroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-dashboard-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: function() { - this.fireEvent('change',this.getValue()); - this.blur(); - return true;} - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + },'->',{ + xtype: 'modx-combo-usergroup' + ,itemId: 'filter-usergroup' + ,emptyText: _('user_group_filter') + ,baseParams: { + action: 'Security/Group/GetList' + ,addAll: true } + ,value: MODx.request.usergroup || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'usergroup'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-usergroup, filter-query') + ] }); MODx.grid.Dashboards.superclass.constructor.call(this,config); }; @@ -252,26 +227,5 @@ Ext.extend(MODx.grid.Dashboards,MODx.grid.Grid,{ return true; } - ,filterUsergroup: function(cb,nv,ov) { - this.getStore().baseParams.usergroup = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'System/Dashboard/GetList' - }; - Ext.getCmp('modx-dashboard-search').reset(); - Ext.getCmp('modx-user-filter-usergroup').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-dashboards',MODx.grid.Dashboards); From dad621faff0ac02adf6ae35df65f289e9e14756d Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:14:21 -0500 Subject: [PATCH 19/47] Filter Persistence: Dashboard Widgets Grid --- .../System/Dashboard/Widget/GetList.php | 6 +- .../system/modx.grid.dashboard.widgets.js | 91 +++++++------------ 2 files changed, 36 insertions(+), 61 deletions(-) diff --git a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php index 6d9399349c9..63f300c9dbb 100644 --- a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php @@ -39,8 +39,10 @@ public function prepareQueryBeforeCount(xPDOQuery $c) { $query = $this->getProperty('query'); if (!empty($query)) { - $c->where(['modDashboardWidget.name:LIKE' => '%' . $query . '%']); - $c->orCondition(['modDashboardWidget.description:LIKE' => '%' . $query . '%']); + $c->where([ + 'modDashboardWidget.name:LIKE' => '%' . $query . '%', + 'OR:modDashboardWidget.description:LIKE' => '%' . $query . '%', + ]); } return $c; } diff --git a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js index 21138b515cd..a21e8b3c3be 100644 --- a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js +++ b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js @@ -4,10 +4,9 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-dashboard-widgets */ -MODx.grid.DashboardWidgets = function(config) { - config = config || {}; +MODx.grid.DashboardWidgets = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {description_trans}

    ' ) }); @@ -18,7 +17,19 @@ MODx.grid.DashboardWidgets = function(config) { ,baseParams: { action: 'System/Dashboard/Widget/GetList' } - ,fields: ['id','name','name_trans','description','description_trans','type','content','namespace','lexicon','size','cls'] + ,fields: [ + 'id', + 'name', + 'name_trans', + 'description', + 'description_trans', + 'type', + 'content', + 'namespace', + 'lexicon', + 'size', + 'cls' + ] ,paging: true ,remoteSort: true ,sm: this.sm @@ -50,47 +61,24 @@ MODx.grid.DashboardWidgets = function(config) { ,width: 120 ,sortable: true }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,handler: this.createDashboard - ,scope: this - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected + ,tbar: [ + { + text: _('create') + ,cls:'primary-button' + ,handler: this.createDashboard ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-dashboard-widget-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-dashboard-widgets-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + }] + }, + '->', + this.getQueryFilterField('filter-query-dashboardWidgets'), + this.getClearFiltersButton('filter-query-dashboardWidgets') + ] }); MODx.grid.DashboardWidgets.superclass.constructor.call(this,config); }; @@ -170,20 +158,5 @@ Ext.extend(MODx.grid.DashboardWidgets,MODx.grid.Grid,{ }); return true; } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'System/Dashboard/Widget/GetList' - }; - Ext.getCmp('modx-dashboard-widget-search').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-dashboard-widgets',MODx.grid.DashboardWidgets); From af24d149a00988b2992ff65d7106e575efda0228 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:24:46 -0500 Subject: [PATCH 20/47] Filter Persistence: Packages Grid --- .../Processors/Workspace/Packages/GetList.php | 4 +- .../modext/workspace/package.containers.js | 1 - .../assets/modext/workspace/package.grid.js | 104 ++++++------------ 3 files changed, 34 insertions(+), 75 deletions(-) diff --git a/core/src/Revolution/Processors/Workspace/Packages/GetList.php b/core/src/Revolution/Processors/Workspace/Packages/GetList.php index 0c49c2ef075..a48d48c1af7 100644 --- a/core/src/Revolution/Processors/Workspace/Packages/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Packages/GetList.php @@ -53,7 +53,7 @@ public function initialize() 'limit' => 10, 'workspace' => 1, 'dateFormat' => $this->modx->getOption('manager_date_format') . ', ' . $this->modx->getOption('manager_time_format'), - 'search' => '', + 'query' => '', ]); return true; } @@ -71,7 +71,7 @@ public function getData() $this->getProperty('workspace', 1), $limit > 0 ? $limit : 0, $start, - $this->getProperty('search', ''), + $this->getProperty('query', ''), ]); $data['results'] = $pkgList['collection']; $data['total'] = $pkgList['total']; diff --git a/manager/assets/modext/workspace/package.containers.js b/manager/assets/modext/workspace/package.containers.js index 124db463da2..07dc9fdaacb 100644 --- a/manager/assets/modext/workspace/package.containers.js +++ b/manager/assets/modext/workspace/package.containers.js @@ -21,7 +21,6 @@ MODx.panel.Packages = function(config) { ,activeItem: 0 ,items:[{ xtype:'modx-package-grid' - ,urlFilters: ['search'] ,id:'modx-package-grid' ,bodyCssClass: 'grid-with-buttons' },{ diff --git a/manager/assets/modext/workspace/package.grid.js b/manager/assets/modext/workspace/package.grid.js index d03e2258888..69abd8aa225 100644 --- a/manager/assets/modext/workspace/package.grid.js +++ b/manager/assets/modext/workspace/package.grid.js @@ -84,9 +84,28 @@ MODx.grid.Package = function(config) { ,id: 'modx-package-grid' ,url: MODx.config.connector_url ,action: 'Workspace/Packages/GetList' - ,fields: ['signature','name','version','release','created','updated','installed','state','workspace' - ,'provider','provider_name','disabled','source','attributes','readme','menu' - ,'install','textaction','iconaction','updateable'] + ,fields: [ + 'signature', + 'name', + 'version', + 'release', + 'created', + 'updated', + 'installed', + 'state', + 'workspace', + 'provider', + 'provider_name', + 'disabled', + 'source', + 'attributes', + 'readme', + 'menu', + 'install', + 'textaction', + 'iconaction', + 'updateable' + ] ,showActionsColumn: false ,plugins: [this.exp] ,pageSize: Math.min(parseInt(MODx.config.default_per_page), 25) @@ -95,56 +114,16 @@ MODx.grid.Package = function(config) { ,primaryKey: 'signature' ,paging: true ,autosave: true - ,tbar: [dlbtn, { - text: _('packages_purge') - ,handler: this.purgePackages - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-package-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.search - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.packageSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.search) { - this.packageSearch(cb, cb.value); - MODx.request.search = ''; - } - } - ,scope: this - } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-package-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + dlbtn, + { + text: _('packages_purge'), + handler: this.purgePackages + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Package.superclass.constructor.call(this,config); this.on('render',function() { @@ -206,25 +185,6 @@ Ext.extend(MODx.grid.Package,MODx.grid.Grid,{ Ext.getCmp('packages-breadcrumbs').reset(msg); } - ,packageSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.search = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var packageSearch = Ext.getCmp('modx-package-search'); - s.baseParams = { - action: 'Workspace/Packages/GetList' - }; - MODx.request.search = ''; - packageSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - /* Main column renderer */ ,mainColumnRenderer:function (value, metaData, record, rowIndex, colIndex, store){ var rec = record.data; From 7e7bfa06d1a216626a659b9d5ae1382eb705b96d Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 19:28:32 -0500 Subject: [PATCH 21/47] Filter Persistence: Media Sources Grid --- .../widgets/source/modx.panel.sources.js | 127 +++++------------- 1 file changed, 34 insertions(+), 93 deletions(-) diff --git a/manager/assets/modext/widgets/source/modx.panel.sources.js b/manager/assets/modext/widgets/source/modx.panel.sources.js index 8912c755481..7cce9d4e9a3 100644 --- a/manager/assets/modext/widgets/source/modx.panel.sources.js +++ b/manager/assets/modext/widgets/source/modx.panel.sources.js @@ -25,7 +25,6 @@ MODx.panel.Sources = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-sources' - ,urlFilters: ['query'] ,cls: 'main-wrapper' ,preventRender: true }] @@ -40,14 +39,7 @@ MODx.panel.Sources = function(config) { ,cls: 'main-wrapper' ,preventRender: true }] - }],{ - stateful: true - ,stateId: 'modx-sources-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + }])] }); MODx.panel.Sources.superclass.constructor.call(this,config); }; @@ -62,16 +54,20 @@ Ext.reg('modx-panel-sources',MODx.panel.Sources); * @param {Object} config An object of configuration properties * @xtype modx-grid-sources */ -MODx.grid.Sources = function(config) { - config = config || {}; - +MODx.grid.Sources = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Source/GetList' } - ,fields: ['id','name','description','class_key','cls'] + ,fields: [ + 'id', + 'name', + 'description', + 'class_key', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Source/UpdateFromGrid' @@ -101,64 +97,26 @@ MODx.grid.Sources = function(config) { ,editor: { xtype: 'textarea' } ,renderer: Ext.util.Format.htmlEncode }] - ,tbar: [{ - text: _('create') - ,handler: { xtype: 'modx-window-source-create' ,blankValues: true } - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-source-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.query - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.sourceSearch(cb, rec, ri); - } - ,scope: this - }, - 'afterrender': { - fn: function (cb){ - if (MODx.request.query) { - this.sourceSearch(cb, cb.value); - MODx.request.query = ''; - } - } - ,scope: this + ,tbar: [ + { + text: _('create') + ,handler: { + xtype: 'modx-window-source-create', + blankValues: true } - ,'render': { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } + ,cls:'primary-button' + },{ + text: _('bulk_actions') + ,menu: [{ + text: _('selected_remove') + ,handler: this.removeSelected ,scope: this - } - } - },{ - xtype: 'button' - ,text: _('filter_clear') - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + }] + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.Sources.superclass.constructor.call(this,config); }; @@ -258,24 +216,6 @@ Ext.extend(MODx.grid.Sources,MODx.grid.Grid,{ return true; } - ,sourceSearch: function(tf,newValue,oldValue) { - var s = this.getStore(); - s.baseParams.query = newValue; - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - var sourceSearch = Ext.getCmp('modx-source-search'); - s.baseParams = { - action: 'Source/GetList' - }; - MODx.request.query = ''; - sourceSearch.setValue(''); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-sources',MODx.grid.Sources); @@ -287,8 +227,7 @@ Ext.reg('modx-grid-sources',MODx.grid.Sources); * @param {Object} config An object of options. * @xtype modx-window-source-create */ -MODx.window.CreateSource = function(config) { - config = config || {}; +MODx.window.CreateSource = function(config = {}) { Ext.applyIf(config,{ title: _('create') ,url: MODx.config.connector_url @@ -322,15 +261,17 @@ MODx.window.CreateSource = function(config) { Ext.extend(MODx.window.CreateSource,MODx.Window); Ext.reg('modx-window-source-create',MODx.window.CreateSource); -MODx.grid.SourceTypes = function(config) { - config = config || {}; - +MODx.grid.SourceTypes = function(config = {}) { Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Source/Type/GetList' } - ,fields: ['class','name','description'] + ,fields: [ + 'class', + 'name', + 'description' + ] ,showActionsColumn: false ,paging: true ,remoteSort: true From b7f4d2e46c7707a7fbf765b8260ab1de49fb0f70 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 20:16:29 -0500 Subject: [PATCH 22/47] Update related to commit 0e0778d Forgot to add this file in for Contexts panel/grid --- manager/assets/modext/widgets/system/modx.panel.context.js | 1 - 1 file changed, 1 deletion(-) diff --git a/manager/assets/modext/widgets/system/modx.panel.context.js b/manager/assets/modext/widgets/system/modx.panel.context.js index 5aa9cb02786..9a4270cab05 100644 --- a/manager/assets/modext/widgets/system/modx.panel.context.js +++ b/manager/assets/modext/widgets/system/modx.panel.context.js @@ -69,7 +69,6 @@ MODx.panel.Context = function(config) { ,xtype: 'modx-description' },{ xtype: 'modx-grid-context-settings' - ,urlFilters: ['namespace', 'area', 'query'] ,cls:'main-wrapper' ,title: '' ,preventRender: true From e4c6f4db7f003e2a18af37da90bafd134a4666d5 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 20:17:37 -0500 Subject: [PATCH 23/47] Update related to commit c7fd92b Forgot to add this file --- core/lexicon/en/dashboards.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lexicon/en/dashboards.inc.php b/core/lexicon/en/dashboards.inc.php index 3a88d926c89..f6cb29fd2d2 100644 --- a/core/lexicon/en/dashboards.inc.php +++ b/core/lexicon/en/dashboards.inc.php @@ -32,7 +32,7 @@ $_lang['dashboards'] = 'Dashboards'; $_lang['dashboards.intro_msg'] = 'Here you can manage all the available Dashboards for this MODX manager.'; $_lang['rank'] = 'Rank'; -$_lang['user_group_filter'] = 'By User Group'; +$_lang['user_group_filter'] = 'Filter by User Group...'; $_lang['widget'] = 'Widget'; $_lang['widget_content'] = 'Widget Content'; $_lang['widget_err_ae_name'] = 'A widget with the name "[[+name]]" already exists! Please try another name.'; @@ -99,4 +99,4 @@ $_lang['w_whosonline'] = 'Who\'s Online'; $_lang['w_whosonline_desc'] = 'Shows a list of online users.'; $_lang['w_view_all'] = 'View all'; -$_lang['w_no_data'] = 'No data to display'; \ No newline at end of file +$_lang['w_no_data'] = 'No data to display'; From cfde3959788ad5d2d34a24281245544ae0062bac Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 21:48:31 -0500 Subject: [PATCH 24/47] Filter Persistence: Users Grid --- .../modext/widgets/security/modx.grid.user.js | 146 +++++++----------- 1 file changed, 57 insertions(+), 89 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.js b/manager/assets/modext/widgets/security/modx.grid.user.js index a1a75a38a8d..5c7a7157b4b 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration options * @xtype modx-panel-users */ -MODx.panel.Users = function(config) { - config = config || {}; +MODx.panel.Users = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-users' ,cls: 'container' @@ -43,17 +42,25 @@ Ext.reg('modx-panel-users',MODx.panel.Users); * @param {Object} config An object of configuration properties * @xtype modx-grid-user */ -MODx.grid.User = function(config) { - config = config || {}; - +MODx.grid.User = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ url: MODx.config.connector_url ,baseParams: { action: 'Security/User/GetList' - ,usergroup: MODx.request['usergroup'] ? MODx.request['usergroup'] : '' + ,usergroup: MODx.request.usergroup || null } - ,fields: ['id','username','fullname','email','gender','blocked','role','active','cls'] + ,fields: [ + 'id', + 'username', + 'fullname', + 'email', + 'gender', + 'blocked', + 'role', + 'active', + 'cls' + ] ,paging: true ,autosave: true ,save_action: 'Security/User/UpdateFromGrid' @@ -111,70 +118,53 @@ MODx.grid.User = function(config) { ,sortable: true ,editor: { xtype: 'combo-boolean', renderer: 'boolean' } }] - ,tbar: [{ - text: _('create') - ,handler: this.createUser - ,scope: this - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_activate') - ,handler: this.activateSelected - ,scope: this - },{ - text: _('selected_deactivate') - ,handler: this.deactivateSelected + ,tbar: [ + { + text: _('create') + ,handler: this.createUser ,scope: this + ,cls:'primary-button' },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] - },'->',{ - xtype: 'modx-combo-usergroup' - ,name: 'usergroup' - ,id: 'modx-user-filter-usergroup' - ,itemId: 'usergroup' - ,emptyText: _('user_group')+'...' - ,baseParams: { - action: 'Security/Group/GetList' - ,addAll: true - } - ,value: MODx.request['usergroup'] ? MODx.request['usergroup'] : '' - ,width: 200 - ,listeners: { - 'select': {fn:this.filterUsergroup,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-user-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + text: _('bulk_actions') + ,menu: [ + { + text: _('selected_activate') + ,handler: this.activateSelected + ,scope: this + },{ + text: _('selected_deactivate') + ,handler: this.deactivateSelected + ,scope: this + },{ + text: _('selected_remove') + ,handler: this.removeSelected + ,scope: this + } + ] + }, + '->', + { + xtype: 'modx-combo-usergroup' + ,itemId: 'filter-usergroup' + ,emptyText: `${_('user_group')}...` + ,baseParams: { + action: 'Security/Group/GetList' + ,addAll: true } + ,value: MODx.request.usergroup || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'usergroup'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-usergroup, filter-query') + ] }); MODx.grid.User.superclass.constructor.call(this,config); }; @@ -336,27 +326,5 @@ Ext.extend(MODx.grid.User,MODx.grid.Grid,{ return _('female'); } } - - ,filterUsergroup: function(cb,nv,ov) { - this.getStore().baseParams.usergroup = Ext.isEmpty(nv) || Ext.isObject(nv) ? cb.getValue() : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/User/GetList' - }; - Ext.getCmp('modx-user-search').reset(); - Ext.getCmp('modx-user-filter-usergroup').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-user',MODx.grid.User); From dc219fb1a677418bdf65a6f69bdd26dca2626e34 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:09:32 -0500 Subject: [PATCH 25/47] Filter Persistence: Groups Panel and Users Grid Note that this is the panel that appears upon first going to the ACLs. The affected grid appears when clicking on a User Group in the left tree. The grid also appears under the Users panel when editing a User Group. --- .../Security/Group/User/GetList.php | 10 +- .../security/modx.panel.groups.roles.js | 134 ++++++++++-------- .../widgets/security/modx.panel.user.group.js | 75 +++------- 3 files changed, 98 insertions(+), 121 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Group/User/GetList.php b/core/src/Revolution/Processors/Security/Group/User/GetList.php index a0d5a3a9d40..628b8cf32d6 100644 --- a/core/src/Revolution/Processors/Security/Group/User/GetList.php +++ b/core/src/Revolution/Processors/Security/Group/User/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties([ 'usergroup' => false, - 'username' => '', + 'query' => '' ]); return parent::initialize(); @@ -60,10 +61,11 @@ public function prepareQueryBeforeCount(xPDOQuery $c) $userGroup = $this->getProperty('usergroup', 0); $c->where(['UserGroupMembers.user_group' => $userGroup]); - $username = $this->getProperty('username', ''); - if (!empty($username)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->where([ - $c->getAlias() . '.username:LIKE' => '%' . $username . '%', + $c->getAlias() . '.username:LIKE' => '%' . $query . '%', + 'OR:UserGroupRole.name:LIKE' => '%' . $query . '%' ]); } diff --git a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js index efaf1623fdf..7b0a3496e7e 100644 --- a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js +++ b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js @@ -4,50 +4,73 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-groups-roles */ -MODx.panel.GroupsRoles = function(config) { - config = config || {}; - Ext.applyIf(config,{ +MODx.panel.GroupsRoles = function(config = {}) { + this.currentGroupId = 0; + Ext.applyIf(config, { id: 'modx-panel-groups-roles' - ,cls: 'container' - ,defaults: { collapsible: false ,autoHeight: true } + ,cls: 'container' + ,defaults: { + collapsible: false, + autoHeight: true + } ,forceLayout: true - ,items: [{ - html: _('user_group_management') - ,id: 'modx-access-permissions-header' - ,xtype: 'modx-header' - },MODx.getPageStructure(this.getPageTabs(config),{ - id: 'modx-access-permissions-tabs' - ,stateful: true - ,stateId: 'access-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - })] + ,items: [ + { + html: _('user_group_management'), + id: 'modx-access-permissions-header', + xtype: 'modx-header' + }, MODx.getPageStructure( + this.getPageTabs(config), + { id: 'modx-access-permissions-tabs' } + ) + ] }); MODx.panel.GroupsRoles.superclass.constructor.call(this,config); - var west, usergroupTree = Ext.getCmp('modx-tree-usergroup'); + const userGrid = Ext.getCmp('modx-usergroup-users'), + usergroupTree = Ext.getCmp('modx-tree-usergroup') + ; - usergroupTree.on('expandnode', this.fixPanelHeight); - usergroupTree.on('collapsenode', this.fixPanelHeight); - - usergroupTree.addListener({ - resize : function(cmp) { - var centre = Ext.getCmp('modx-usergroup-users'); - if (centre.hidden){ - Ext.getCmp('modx-tree-panel-usergroup').layout.west.getSplitBar().el.hide(); - } + usergroupTree.on({ + resize: { + fn: function(cmp) { + if (userGrid.hidden) { + Ext.getCmp('modx-tree-panel-usergroup').layout.west.getSplitBar().el.hide(); + } + }, + scope: this + }, + refresh: { + fn: function() { + this.setActiveGroupNodeFromParam(); + }, + scope: this } }); if (MODx.perm.usergroup_user_list) { - Ext.getCmp('modx-tree-usergroup').on('click', function(node,e){ + usergroupTree.on('click', function(node, e){ + this.currentGroupId = MODx.util.tree.getGroupIdFromNode(node); + Ext.getCmp('modx-usergroup-users').clearGridFilters('filter-query'); + if (this.currentGroupId > 0) { + MODx.util.url.setParams({ + group: this.currentGroupId, + tab: 0 + }); + } this.getUsers(node); }, this); - } - Ext.getCmp('modx-usergroup-users').store.on('load', this.fixPanelHeight); + usergroupTree.getLoader().on({ + load: { + fn: function() { + this.currentGroupId = MODx.request.group || 0; + this.setActiveGroupNodeFromParam(); + }, + scope: this + } + }); + } }; Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ getPageTabs: function(config) { @@ -89,8 +112,8 @@ Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ region: 'center' ,id: 'modx-usergroup-users' ,xtype: 'modx-grid-user-group-users' - ,hidden: true - ,usergroup: '0' + ,hidden: MODx.perm.usergroup_user_list && this.currentGroupId > 0 ? false : true + ,usergroup: this.currentGroupId ,layout: 'fit' ,cls:'main-wrapper' } @@ -146,40 +169,35 @@ Ext.extend(MODx.panel.GroupsRoles,MODx.FormPanel,{ } return tbs; } - ,getUsers: function(node) { - var center = Ext.getCmp('modx-usergroup-users'); - center.removeAll(); - var id = node.attributes.id; - var usergroup = id.replace('n_ug_', '') - 0; // typecasting - var userGrid = Ext.getCmp('modx-usergroup-users'); - var westPanel = Ext.getCmp('modx-tree-panel-usergroup').layout.west; - - if (usergroup == 0) { + ,getUsers: function(node) { + const userGrid = Ext.getCmp('modx-usergroup-users'), + westPanel = Ext.getCmp('modx-tree-panel-usergroup').layout.west + ; + if (this.currentGroupId == 0) { userGrid.hide(); westPanel.getSplitBar().el.hide(); } else { userGrid.show(); westPanel.getSplitBar().el.show(); - userGrid.usergroup = usergroup; - userGrid.config.usergroup = usergroup; - userGrid.store.baseParams.usergroup = usergroup; - userGrid.clearGridFilters('filter-username'); + userGrid.usergroup = this.currentGroupId; + userGrid.config.usergroup = this.currentGroupId; + userGrid.store.baseParams.usergroup = this.currentGroupId; + userGrid.store.load(); } - } - ,fixPanelHeight: function() { - // fixing border layout's height regarding to tree panel's - var treeEl = Ext.getCmp('modx-tree-usergroup').getEl(); - if (!treeEl) { - return; + + ,setActiveGroupNodeFromParam: function() { + if (this.currentGroupId > 0) { + const usergroupTree = Ext.getCmp('modx-tree-usergroup'), + groupNodeId = `n_ug_${this.currentGroupId}`, + groupNode = usergroupTree.getNodeById(`n_ug_${this.currentGroupId}`) + ; + if (typeof groupNode !== 'undefined' && groupNodeId === groupNode.id) { + groupNode.select(); + this.getUsers(groupNode); + } } - var treeH = treeEl.getHeight(); - var cHeight = Ext.getCmp('modx-usergroup-users').getHeight(); // .main-wrapper - var maxH = (treeH > cHeight) ? treeH : cHeight; - maxH = maxH > 500 ? maxH : 500; - Ext.getCmp('modx-tree-panel-usergroup').setHeight(maxH); - Ext.getCmp('modx-content').doLayout(); } }); Ext.reg('modx-panel-groups-roles',MODx.panel.GroupsRoles); diff --git a/manager/assets/modext/widgets/security/modx.panel.user.group.js b/manager/assets/modext/widgets/security/modx.panel.user.group.js index afaf66ece42..23707b3b639 100644 --- a/manager/assets/modext/widgets/security/modx.panel.user.group.js +++ b/manager/assets/modext/widgets/security/modx.panel.user.group.js @@ -350,7 +350,6 @@ MODx.grid.UserGroupUsers = function(config) { ,baseParams: { action: 'Security/Group/User/GetList' ,usergroup: config.usergroup - ,username: MODx.request.username ? decodeURIComponent(MODx.request.username) : '' } ,paging: true ,grouping: true @@ -390,64 +389,22 @@ MODx.grid.UserGroupUsers = function(config) { }); }, scope: this } }] - ,tbar: [{ - text: _('user_group_update') - ,cls: 'primary-button' - ,handler: this.updateUserGroup - ,hidden: (MODx.perm.usergroup_edit == 0 || config.ownerCt.id != 'modx-tree-panel-usergroup') - },{ - text: _('user_group_user_add') - ,cls: 'primary-button' - ,handler: this.addUser - ,hidden: MODx.perm.usergroup_user_edit == 0 - },'->',{ - xtype: 'textfield' - ,itemId: 'filter-username' - ,emptyText: _('search') - ,value: MODx.request.username ? decodeURIComponent(MODx.request.username) : '' - ,listeners: { - change: { - fn: function (cmp, newValue, oldValue) { - this.applyGridFilter(cmp, 'username'); - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp, 'username'); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function() { - this.clearGridFilters('filter-username'); - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('user_group_update') + ,cls: 'primary-button' + ,handler: this.updateUserGroup + ,hidden: (MODx.perm.usergroup_edit == 0 || config.ownerCt.id != 'modx-tree-panel-usergroup') + },{ + text: _('user_group_user_add') + ,cls: 'primary-button' + ,handler: this.addUser + ,hidden: MODx.perm.usergroup_user_edit == 0 + }, + '->', + this.getQueryFilterField('filter-query', 'user-group-users'), + this.getClearFiltersButton() + ] }); MODx.grid.UserGroupUsers.superclass.constructor.call(this,config); this.addEvents('updateRole','addUser'); From 77193ca84e0dfc897d8615d84183542dba43d7db Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:41:34 -0500 Subject: [PATCH 26/47] Filter Persistence: Template TVs Grid Also fixes processor so the grid total count is always correct, making the paging correct and navigable. --- .../Element/Template/TemplateVar/GetList.php | 97 +++++++++---- .../widgets/element/modx.grid.template.tv.js | 129 +++++------------- 2 files changed, 105 insertions(+), 121 deletions(-) diff --git a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php index 459fe34e6dc..b32d6c4d63e 100644 --- a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php +++ b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php @@ -1,4 +1,5 @@ true, 'view_template' => true]; public $languageTopics = ['template']; + protected $category = 0; + protected $query = ''; + protected $isFiltered = false; + + /** + * {@inheritDoc} + */ + public function initialize() + { + $this->category = (int)$this->getProperty('category', 0); + $this->query = $this->getProperty('query', ''); + $this->isFiltered = $this->category > 0 || $this->query; + return parent::initialize(); + } + /** * Prepare conditions for TV list - * - * @return array */ - public function prepareConditions() + public function prepareConditions(): array { $conditions = []; - $category = (integer)$this->getProperty('category', 0); - if ($category) { - $conditions[] = ['category' => $category]; + if (!$this->isFiltered) { + return $conditions; + } + + if ($this->category) { + $conditions[] = ['category' => $this->category]; } - $query = $this->getProperty('query', ''); - if (!empty($query)) { + if (!empty($this->query)) { $conditions[] = [ - 'name:LIKE' => '%' . $query . '%', - 'OR:caption:LIKE' => '%' . $query . '%', - 'OR:description:LIKE' => '%' . $query . '%', + 'name:LIKE' => '%' . $this->query . '%', + 'OR:caption:LIKE' => '%' . $this->query . '%', + 'OR:description:LIKE' => '%' . $this->query . '%' ]; } @@ -79,21 +96,21 @@ public function loadTemplate() } /** - * {@inheritdoc} - * @return array + * {@inheritDoc} */ - public function getData() + public function getData(): array { $sort = $this->getProperty('sort'); $dir = $this->getProperty('dir'); - $limit = intval($this->getProperty('limit')); - $start = intval($this->getProperty('start')); + $limit = (int)$this->getProperty('limit'); + $start = (int)$this->getProperty('start'); $conditions = $this->prepareConditions(); $template = $this->loadTemplate(); $tvList = $template->getTemplateVarList([$sort => $dir], $limit, $start, $conditions); + $data = [ - 'total' => $tvList['total'], + 'total' => $this->isFiltered ? $this->getFilteredCount() : $tvList['total'], 'results' => $tvList['collection'], ]; @@ -101,15 +118,49 @@ public function getData() } /** - * {@inheritdoc} - * @param xPDOObject $object - * - * @return array|mixed + * Workaround to get correct total count when list is filtered + */ + public function getFilteredCount(): int + { + $c = $this->modx->newQuery(modTemplateVar::class); + $c = $this->prepareQueryBeforeCount($c); + $filteredCount = $this->modx->getCount(modTemplateVar::class, $c); + + return $filteredCount; + } + + /** + * {@inheritDoc} + */ + public function prepareQueryBeforeCount(xPDOQuery $c) + { + if (!$this->isFiltered) { + return $c; + } + + if ($this->category) { + $c->where( + ['category' => $this->category] + ); + } + + if ($this->query) { + $c->where([ + 'name:LIKE' => '%' . $this->query . '%', + 'OR:caption:LIKE' => '%' . $this->query . '%', + 'OR:description:LIKE' => '%' . $this->query . '%', + ]); + } + return $c; + } + + /** + * {@inheritDoc} */ public function prepareRow(xPDOObject $object) { $tvArray = $object->get(['id', 'name', 'caption', 'tv_rank', 'category_name']); - $tvArray['access'] = (boolean)$object->get('access'); + $tvArray['access'] = (bool)$object->get('access'); $tvArray['perm'] = []; if ($this->modx->hasPermission('edit_tv')) { diff --git a/manager/assets/modext/widgets/element/modx.grid.template.tv.js b/manager/assets/modext/widgets/element/modx.grid.template.tv.js index e3f3129e13a..d653e2fa39b 100644 --- a/manager/assets/modext/widgets/element/modx.grid.template.tv.js +++ b/manager/assets/modext/widgets/element/modx.grid.template.tv.js @@ -6,9 +6,8 @@ * @param {Object} config An object of options. * @xtype modx-grid-template-tv */ -MODx.grid.TemplateTV = function(config) { - config = config || {}; - var tt = new Ext.ux.grid.CheckColumn({ +MODx.grid.TemplateTV = function(config = {}) { + const tt = new Ext.ux.grid.CheckColumn({ header: _('access') ,dataIndex: 'access' ,width: 70 @@ -18,13 +17,21 @@ MODx.grid.TemplateTV = function(config) { title: _('template_assignedtv_tab') ,id: 'modx-grid-template-tv' ,url: MODx.config.connector_url - ,fields: ['id','name','caption','tv_rank','access','perm','category_name','category'] + ,fields: [ + 'id', + 'name', + 'caption', + 'tv_rank', + 'access', + 'perm', + 'category_name', + 'category' + ] ,baseParams: { action: 'Element/Template/TemplateVar/GetList' ,template: config.template ,sort: 'tv_rank' - ,category: MODx.request.category || '' - ,query: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' + ,category: MODx.request.category || null } ,saveParams: { template: config.template @@ -76,72 +83,26 @@ MODx.grid.TemplateTV = function(config) { ,editor: { xtype: 'textfield' ,allowBlank: false } ,sortable: true }] - ,tbar: ['->',{ - xtype: 'modx-combo-category' - ,name: 'filter_category' - ,hiddenName: 'filter_category' - ,id: 'modx-temptv-filter-category' - ,emptyText: _('filter_by_category') - ,value: MODx.request.category || '' - ,allowBlank: true - ,width: 150 - ,listeners: { - 'select': { - fn: function (cb, rec, ri) { - if (!MODx.request.query) { - this.filterByCategory(cb, rec, ri); - } - } - ,scope: this - } - } - },{ - xtype: 'textfield' - ,name: 'query' - ,id: 'modx-temptv-query' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,value: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' - ,listeners: { - 'change': { - fn: function (cb, rec, ri) { - this.tvSearch(cb, rec, ri); - } - ,scope: this - } - ,'afterrender': { - fn: function (cb) { - if (MODx.request.query) { - this.tvSearch(cb, cb.value); - MODx.request.query = ''; - } + ,tbar: [ + '->', + { + xtype: 'modx-combo-category' + ,itemId: 'filter-category' + ,emptyText: _('filter_by_category') + ,value: MODx.request.category || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'category'); + }, + scope: this } - ,scope: this - } - ,'render': { - fn: function (cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - } - ,scope: this - } - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); } - } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-category, filter-query') + ] }); MODx.grid.TemplateTV.superclass.constructor.call(this,config); this.on('render', this.prepareDDSort, this); @@ -191,34 +152,6 @@ Ext.extend(MODx.grid.TemplateTV,MODx.grid.Grid,{ }, this); } - ,filterByCategory: function (cb, newValue, ov) { - var nv = Ext.isEmpty(newValue) || Ext.isObject(newValue) ? cb.getValue() : newValue; - var s = this.getStore(); - s.baseParams.category = nv; - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,tvSearch: function(tf, newValue, ov) { - var nv = newValue || tf; - var s = this.getStore(); - s.baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - - ,clearFilter: function() { - var s = this.getStore(); - s.baseParams.category = s.baseParams.query = ''; - Ext.getCmp('modx-temptv-filter-category').setValue(''); - Ext.getCmp('modx-temptv-query').setValue(''); - s.load(); - this.replaceState(); - this.getBottomToolbar().changePage(1); - } - ,prepareDDSort: function(grid) { this.dropTarget = new Ext.dd.DropTarget(grid.getView().mainBody, { ddGroup: 'template-tvs-ddsort' From ea9867943f52165aebdbb00e502ebd41b35b1982 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 30 Jan 2023 22:42:19 -0500 Subject: [PATCH 27/47] Filter Persistence: TV Templates Grid --- .../Element/TemplateVar/Template/GetList.php | 15 ++- .../widgets/element/modx.grid.tv.template.js | 94 ++++++------------- 2 files changed, 41 insertions(+), 68 deletions(-) diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php index c564e59c485..0b995785b5d 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php @@ -1,4 +1,5 @@ modx->newQuery(modTemplate::class); - $query = $this->getProperty('query'); + $query = $this->getProperty('query', ''); if (!empty($query)) { $c->where([ - 'templatename:LIKE' => "%$query%", + 'templatename:LIKE' => '%' . $query . '%', + 'OR:description:LIKE' => '%' . $query . '%' ]); } $c->leftJoin(modCategory::class, 'Category'); @@ -107,8 +108,12 @@ public function getData() $c->select([ 'category_name' => 'Category.category', ]); - $c->select($this->modx->getSelectColumns(modTemplateVarTemplate::class, 'TemplateVarTemplates', '', - ['tmplvarid'])); + $c->select($this->modx->getSelectColumns( + modTemplateVarTemplate::class, + 'TemplateVarTemplates', + '', + ['tmplvarid'] + )); $c->select(['access' => 'TemplateVarTemplates.tmplvarid']); $c->sortby($this->getProperty('sort'), $this->getProperty('dir')); if ($isLimit) { diff --git a/manager/assets/modext/widgets/element/modx.grid.tv.template.js b/manager/assets/modext/widgets/element/modx.grid.tv.template.js index 56c55aaf885..25194e84de9 100644 --- a/manager/assets/modext/widgets/element/modx.grid.tv.template.js +++ b/manager/assets/modext/widgets/element/modx.grid.tv.template.js @@ -11,17 +11,26 @@ MODx.grid.TemplateVarTemplate = function(config) { var tt = new Ext.ux.grid.CheckColumn({ header: _('access') ,dataIndex: 'access' - ,width: 50 + ,width: 60 ,sortable: true }); Ext.applyIf(config,{ id: 'modx-grid-tv-template' ,url: MODx.config.connector_url - ,fields: ['id','templatename','category','category_name','description','access','menu'] + ,fields: [ + 'id', + 'templatename', + 'category', + 'category_name', + 'description', + 'access', + 'menu' + ] ,showActionsColumn: false ,baseParams: { action: 'Element/TemplateVar/Template/GetList' ,tv: config.tv + ,category: MODx.request.category || null } ,saveParams: { tv: config.tv @@ -50,69 +59,28 @@ MODx.grid.TemplateVarTemplate = function(config) { ,dataIndex: 'description' ,width: 300 },tt] - ,tbar: ['->',{ - xtype: 'modx-combo-category' - ,name: 'filter_category' - ,hiddenName: 'filter_category' - ,id: 'modx-tvtemp-filter-category' - ,emptyText: _('filter_by_category') - ,value: '' - ,allowBlank: true - ,width: 150 - ,listeners: { - 'select': {fn: this.filterByCategory, scope:this} - } - },'-',{ - xtype: 'textfield' - ,name: 'query' - ,id: 'modx-tvtemp-search' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); + ,tbar: [ + '->', + { + xtype: 'modx-combo-category' + ,itemId: 'filter-category' + ,emptyText: _('filter_by_category') + ,value: MODx.request.category || null + ,width: 200 + ,listeners: { + select: { + fn: function (cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'category'); + }, + scope: this + } } - } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-category, filter-query') + ] }); MODx.grid.TemplateVarTemplate.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.TemplateVarTemplate,MODx.grid.Grid,{ - filterByCategory: function(cb,rec,ri) { - this.getStore().baseParams['category'] = cb.getValue(); - this.getBottomToolbar().changePage(1); - } - - ,search: function(tf,newValue,oldValue) { - var nv = newValue || tf; - this.getStore().baseParams.query = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - Ext.getCmp('modx-tvtemp-filter-category').setValue(''); - this.getBottomToolbar().changePage(1); - return true; - } - - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Element/TemplateVar/Template/GetList' - }; - Ext.getCmp('modx-tvtemp-filter-category').reset(); - Ext.getCmp('modx-tvtemp-search').setValue(''); - this.getBottomToolbar().changePage(1); - } -}); +Ext.extend(MODx.grid.TemplateVarTemplate,MODx.grid.Grid); Ext.reg('modx-grid-tv-template',MODx.grid.TemplateVarTemplate); From c7e8bbadf05182ff8a951c97d257202607d2de1f Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 1 Feb 2023 22:28:19 -0500 Subject: [PATCH 28/47] Filter Persistence: User Messages Grid --- .../Processors/Security/Message/GetList.php | 22 +-- .../widgets/security/modx.grid.message.js | 148 +++++++++--------- 2 files changed, 74 insertions(+), 96 deletions(-) diff --git a/core/src/Revolution/Processors/Security/Message/GetList.php b/core/src/Revolution/Processors/Security/Message/GetList.php index 6760d6d673e..4746f3e842e 100644 --- a/core/src/Revolution/Processors/Security/Message/GetList.php +++ b/core/src/Revolution/Processors/Security/Message/GetList.php @@ -32,20 +32,6 @@ class GetList extends GetListProcessor public $permission = 'messages'; public $defaultSortField = 'date_sent'; - /** - * @return bool - */ - public function initialize() - { - $initialized = parent::initialize(); - $this->setDefaultProperties([ - 'search' => '', - 'type' => 'inbox' - ]); - - return $initialized; - } - /** * @param xPDOQuery $c * @return xPDOQuery @@ -68,11 +54,11 @@ public function prepareQueryBeforeCount(xPDOQuery $c) } $c->where($where); - $search = $this->getProperty('search', ''); - if (!empty($search)) { + $query = $this->getProperty('query', ''); + if (!empty($query)) { $c->andCondition([ - 'subject:LIKE' => '%' . $search . '%', - 'OR:message:LIKE' => '%' . $search . '%', + 'subject:LIKE' => '%' . $query . '%', + 'OR:message:LIKE' => '%' . $query . '%', ], null, 2); } diff --git a/manager/assets/modext/widgets/security/modx.grid.message.js b/manager/assets/modext/widgets/security/modx.grid.message.js index 28d80e7db8d..f2f77450b92 100644 --- a/manager/assets/modext/widgets/security/modx.grid.message.js +++ b/manager/assets/modext/widgets/security/modx.grid.message.js @@ -6,8 +6,7 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-messages */ -MODx.panel.Messages = function(config) { - config = config || {}; +MODx.panel.Messages = function(config = {}) { Ext.applyIf(config,{ id: 'modx-panel-message' ,cls: 'container' @@ -57,9 +56,7 @@ Ext.reg('modx-panel-messages',MODx.panel.Messages); * @param {Object} config An object of options. * @xtype modx-grid-message */ -MODx.grid.Message = function(config) { - config = config || {}; - +MODx.grid.Message = function(config = {}) { this.exp = new Ext.grid.RowExpander({ tpl : new Ext.Template( '' @@ -81,10 +78,22 @@ MODx.grid.Message = function(config) { ,id: 'modx-grid-message' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/Message/GetList' + action: 'Security/Message/GetList', + type: MODx.request.type || null } - ,fields: ['id','type','subject','message','sender','recipient','private' - ,'date_sent','read','sender_name','recipient_name'] + ,fields: [ + 'id', + 'type', + 'subject', + 'message', + 'sender', + 'recipient', + 'private', + 'date_sent', + 'read', + 'sender_name', + 'recipient_name' + ] ,autosave: true ,paging: true ,plugins: this.exp @@ -115,57 +124,60 @@ MODx.grid.Message = function(config) { header: _('read') ,dataIndex: 'read' ,width: 100 - ,editor: { xtype: 'combo-boolean' ,renderer: 'boolean' } + ,editor: { + xtype: 'combo-boolean', + renderer: 'boolean' + } ,editable: false }] - ,tbar: [{ - text: _('create') - ,cls:'primary-button' - ,disabled: disabled - ,scope: this - ,handler: this.newMessage - },'->',{ - xtype: 'modx-combo-message-type' - ,name: 'type' - ,id: 'modx-messages-filter' - ,emptyText: _('filter_by_type') - ,allowBlank: false - ,editable: false - ,typeAhead: false - ,forceSelection: true - ,width: 200 - ,listeners: { - 'select': {fn: this.filterByType, scope: this} - } - },{ - xtype: 'textfield' - ,name: 'search' - ,id: 'modx-messages-search' - ,cls: 'x-form-filter' - ,emptyText: _('search') - ,listeners: { - 'change': {fn: this.search, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-filter-clear' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } + ,tbar: [ + { + text: _('create') + ,cls: 'primary-button' + ,disabled: disabled + ,scope: this + ,handler: this.newMessage + }, + '->', + { + xtype: 'modx-combo-message-type' + ,name: 'type' + ,itemId: 'filter-type' + ,emptyText: _('filter_by_type') + ,width: 200 + ,value: MODx.request.type || null + ,listeners: { + render: { + fn: function(cmp) { + // Maintain default type in URL and in this combo when loading this combo and when clearing all filters + const clearFiltersButton = cmp.ownerCt.getComponent('filter-clear'), + resetDefaults = () => { + MODx.util.url.setParams({ type: 'inbox' }); + cmp.setValue('inbox'); + } + ; + if (!MODx.request.type) { + resetDefaults(); + } + if (clearFiltersButton) { + clearFiltersButton.on('click', button => { + resetDefaults(); + }); + } + }, + scope: this + }, + select: { + fn: function(cmp, record, selectedIndex) { + this.applyGridFilter(cmp, 'type'); + }, + scope: this + } } - } - }] + }, + this.getQueryFilterField(), + this.getClearFiltersButton('filter-type, filter-query') + ] }); MODx.grid.Message.superclass.constructor.call(this,config); }; @@ -264,24 +276,6 @@ Ext.extend(MODx.grid.Message,MODx.grid.Grid,{ xtype: 'modx-window-message-create' }); } - ,filterByType: function (combo) { - this.getStore().baseParams.type = combo.getValue(); - this.getBottomToolbar().changePage(1); - } - ,search: function(tf,newValue) { - var nv = newValue || tf; - this.getStore().baseParams.search = Ext.isEmpty(nv) || Ext.isObject(nv) ? '' : nv; - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.getStore().baseParams = { - action: 'Security/Message/GetList' - }; - Ext.getCmp('modx-messages-search').reset(); - Ext.getCmp('modx-messages-filter').reset(); - this.getBottomToolbar().changePage(1); - } }); Ext.reg('modx-grid-message',MODx.grid.Message); @@ -293,8 +287,7 @@ Ext.reg('modx-grid-message',MODx.grid.Message); * @param {Object} config An object of options. * @xtype modx-window-message-create */ -MODx.window.CreateMessage = function(config) { - config = config || {}; +MODx.window.CreateMessage = function(config = {}) { Ext.applyIf(config,{ title: _('create') ,url: MODx.config.connector_url @@ -415,8 +408,7 @@ Ext.reg('modx-window-message-create',MODx.window.CreateMessage); * @param {Object} config An object of options. * @xtype modx-combo-message-type */ -MODx.combo.MessageType = function(config) { - config = config || {}; +MODx.combo.MessageType = function(config = {}) { Ext.applyIf(config,{ store: new Ext.data.SimpleStore({ fields: ['d', 'v'], From 5f40560709c46b43cb3a970b9678d01a51329224 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 16 Feb 2023 23:00:12 -0500 Subject: [PATCH 29/47] Filter Persistence: Settings Grids (4) Includes system settings, usergroup settings, context settings, and user settings. Also adds new method to utilities and makes minor updates for the base grid class. --- .../Processors/System/Settings/GetAreas.php | 85 ++++- .../Workspace/PackageNamespace/GetList.php | 86 ++++- manager/assets/modext/util/utilities.js | 34 +- .../assets/modext/widgets/core/modx.combo.js | 2 +- .../assets/modext/widgets/core/modx.grid.js | 28 +- .../modext/widgets/core/modx.grid.settings.js | 352 +++++++++--------- .../security/modx.grid.user.group.settings.js | 51 ++- .../security/modx.grid.user.settings.js | 51 ++- .../system/modx.grid.context.settings.js | 55 +-- .../system/modx.panel.system.settings.js | 20 +- 10 files changed, 462 insertions(+), 302 deletions(-) diff --git a/core/src/Revolution/Processors/System/Settings/GetAreas.php b/core/src/Revolution/Processors/System/Settings/GetAreas.php index a9268e60b97..9a27ee521f5 100644 --- a/core/src/Revolution/Processors/System/Settings/GetAreas.php +++ b/core/src/Revolution/Processors/System/Settings/GetAreas.php @@ -11,7 +11,10 @@ namespace MODX\Revolution\Processors\System\Settings; use MODX\Revolution\Processors\Processor; +use MODX\Revolution\modContextSetting; use MODX\Revolution\modSystemSetting; +use MODX\Revolution\modUserGroupSetting; +use MODX\Revolution\modUserSetting; use PDO; use xPDO\Om\xPDOQuery; @@ -26,6 +29,9 @@ class GetAreas extends Processor { public $permission = 'settings'; + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $isGridFilter = false; + /** * @return mixed */ @@ -48,9 +54,9 @@ public function getLanguageTopics() public function initialize() { $this->setDefaultProperties([ - 'dir' => 'ASC', - 'namespace' => 'core', + 'query' => '' ]); + $this->isGridFilter = $this->getProperty('isGridFilter', false); return true; } @@ -108,31 +114,76 @@ public function process() */ public function getQuery() { - $namespace = $this->getProperty('namespace', 'core'); - $query = $this->getProperty('query'); + $alias = 'settingsArea'; + $aliasEscaped = $this->modx->escape($alias); + $settingsClass = modSystemSetting::class; + + // $foreignKey is the primary key of the child settings entity (e.g., user, usergroup, context) + $foreignKey = $this->getProperty('foreignKey', ''); + $foreignKeyWhere = null; - $c = $this->modx->newQuery(modSystemSetting::class); - $c->setClassAlias('settingsArea'); - $c->leftJoin(modSystemSetting::class, 'settingsCount', [ - 'settingsArea.' . $this->modx->escape('key') . ' = settingsCount.' . $this->modx->escape('key'), + /* + When this class is used to fetch data for a grid filter's store (combo), + limit results to only those areas present in the current grid. + */ + if ($this->isGridFilter && $this->getProperty('targetGrid', false) === 'MODx.grid.SettingsGrid') { + $settingsType = $this->getProperty('targetSettingsType', 'system'); + switch($settingsType) { + case 'context': + $settingsClass = modContextSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('context_key') => $this->modx->sanitizeString($foreignKey) ] + : null + ; + break; + case 'group': + $settingsClass = modUserGroupSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('group') => (int)$foreignKey ] + : null + ; + break; + case 'user': + $settingsClass = modUserSetting::class; + $foreignKeyWhere = $foreignKey + ? [ $aliasEscaped . '.' . $this->modx->escape('user') => (int)$foreignKey ] + : null + ; + break; + // no default + } + } + $areaColumn = $this->modx->escape('area'); + $keyColumn = $this->modx->escape('key'); + $namespaceColumn = $this->modx->escape('namespace'); + $joinAlias = 'settingsCount'; + $joinAliasEscaped = $this->modx->escape($joinAlias); + + $c = $this->modx->newQuery($settingsClass); + $c->setClassAlias($alias); + $c->leftJoin($settingsClass, $joinAlias, [ + "{$aliasEscaped}.{$keyColumn} = {$joinAliasEscaped}.{$keyColumn}" ]); - if (!empty($namespace)) { + if ($namespace = $this->getProperty('namespace', false)) { $c->where([ - 'settingsArea.namespace' => $namespace, + "{$aliasEscaped}.{$namespaceColumn}" => $namespace ]); } - if (!empty($query)) { + if ($query = $this->getProperty('query', '')) { $c->where([ - 'settingsArea.area:LIKE' => "%{$query}%", + "{$aliasEscaped}.{$areaColumn}:LIKE" => "%{$query}%" ]); } + if ($foreignKeyWhere) { + $c->where($foreignKeyWhere); + } $c->select([ - 'settingsArea.' . $this->modx->escape('area'), - 'settingsArea.' . $this->modx->escape('namespace'), - 'COUNT(settingsCount.' . $this->modx->escape('key') . ') AS num_settings', + "{$aliasEscaped}.{$areaColumn}", + "{$aliasEscaped}.{$namespaceColumn}", + "COUNT({$joinAliasEscaped}.{$keyColumn}) AS num_settings", ]); - $c->groupby('settingsArea.' . $this->modx->escape('area') . ', settingsArea.' . $this->modx->escape('namespace')); - $c->sortby($this->modx->escape('area'), $this->getProperty('dir', 'ASC')); + $c->groupby("{$aliasEscaped}.{$areaColumn}, {$aliasEscaped}.{$namespaceColumn}"); + $c->sortby($areaColumn, $this->getProperty('dir', 'ASC')); return $c; } } diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index 1ae9c9216c0..24c60d2f07e 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -18,6 +18,11 @@ use xPDO\Om\xPDOObject; use xPDO\Om\xPDOQuery; +use MODX\Revolution\modContextSetting; +use MODX\Revolution\modSystemSetting; +use MODX\Revolution\modUserGroupSetting; +use MODX\Revolution\modUserSetting; + /** * Gets a list of namespaces * @param string $name (optional) If set, will search by name @@ -64,26 +69,77 @@ public function prepareQueryBeforeCount(xPDOQuery $c) 'OR:path:LIKE' => '%' . $query . '%', ]); } + + // $foreignKey is the primary key of the child settings entity (e.g., user, usergroup, context) + $foreignKey = $this->getProperty('foreignKey', ''); + $foreignKeyWhere = null; + /* When this class is used to fetch data for a grid filter's store (combo), limit results to only those namespaces present in the current grid. */ - if ($this->isGridFilter) { - if ($userGroup = $this->getProperty('usergroup', false)) { - $c->innerJoin( - modAccessNamespace::class, - 'modAccessNamespace', - [ - '`modAccessNamespace`.`target` = `modNamespace`.`name`', - '`modAccessNamespace`.`principal` = ' . (int)$userGroup, - '`modAccessNamespace`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) - ] - ); - if ($policy = $this->getProperty('policy', false)) { - $c->where([ - '`modAccessNamespace`.`policy`' => (int)$policy + if ($this->isGridFilter && $targetGrid = $this->getProperty('targetGrid', false)) { + switch($targetGrid) { + case 'MODx.grid.SettingsGrid': + $settingsType = $this->getProperty('targetSettingsType', 'system'); + $alias = 'settingsNamespace'; + switch($settingsType) { + case 'context': + $settingsClass = modContextSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.context_key' => $this->modx->sanitizeString($foreignKey) ] : null ; + break; + case 'group': + $settingsClass = modUserGroupSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.group' => (int)$foreignKey ] : null ; + break; + case 'system': + $settingsClass = modSystemSetting::class; + break; + case 'user': + $settingsClass = modUserSetting::class; + $foreignKeyWhere = $foreignKey ? [ $alias . '.user' => (int)$foreignKey ] : null ; + break; + // no default + } + + $nsSubquery = $this->modx->newQuery($settingsClass); + $nsSubquery->setClassAlias($alias); + $nsSubquery->select([ + 'namespaces' => "GROUP_CONCAT(DISTINCT `{$alias}`.`namespace` SEPARATOR '\",\"')" ]); - } + if ($area = $this->getProperty('area', false)) { + $nsSubquery->where([ + "`{$alias}`.`area`" => $area + ]); + } + if ($foreignKeyWhere) { + $nsSubquery->where($foreignKeyWhere); + } + $namespaces = $this->modx->getObject($settingsClass, $nsSubquery)->get('namespaces'); + + $c->where( + "`{$c->getAlias()}`.`name` IN (\"{$namespaces}\")" + ); + break; + case 'MODx.grid.UserGroupNamespace': + if ($userGroup = $this->getProperty('usergroup', false)) { + $c->innerJoin( + modAccessNamespace::class, + 'modAccessNamespace', + [ + '`modAccessNamespace`.`target` = `modNamespace`.`name`', + '`modAccessNamespace`.`principal` = ' . (int)$userGroup, + '`modAccessNamespace`.`principal_class` = ' . $this->modx->quote(modUserGroup::class) + ] + ); + if ($policy = $this->getProperty('policy', false)) { + $c->where([ + '`modAccessNamespace`.`policy`' => (int)$policy + ]); + } + } + break; + // no default } } return $c; diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index 74f6fd49d54..4e01f439d36 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -581,17 +581,18 @@ MODx.util.url = { */ clearParam: function(reference, referenceIsComponent = true, stateData = {}) { if (typeof window.history.replaceState !== 'undefined') { - const urlParts = window.location.href.split('?'), - removeParamName = referenceIsComponent ? this.getParamNameFromCmp(reference) : reference.trim(), - regex = new RegExp(`${removeParamName}=[^&]+`, 'i') + let url = new URL(window.location.href), + removeParamName ; - let params = urlParts[1].replace(regex, '').replace('&&', '&'); - if (params.endsWith('&')) { - params = params.substr(0, params.length - 1); + if (referenceIsComponent) { + removeParamName = this.getParamNameFromCmp(reference); + removeParamName = removeParamName === 'namespace' ? 'ns' : removeParamName; + } else { + removeParamName = reference.trim(); } - let newUrl = new URL(`${urlParts[0]}?${params}`); - newUrl = newUrl.toString().replace(/%2F/g, '/'); - window.history.replaceState(stateData, document.title, newUrl); + url.searchParams.delete(removeParamName); + url = url.toString().replace(/%2F/g, '/'); + window.history.replaceState(stateData, document.title, url); } }, /** @@ -606,6 +607,21 @@ MODx.util.url = { const param = cmp.itemId.split('-')[1]; return param === 'ns' ? 'namespace' : param ; }, + /** + * @property {Function} getParamValue - Fetch and decode given parameter value from a request + * + * @param {String} param - The url query parameter + * @param {Boolean} setEmptyToString - For some components, like combos, setting to null is better + * when no value is present. Set this to true for components that prefer an empty string + * @return {Mixed} + */ + getParamValue: function(param, setEmptyToString = false) { + const + key = param === 'namespace' ? 'ns' : param, + emptyValue = setEmptyToString ? '' : null + ; + return MODx.request[key] ? this.decodeParamValue(MODx.request[key]) : emptyValue ; + }, /** * @property {Function} decodeParamValue - Decodes a given parameter's value * diff --git a/manager/assets/modext/widgets/core/modx.combo.js b/manager/assets/modext/widgets/core/modx.combo.js index 9acf73a08b8..fb63f9b9ffd 100644 --- a/manager/assets/modext/widgets/core/modx.combo.js +++ b/manager/assets/modext/widgets/core/modx.combo.js @@ -621,7 +621,7 @@ MODx.combo.Namespace = function(config) { ,hiddenName: 'namespace' ,typeAhead: true ,minChars: 1 - ,queryParam: 'search' + ,queryParam: 'query' ,editable: true ,allowBlank: true ,preselectValue: false diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index 1e8cd0fa91a..21f95c376db 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -108,16 +108,16 @@ MODx.grid.Grid = function(config) { if (config.columns && Array.isArray(config.columns)) { if (config.actionsColumnWidth === undefined) { - if (isPercentage(config.columns)) { defaultActionsColumnWidth = 0.1; } } config.columns.push({ - width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + hideable: false, + renderer: this.actionsColumnRenderer.bind(this) }); } @@ -129,9 +129,9 @@ MODx.grid.Grid = function(config) { } config.cm.columns.push({ - width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + renderer: this.actionsColumnRenderer.bind(this) }); } } @@ -950,15 +950,19 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ bottomToolbar = this.getBottomToolbar(), data = Array.isArray(items) ? items : items.split(',') ; - data.forEach(item => { - itemData = item.replace(/\s+/g, '').split(':'); - const itemId = itemData[0], + data.forEach(item => {; + const itemData = item.replace(/\s+/g, '').split(':'), + itemId = itemData[0], itemDefaultVal = itemData.length == 2 ? itemData[1] : null , cmp = this.getFilterComponent(itemId), param = MODx.util.url.getParamNameFromCmp(cmp) ; if (cmp.xtype.includes('combo')) { - cmp.setValue(itemDefaultVal); + if (itemDefaultVal === '') { + cmp.setValue(null); + } else { + cmp.setValue(itemDefaultVal); + } } else { cmp.setValue(''); } @@ -1081,7 +1085,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ fn: function(cmp, newValue, oldValue) { this.applyGridFilter(cmp); const usergroupTree = Ext.getCmp('modx-tree-usergroup') - if (implementation == 'user-group-users' && usergroupTree) { + if (implementation === 'user-group-users' && usergroupTree) { /* When the user group users grid is shown in the primary ACLs panel, having the user groups tree along with a corresponding grid, the diff --git a/manager/assets/modext/widgets/core/modx.grid.settings.js b/manager/assets/modext/widgets/core/modx.grid.settings.js index 57afc1b6efd..f1eeccc1ce1 100644 --- a/manager/assets/modext/widgets/core/modx.grid.settings.js +++ b/manager/assets/modext/widgets/core/modx.grid.settings.js @@ -6,164 +6,149 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-settings */ -MODx.grid.SettingsGrid = function(config) { - config = config || {}; +MODx.grid.SettingsGrid = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.XTemplate( + tpl: new Ext.XTemplate( '

    {[MODx.util.safeHtml(values.description_trans)]}

    ' ) }); + this.areaFilterValue = MODx.util.url.getParamValue('area'); + this.namespaceFilterValue = MODx.util.url.getParamValue('ns'); if (!config.tbar) { config.tbar = [{ text: _('create') ,scope: this - ,cls:'primary-button' + ,cls: 'primary-button' ,handler: { xtype: 'modx-window-setting-create' ,url: config.url || MODx.config.connector_url ,blankValues: true + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }]; } config.tbar.push( - '->' - ,{ - /** - * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 - * - * To access this combo in the future, get a reference to the topToolbar and use - * the getComponent method ( e.g., [gridObj].getTopToolbar().getComponent([itemId]) ) - * - * Also, itemId to be renamed 'filter-namespace' in 3.1 - */ - xtype: 'modx-combo-namespace' - ,id: 'modx-filter-namespace' - ,itemId: 'filter-ns' - ,emptyText: _('namespace_filter') - ,allowBlank: false - ,editable: true - ,typeAhead: true - ,minChars: 2 - ,forceSelection: true - ,width: 200 - ,value: MODx.request.ns || null - ,listeners: { - select: { - fn: function (cmp, record, selectedIndex) { - this.applyGridFilter(cmp, 'ns'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); - } + '->', + { + // xtype: 'modx-combo-namespace' + // ,itemId: 'filter-namespace' + /** + * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 + * + * To access this combo in the future, get a reference to the topToolbar and use + * the getComponent method ( e.g., [gridObj].getTopToolbar().getComponent([itemId]) ) + * + * Also, itemId to be renamed 'filter-namespace' in 3.1 + */ + xtype: 'modx-combo-namespace' + ,id: 'modx-filter-namespace' + ,itemId: 'filter-ns' + ,emptyText: _('namespace_filter') + ,typeAhead: true + ,minChars: 2 + ,forceSelection: true + ,width: 200 + ,value: MODx.request.ns || null + ,baseParams: { + action: 'Workspace/PackageNamespace/GetList', + area: this.areaFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.SettingsGrid', + targetSettingsType: this.getSettingsType(), + foreignKey: config.fk || false + } + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-area', 'namespace', record.data.name); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this }, - scope: this - }, - change: { + change: { // Support typed-in value (where the select event is not triggered) - fn: function (cmp, newValue, oldValue) { - if (newValue != oldValue) { - this.applyGridFilter(cmp, 'ns'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); + fn: function(cmp, newValue, oldValue) { + /* + Note that clicking the combo trigger when there is no value chosen initially, + (the combo's value is null) but not choosing an item fires change event and + changes the combo's value to an empty string; this can cause issues in some + instances, so explicity reset to null + */ + if (newValue === '') { + cmp.setValue(null); } - } - }, - scope: this + this.updateDependentFilter('filter-area', 'namespace', newValue); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + } } - } - },{ - /** - * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 - * - * To access this combo in the future, get a reference to the topToolbar and use - * the getComponent method ( e.g., [gridObj].getTopToolbar().getComponent([itemId]) ) - */ - xtype: 'modx-combo-area' - ,id: 'modx-filter-area' - ,itemId: 'filter-area' - ,emptyText: _('area_filter') - ,value: MODx.request.area || null - ,baseParams: { - action: 'System/Settings/GetAreas' - ,namespace: MODx.request.ns || null - } - ,width: 250 - ,allowBlank: true - ,editable: true - ,typeAhead: true - ,minChars: 2 - ,forceSelection: true - ,listeners: { - select: { - fn: function (cmp, record, selectedIndex) { - this.applyGridFilter(cmp, 'area'); + }, + { + // xtype: 'modx-combo-area' + // ,itemId: 'filter-area' + /** + * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 + * + * To access this combo in the future, get a reference to the topToolbar and use + * the getComponent method ( e.g., [gridObj].getTopToolbar().getComponent([itemId]) ) + */ + xtype: 'modx-combo-area' + ,id: 'modx-filter-area' + ,itemId: 'filter-area' + ,emptyText: _('area_filter') + ,value: this.areaFilterValue + ,baseParams: { + action: 'System/Settings/GetAreas', + namespace: MODx.request.ns || null, + isGridFilter: true, + targetGrid: 'MODx.grid.SettingsGrid', + targetSettingsType: this.getSettingsType(), + foreignKey: config.fk || false + } + ,width: 250 + ,allowBlank: true + ,editable: true + ,typeAhead: true + ,minChars: 2 + ,forceSelection: true + ,listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-ns', 'area', record.data.v); + this.applyGridFilter(cmp, 'area'); + }, + scope: this }, - scope: this - }, - change: { + change: { // Support typed-in value (where the select event is not triggered) - fn: function (cmp, newValue, oldValue) { - if (newValue != oldValue) { + fn: function(cmp, newValue, oldValue) { + // See note in namespace combo above re setting to null + if (newValue === '') { + cmp.setValue(null); + } + this.updateDependentFilter('filter-ns', 'area', newValue); this.applyGridFilter(cmp, 'area'); - } - }, - scope: this - } - } - },{ - xtype: 'textfield' - ,itemId: 'filter-query' - ,emptyText: _('search') - ,value: MODx.request.query ? decodeURIComponent(MODx.request.query) : '' - ,listeners: { - change: { - fn: function (cmp, newValue, oldValue) { - this.applyGridFilter(cmp); - }, - scope: this - }, - afterrender: { - fn: function(cmp) { - if (MODx.request.query) { - this.applyGridFilter(cmp); - } - }, - scope: this - }, - render: { - fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER, - fn: this.blur, - scope: cmp - }); - } - ,scope: this - } - } - },{ - text: _('filter_clear') - ,itemId: 'filter-clear' - ,listeners: { - click: { - fn: function(cmp) { - this.clearGridFilters('filter-ns, filter-area, filter-query'); - const areaCmp = cmp.ownerCt.getComponent('filter-area'); - if (areaCmp) { - this.getAreaFilterOptions(areaCmp); - } - }, - scope: this - }, - mouseout: { - fn: function(evt) { - this.removeClass('x-btn-focus'); + }, + scope: this } } - } - }); + }, + this.getQueryFilterField(), + this.getClearFiltersButton( + 'filter-ns:, filter-area:, filter-query', + 'filter-area:namespace, filter-ns:area' + ) + ); this.cm = new Ext.grid.ColumnModel({ columns: [this.exp,{ @@ -253,9 +238,9 @@ MODx.grid.SettingsGrid = function(config) { ] ,url: MODx.config.connector_url ,baseParams: { - action: 'System/Settings/GetList' - ,namespace: MODx.request.ns || null - ,area: MODx.request.area || null + action: 'System/Settings/GetList', + namespace: this.namespaceFilterValue, + area: this.areaFilterValue } ,clicksToEdit: 2 ,grouping: true @@ -293,6 +278,28 @@ MODx.grid.SettingsGrid = function(config) { ,scrollOffset: 0 }); MODx.grid.SettingsGrid.superclass.constructor.call(this,config); + this.addEvents('createSetting', 'updateSetting'); + + const gridFilterData = [ + { filterId: 'filter-namespace', dependentParams: ['area'] }, + { filterId: 'filter-area', dependentParams: ['namespace'] } + ]; + + this.on({ + createSetting: function(...args) { + if (args[0].a.response.status === 200) { + this.refreshFilterOptions(gridFilterData); + } + }, + updateSetting: function(...args) { + if (args[0].a.response.status === 200) { + this.refreshFilterOptions(gridFilterData); + } + }, + afterRemoveRow: function() { + this.refreshFilterOptions(gridFilterData); + } + }); // prevents navigation to next cell editor field when pressing the ENTER key this.selModel.onEditorKey = this.selModel.onEditorKey.createInterceptor(function(field, e) { @@ -339,35 +346,28 @@ Ext.extend(MODx.grid.SettingsGrid,MODx.grid.Grid,{ ,removeSetting: function() { return this.remove('setting_remove_confirm', 'System/Settings/Remove'); } - - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + /* + TBD: Remove child settings grids' update methods and adjust this base one to accommodate those grids; only difference is the child window configs include and action property. Maybe use createDelegate to pass additional var. + */ + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); - } - - /** - * @property {Function} getAreaFilterOptions - Builds the dropdown list of Areas based on the namespace - * - * @param {Object} cmp - The Area filter's Ext component - */ - ,getAreaFilterOptions: function(cmp) { - cmp.store.baseParams.namespace = this.getStore().baseParams.namespace; - cmp.store.removeAll(); - cmp.store.load(); - cmp.setValue(''); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } ,renderDynField: function(v,md,rec,ri,ci,s,g) { @@ -418,12 +418,21 @@ Ext.extend(MODx.grid.SettingsGrid,MODx.grid.Grid,{ // Return formatted date (server side) return value; } + + /** + * Gets an identifier for the type of setting being worked with; + * expected format: 'modx-grid-[type]-setting' + * + * @return {String} + */ + ,getSettingsType: function() { + const type = Object.getPrototypeOf(this).constructor.xtype; + return type.split('-')[2]; + } }); Ext.reg('modx-grid-settings',MODx.grid.SettingsGrid); - -MODx.combo.Area = function(config) { - config = config || {}; +MODx.combo.Area = function(config = {}) { Ext.applyIf(config,{ name: 'area' ,hiddenName: 'area' @@ -441,8 +450,7 @@ Ext.extend(MODx.combo.Area,MODx.combo.ComboBox); Ext.reg('modx-combo-area',MODx.combo.Area); -MODx.window.CreateSetting = function(config) { - config = config || {}; +MODx.window.CreateSetting = function(config = {}) { config.keyField = config.keyField || {}; Ext.applyIf(config,{ title: _('create') @@ -521,7 +529,7 @@ MODx.window.CreateSetting = function(config) { ,fieldLabel: _('namespace') ,name: 'namespace' ,id: 'modx-cs-namespace' - ,value: config.grid.getTopToolbar().getComponent('filter-ns').getValue() + ,value: config.grid.getTopToolbar().getComponent('filter-namespace').getValue() ,anchor: '100%' },{ xtype: 'label' @@ -557,7 +565,7 @@ MODx.window.CreateSetting = function(config) { this.on('show',function() { this.reset(); this.setValues({ - namespace: config.grid.getTopToolbar().getComponent('filter-ns').value + namespace: config.grid.getTopToolbar().getComponent('filter-namespace').value ,area: config.grid.getTopToolbar().getComponent('filter-area').value }); },this); @@ -566,8 +574,7 @@ Ext.extend(MODx.window.CreateSetting,MODx.Window); Ext.reg('modx-window-setting-create',MODx.window.CreateSetting); -MODx.combo.xType = function(config) { - config = config || {}; +MODx.combo.xType = function(config = {}) { Ext.applyIf(config,{ store: new Ext.data.SimpleStore({ fields: ['d','v'] @@ -607,8 +614,7 @@ Ext.extend(MODx.combo.xType,Ext.form.ComboBox); Ext.reg('modx-combo-xtype-spec',MODx.combo.xType); -MODx.window.UpdateSetting = function(config) { - config = config || {}; +MODx.window.UpdateSetting = function(config = {}) { this.ident = config.ident || 'modx-uss-'+Ext.id(); Ext.applyIf(config,{ title: _('edit') diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js index bc441fbb53e..07ec14a1444 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-group-settings */ -MODx.grid.GroupSettings = function(config) { - config = config || {}; +MODx.grid.GroupSettings = function(config = {}) { Ext.applyIf(config,{ title: _('user_group_settings') ,id: 'modx-grid-group-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/Group/Setting/GetList' - ,group: config.group + action: 'Security/Group/Setting/GetList', + group: config.group, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { group: config.group @@ -32,6 +33,15 @@ MODx.grid.GroupSettings = function(config) { action: 'Security/Group/Setting/Create' } ,fk: config.group + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); @@ -65,23 +75,26 @@ Ext.extend(MODx.grid.GroupSettings,MODx.grid.SettingsGrid, { } } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Security/Group/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Security/Group/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-group-settings',MODx.grid.GroupSettings); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.settings.js index cfea16bd7c0..8b75005abf0 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-user-settings */ -MODx.grid.UserSettings = function(config) { - config = config || {}; +MODx.grid.UserSettings = function(config = {}) { Ext.applyIf(config,{ title: _('user_settings') ,id: 'modx-grid-user-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Security/User/Setting/GetList' - ,user: config.user + action: 'Security/User/Setting/GetList', + user: config.user, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { user: config.user @@ -53,6 +54,15 @@ MODx.grid.UserSettings = function(config) { } } ,fk: config.user + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); @@ -86,23 +96,26 @@ Ext.extend(MODx.grid.UserSettings,MODx.grid.SettingsGrid, { } } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Security/User/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Security/User/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-user-settings',MODx.grid.UserSettings); diff --git a/manager/assets/modext/widgets/system/modx.grid.context.settings.js b/manager/assets/modext/widgets/system/modx.grid.context.settings.js index 31cfa78e324..625188bcb04 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.settings.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.settings.js @@ -6,15 +6,16 @@ * @param {Object} config An object of options. * @xtype modx-grid-context-settings */ -MODx.grid.ContextSettings = function(config) { - config = config || {}; +MODx.grid.ContextSettings = function(config = {}) { Ext.applyIf(config,{ title: _('context_settings') ,id: 'modx-grid-context-settings' ,url: MODx.config.connector_url ,baseParams: { - action: 'Context/Setting/GetList' - ,context_key: config.context_key + action: 'Context/Setting/GetList', + context_key: config.context_key, + namespace: MODx.util.url.getParamValue('ns'), + area: MODx.util.url.getParamValue('area') } ,saveParams: { context_key: config.context_key @@ -53,38 +54,50 @@ MODx.grid.ContextSettings = function(config) { } } ,fk: config.context_key + ,listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('createSetting', response); + }, + scope: this + } + } } }] }); MODx.grid.ContextSettings.superclass.constructor.call(this,config); }; + Ext.extend(MODx.grid.ContextSettings,MODx.grid.SettingsGrid, { removeSetting: function() { return this.remove('setting_remove_confirm', 'Context/Setting/Remove'); } - ,updateSetting: function(btn,e) { - var r = this.menu.record; - r.fk = Ext.isDefined(this.config.fk) ? this.config.fk : 0; - var uss = MODx.load({ - xtype: 'modx-window-setting-update' - ,action: 'Context/Setting/Update' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + + ,updateSetting: function(btn, e) { + const { record } = this.menu; + record.fk = this.config?.fk || 0; + this.windows.updateSetting = MODx.load({ + xtype: 'modx-window-setting-update', + action: 'Context/Setting/Update', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateSetting', response); + }, + scope: this + } } }); - uss.reset(); - uss.setValues(r); - uss.show(e.target); + this.windows.updateSetting.setValues(record); + this.windows.updateSetting.show(e.target); } }); Ext.reg('modx-grid-context-settings',MODx.grid.ContextSettings); - - /** * Update a Context Setting * diff --git a/manager/assets/modext/widgets/system/modx.panel.system.settings.js b/manager/assets/modext/widgets/system/modx.panel.system.settings.js index dd78f4e2e53..d853be2c84f 100644 --- a/manager/assets/modext/widgets/system/modx.panel.system.settings.js +++ b/manager/assets/modext/widgets/system/modx.panel.system.settings.js @@ -12,42 +12,31 @@ MODx.panel.SystemSettings = function(config) { id: 'modx-panel-system-settings' ,cls: 'container' ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } + ,defaults: { + collapsible: false, + autoHeight: true + } ,items: [{ html: _('system_settings')+' & '+_('events') ,id: 'modx-system-settings-header' ,xtype: 'modx-header' },MODx.getPageStructure([{ title: _('system_settings') - ,autoHeight: true ,layout: 'form' - ,defaults: { border: false ,msgTarget: 'side' } ,items:[{ - layout: 'form' - ,autoHeight: true - ,defaults: { border: false } - ,items: [{ html: '

    '+_('settings_desc')+'

    ' ,xtype: 'modx-description' },{ xtype: 'modx-grid-system-settings' - ,urlFilters: ['namespace', 'area', 'query'] ,cls: 'main-wrapper' ,preventSaveRefresh: true },{ html: MODx.onSiteSettingsRender }] - }] },{ title: _('system_events') - ,autoHeight: true ,layout: 'form' - ,defaults: { border: false ,msgTarget: 'side' } ,items:[{ - layout: 'form' - ,autoHeight: true - ,defaults: { border: false } - ,items: [{ html: '

    '+_('system_events.desc')+'

    ' ,xtype: 'modx-description' },{ @@ -55,7 +44,6 @@ MODx.panel.SystemSettings = function(config) { ,cls: 'main-wrapper' ,preventSaveRefresh: true }] - }] }],{ id: 'modx-system-settings-tabs' })] From bfc8b4647dcd14e27efb005ab61664976ee4a53b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 17 Feb 2023 00:44:56 -0500 Subject: [PATCH 30/47] Filter Persistence: System Events Grid --- .../widgets/system/modx.grid.system.event.js | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/manager/assets/modext/widgets/system/modx.grid.system.event.js b/manager/assets/modext/widgets/system/modx.grid.system.event.js index 11afce1ac4c..b53797db675 100644 --- a/manager/assets/modext/widgets/system/modx.grid.system.event.js +++ b/manager/assets/modext/widgets/system/modx.grid.system.event.js @@ -14,7 +14,13 @@ MODx.grid.SystemEvent = function(config) { ,baseParams: { action: 'System/Event/GetList' } - ,fields: ['id','name','service','groupname','plugins'] + ,fields: [ + 'id', + 'name', + 'service', + 'groupname', + 'plugins' + ] ,autosave: true ,paging: true ,clicksToEdit: 2 @@ -41,44 +47,22 @@ MODx.grid.SystemEvent = function(config) { ,width: 150 ,hidden: true }] - ,tbar: [{ - text: _('create') - ,scope: this - ,cls:'primary-button' - ,handler: { - xtype: 'modx-window-events-create-update' - ,url: config.url || MODx.config.connector_url - ,blankValues: true - ,isUpdate: false - } - },'->',{ - xtype: 'textfield' - ,name: 'filter_key' - ,id: 'modx-filter-event' - ,cls: 'x-form-filter' - ,emptyText: _('system_events.search_by_name')+'...' - ,listeners: { - 'change': {fn: this.filterByName, scope: this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,cls: 'x-form-filter-clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } - } - }] + ,tbar: [ + { + text: _('create') + ,scope: this + ,cls:'primary-button' + ,handler: { + xtype: 'modx-window-events-create-update' + ,url: config.url || MODx.config.connector_url + ,blankValues: true + ,isUpdate: false + } + }, + '->', + this.getQueryFilterField(), + this.getClearFiltersButton() + ] }); MODx.grid.SystemEvent.superclass.constructor.call(this,config); }; @@ -94,22 +78,6 @@ Ext.extend(MODx.grid.SystemEvent,MODx.grid.Grid,{ return m; } - ,filterByName: function(tf,newValue,oldValue) { - this.getStore().baseParams.query = newValue; - this.getBottomToolbar().changePage(1); - this.refresh(); - return true; - } - ,clearFilter: function() { - Ext.getCmp('modx-filter-event').reset(); - - this.getStore().baseParams = this.initialConfig.baseParams; - this.getStore().baseParams.query = ''; - - this.getBottomToolbar().changePage(1); - this.refresh(); - } - ,removeEvent: function(btn, e) { MODx.msg.confirm({ title: _('delete') From 9af2ea2635c36e043835ec26a62b96c28c5b3307 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 15:40:10 -0500 Subject: [PATCH 31/47] Filter Persistence: Lexicons Grid Also provides two new helper methods in the base modLexicon class to improve filtering granularity, as well as a small styling tweak for the top toolbar. --- _build/templates/default/sass/_toolbars.scss | 14 +- .../Processors/System/Language/GetList.php | 10 +- .../Processors/Workspace/Lexicon/GetList.php | 21 +- .../Workspace/Lexicon/Topic/GetList.php | 6 +- .../Workspace/PackageNamespace/GetList.php | 10 + core/src/Revolution/modLexicon.php | 138 +++++- .../modext/workspace/lexicon/lexicon.grid.js | 403 +++++++++--------- 7 files changed, 389 insertions(+), 213 deletions(-) diff --git a/_build/templates/default/sass/_toolbars.scss b/_build/templates/default/sass/_toolbars.scss index 8ba4d969b4b..7bea60a41b3 100644 --- a/_build/templates/default/sass/_toolbars.scss +++ b/_build/templates/default/sass/_toolbars.scss @@ -187,13 +187,25 @@ /* top toolbars */ .x-panel-tbar { overflow: visible; /* prevent cut off box-shadows in FF */ - padding-bottom: 2px; + padding-bottom: 4px; .x-toolbar { /*background-color: #F5F5F5;*/ border: 0; padding: 5px 0; overflow: visible; /* prevent cut off box-shadows in FF */ + + td { + vertical-align: bottom; + } + + input { + &.filter-query { + /* fix positioning issue with query field */ + position: relative; + bottom: -1px; + } + } } } diff --git a/core/src/Revolution/Processors/System/Language/GetList.php b/core/src/Revolution/Processors/System/Language/GetList.php index 85b1953807f..35e26ee0860 100644 --- a/core/src/Revolution/Processors/System/Language/GetList.php +++ b/core/src/Revolution/Processors/System/Language/GetList.php @@ -20,6 +20,9 @@ */ class GetList extends Processor { + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $isGridFilter = false; + /** * @return mixed */ @@ -46,6 +49,7 @@ public function initialize() 'limit' => 0, 'namespace' => 'core', ]); + $this->isGridFilter = $this->getProperty('isGridFilter', false); return true; } @@ -80,7 +84,11 @@ public function getData() $limit = $this->getProperty('limit'); - $data['results'] = $this->modx->lexicon->getLanguageList($this->getProperty('namespace')); + if ($this->isGridFilter && $topic = $this->getProperty('topic', null)) { + $data['results'] = $this->modx->lexicon->getFilterLanguageList($this->getProperty('namespace'), $topic); + } else { + $data['results'] = $this->modx->lexicon->getLanguageList($this->getProperty('namespace')); + } $data['total'] = count($data['results']); // this allows for typeahead filtering in the lexicon topics combobox diff --git a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php index 32f4298aef1..f2e874e510f 100644 --- a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php @@ -1,4 +1,5 @@ 'en', 'namespace' => 'core', 'topic' => 'default', + 'query' => '' ]); if ($this->getProperty('language') === '') { $this->setProperty('language', 'en'); @@ -77,11 +79,11 @@ public function process() 'language' => $this->getProperty('language'), ]; - $search = $this->getProperty('search'); - if (!empty($search)) { + $query = $this->getProperty('query'); + if (!empty($query)) { $where[] = [ - 'name:LIKE' => '%' . $search . '%', - 'OR:value:LIKE' => '%' . $search . '%', + 'name:LIKE' => '%' . $query . '%', + 'OR:value:LIKE' => '%' . $query . '%', ]; } @@ -97,12 +99,15 @@ public function process() } /* first get file-based lexicon */ - $entries = $this->modx->lexicon->getFileTopic($this->getProperty('language'), $this->getProperty('namespace'), - $this->getProperty('topic')); + $entries = $this->modx->lexicon->getFileTopic( + $this->getProperty('language'), + $this->getProperty('namespace'), + $this->getProperty('topic') + ); $entries = is_array($entries) ? $entries : []; /* if searching */ - if (!empty($search)) { + if (!empty($query)) { function parseArray($needle, array $haystack = []) { if (!is_array($haystack)) { @@ -117,7 +122,7 @@ function parseArray($needle, array $haystack = []) return $results; } - $entries = parseArray($search, $entries); + $entries = parseArray($query, $entries); } /* add in unique entries */ diff --git a/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php b/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php index f71350c3fb6..3d4a76c8861 100644 --- a/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Lexicon/Topic/GetList.php @@ -72,8 +72,10 @@ public function process() public function getData() { $data = []; - $data['results'] = $this->modx->lexicon->getTopicList($this->getProperty('language'), - $this->getProperty('namespace')); + $data['results'] = $this->modx->lexicon->getTopicList( + $this->getProperty('language'), + $this->getProperty('namespace') + ); $data['total'] = count($data['results']); // this allows for typeahead filtering in the lexicon topics combobox diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index 24c60d2f07e..78014f07a14 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -139,6 +139,16 @@ public function prepareQueryBeforeCount(xPDOQuery $c) } } break; + case 'MODx.grid.Lexicon': + $language = $this->getProperty('language', 'en'); + $topic = $this->getProperty('topic', ''); + $namespaces = $this->modx->lexicon->getNamespaceList($language, $topic); + if (!empty($namespaces)) { + $c->where([ + "`{$c->getAlias()}`.`name`:IN" => $namespaces + ]); + } + break; // no default } } diff --git a/core/src/Revolution/modLexicon.php b/core/src/Revolution/modLexicon.php index 9fde50f99d7..9811901c0ce 100644 --- a/core/src/Revolution/modLexicon.php +++ b/core/src/Revolution/modLexicon.php @@ -381,11 +381,71 @@ public function getNamespacePath($namespace = 'core') return $corePath; } + /** + * Get a list of available Namespaces when given a Language and Topic + * + * @param string $language The language to filter by + * @param string $topic The topic to filter by + * + * @return array An array of Namespace names + */ + public function getNamespaceList($language = 'en', $topic = '') : array + { + $namespaceList = []; + if ($namespaces = $this->modx->getCollection(modNamespace::class)) { + foreach($namespaces as $namespace) { + $name = $namespace->get('name'); + $corePath = $name === 'core' + ? $this->modx->getOption('core_path', null, MODX_CORE_PATH) + : $namespace->getCorePath() + ; + $lexiconPath = str_replace('//', '/', $corePath . '/lexicon/' . $language . '/'); + + if (!is_dir($lexiconPath)) { + continue; + } + if ($topic) { + $file = $topic . '.inc.php'; + if (!is_readable($lexiconPath . $file)) { + continue; + } + } + $namespaceList[] = $name; + } + } + $c = $this->modx->newQuery(modLexiconEntry::class, [ + 'language' => $language + ]); + if (!empty($namespaceList)) { + $c->where([ + 'namespace:NOT IN' => $namespaceList + ]); + } + if ($topic) { + $c->where([ + 'topic' => $topic + ]); + } + $c->select(['namespace']); + $c->query['distinct'] = 'DISTINCT'; + if ($c->prepare() && $c->stmt->execute()) { + $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC); + if (is_array($entries) and count($entries) > 0) { + foreach ($entries as $v) { + $namespaceList[] = $v['namespace']; + } + } + } + sort($namespaceList); + + return $namespaceList; + } + /** * Get a list of available Topics when given a Language and Namespace. * * @param string $language The language to filter by. - * @param string $namespace The language to filter by. + * @param string $namespace The namespace to filter by. * * @return array An array of Topic names. */ @@ -419,6 +479,7 @@ public function getTopicList($language = 'en', $namespace = 'core') $c->where([ 'namespace' => $namespace, 'topic:NOT IN' => $topics, + 'language' => $language ]); $c->select(['topic']); $c->query['distinct'] = 'DISTINCT'; @@ -506,6 +567,81 @@ public function getLanguageList($namespace = 'core') return $languages; } + /** + * Get a list of available languages for a Namespace and Topic + * + * @param string $namespace The Namespace to filter by + * @param string $topic The Topic to filter by + * + * @return array An array of available languages + */ + public function getFilterLanguageList($namespace = 'core', $topic = '') + { + $corePath = $this->getNamespacePath($namespace); + $lexiconPath = str_replace('//', '/', $corePath . '/lexicon/'); + if (!is_dir($lexiconPath)) { + return []; + } + $languageList = []; + + /** @var DirectoryIterator $language */ + foreach (new DirectoryIterator($lexiconPath) as $language) { + if (in_array($language, ['.', '..', '.svn', '.git', '_notes', 'country']) || !$language->isReadable()) { + continue; + } + + if ($language->isDir()) { + $languageKey = $language->getFilename(); + + /* + Only show languages that contain the selected topic + + Note that all custom (new, user-created) topics for core entries will only be + present in the database, which is queried below, so no need to assess whether a + file for a particular language and topic exists in that scenario + */ + if ($topic && $namespace !== 'core') { + $topicsPath = $language->getPath() . '/' . $languageKey . '/'; + if (!is_dir($topicsPath)) { + continue; + } + $file = $topic . '.inc.php'; + if (!is_readable($topicsPath . $file)) { + continue; + } + } + $languageList[] = $languageKey; + } + } + + $c = $this->modx->newQuery(modLexiconEntry::class, [ + 'namespace' => $namespace + ]); + if (!empty($languageList)) { + $c->where([ + 'language:NOT IN' => $languageList + ]); + } + if ($topic) { + $c->where([ + 'topic' => $topic + ]); + } + $c->select(['language']); + $c->query['distinct'] = 'DISTINCT'; + if ($c->prepare() && $c->stmt->execute()) { + $entries = $c->stmt->fetchAll(\PDO::FETCH_ASSOC); + if (is_array($entries) and count($entries) > 0) { + foreach ($entries as $v) { + $languageList[] = $v['language']; + } + } + } + sort($languageList); + + return $languageList; + } + /** * Get a lexicon string by its index. * diff --git a/manager/assets/modext/workspace/lexicon/lexicon.grid.js b/manager/assets/modext/workspace/lexicon/lexicon.grid.js index 07c1d8d703d..3202445ec90 100644 --- a/manager/assets/modext/workspace/lexicon/lexicon.grid.js +++ b/manager/assets/modext/workspace/lexicon/lexicon.grid.js @@ -6,19 +6,29 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-lexicon */ -MODx.grid.Lexicon = function(config) { - config = config || {}; +MODx.grid.Lexicon = function(config = {}) { + this.languageFilterValue = MODx.util.url.getParamValue('language') || this.currentLanguage; + this.topicFilterValue = MODx.util.url.getParamValue('topic') || 'default'; + this.namespaceFilterValue = MODx.util.url.getParamValue('ns') || 'core'; + Ext.applyIf(config,{ id: 'modx-grid-lexicon' ,url: MODx.config.connector_url - ,fields: ['name','value','namespace','topic','language','editedon','overridden'] + ,fields: [ + 'name', + 'value', + 'namespace', + 'topic', + 'language', + 'editedon', + 'overridden' + ] ,baseParams: { - action: 'Workspace/Lexicon/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : 'core' - ,topic: '' - ,language: MODx.config.cultureKey || 'en' + action: 'Workspace/Lexicon/GetList', + namespace: this.namespaceFilterValue, + topic: this.topicFilterValue, + language: this.languageFilterValue } - ,width: '98%' ,paging: true ,autosave: true ,save_action: 'Workspace/Lexicon/UpdateFromGrid' @@ -41,96 +51,177 @@ MODx.grid.Lexicon = function(config) { ,width: 125 ,renderer: this._renderLastModDate }] - ,tbar: [{ - xtype: 'button' - ,text: _('create') - ,cls:'primary-button' - ,handler: this.createEntry - ,scope: this - }, - '->' - ,{ - xtype: 'tbtext' - ,text: _('namespace')+':' - },{ - xtype: 'modx-combo-namespace' - ,id: 'modx-lexicon-filter-namespace' - ,itemId: 'namespace' - ,preselectValue: MODx.request['ns'] ? MODx.request['ns'] : '' - ,width: 150 - ,listeners: { - 'select': {fn: this.changeNamespace,scope:this} - } - },{ - xtype: 'tbtext' - ,text: _('topic')+':' - },{ - xtype: 'modx-combo-lexicon-topic' - ,id: 'modx-lexicon-filter-topic' - ,itemId: 'topic' - ,value: 'default' - ,width: 150 - ,baseParams: { - action: 'Workspace/Lexicon/Topic/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : '' - ,'language': 'en' - } - ,listeners: { - 'select': {fn:this.changeTopic,scope:this} - } - },{ - xtype: 'tbtext' - ,text: _('language')+':' - },{ - xtype: 'modx-combo-language' - ,name: 'language' - ,id: 'modx-lexicon-filter-language' - ,itemId: 'language' - ,value: MODx.config.cultureKey || 'en' - ,width: 100 - ,baseParams: { - action: 'System/Language/GetList' - ,'namespace': MODx.request['ns'] ? MODx.request['ns'] : '' - } - ,listeners: { - 'select': {fn:this.changeLanguage,scope:this} - } - },{ - xtype: 'textfield' - ,name: 'name' - ,id: 'modx-lexicon-filter-search' - ,cls: 'x-form-filter' - ,itemId: 'search' - ,emptyText: _('search') - ,listeners: { - 'change': {fn:this.filter.createDelegate(this,['search'],true),scope:this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} - } - },{ - xtype: 'button' - ,id: 'modx-lexicon-filter-clear' - ,cls: 'x-form-filter-clear' - ,itemId: 'clear' - ,text: _('filter_clear') - ,listeners: { - 'click': {fn: this.clearFilter, scope: this}, - 'mouseout': { fn: function(evt){ - this.removeClass('x-btn-focus'); - } - } + ,tbar: { + cls: 'has-nested-filters', + items: [ + { + xtype: 'button' + ,text: _('create') + ,cls: 'primary-button' + ,handler: this.createEntry + ,scope: this + }, + '->', + { + xtype: 'container', + layout: 'form', + itemId: 'filter-namespace-container', + cls: 'grid-filter', + width: 150, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('namespace') + }, + { + xtype: 'modx-combo-namespace', + itemId: 'filter-namespace', + hideLabel: true, + submitValue: false, + value: this.namespaceFilterValue, + baseParams: { + action: 'Workspace/PackageNamespace/GetList', + language: this.languageFilterValue, + topic: this.topicFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-language', 'namespace', record.data.name); + this.updateDependentFilter('filter-topic', 'namespace', record.data.name); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-language', 'namespace', newValue); + this.updateDependentFilter('filter-topic', 'namespace', newValue); + this.applyGridFilter(cmp, 'ns'); + }, + scope: this + } + } + } + ] + }, + { + xtype: 'container', + layout: 'form', + itemId: 'filter-language-container', + cls: 'grid-filter', + width: 100, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('language') + }, + { + xtype: 'modx-combo-language', + itemId: 'filter-language', + hideLabel: true, + submitValue: false, + queryParam: 'query', + value: this.languageFilterValue, + baseParams: { + action: 'System/Language/GetList', + namespace: this.namespaceFilterValue, + topic: this.topicFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-topic', 'language', record.data.name); + this.updateDependentFilter('filter-namespace', 'language', record.data.name); + this.applyGridFilter(cmp, 'language'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-topic', 'language', newValue); + this.updateDependentFilter('filter-namespace', 'language', newValue); + this.applyGridFilter(cmp, 'language'); + }, + scope: this + } + } + } + ] + }, + { + xtype: 'container', + layout: 'form', + itemId: 'filter-topic-container', + cls: 'grid-filter', + width: 150, + defaults: { + anchor: '100%' + }, + items: [ + { + xtype: 'label', + html: _('topic') + }, + { + xtype: 'modx-combo-lexicon-topic', + itemId: 'filter-topic', + hideLabel: true, + submitValue: false, + queryParam: 'query', + value: this.topicFilterValue, + baseParams: { + action: 'Workspace/Lexicon/Topic/GetList', + namespace: this.namespaceFilterValue, + language: this.languageFilterValue, + isGridFilter: true, + targetGrid: 'MODx.grid.Lexicon' + }, + listeners: { + select: { + fn: function(cmp, record, selectedIndex) { + this.updateDependentFilter('filter-namespace', 'topic', record.data.name); + this.updateDependentFilter('filter-language', 'topic', record.data.name); + this.applyGridFilter(cmp, 'topic'); + }, + scope: this + }, + change: { + // Support typed-in value (where the select event is not triggered) + fn: function(cmp, newValue, oldValue) { + this.updateDependentFilter('filter-namespace', 'topic', newValue); + this.updateDependentFilter('filter-language', 'topic', newValue); + this.applyGridFilter(cmp, 'topic'); + }, + scope: this + } + } + } + ] + }, + this.getQueryFilterField(), + this.getClearFiltersButton(`filter-namespace:core, filter-topic:default, filter-language:${this.currentLanguage}, filter-query`) + ] + } + ,pagingItems: [ + { + text: _('reload_from_base') + ,handler: this.reloadFromBase + ,scope: this } - }] - ,pagingItems: [{ - text: _('reload_from_base') - ,handler: this.reloadFromBase - ,scope: this - }] + ] }); MODx.grid.Lexicon.superclass.constructor.call(this,config); }; @@ -156,86 +247,6 @@ Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ return new Date(value*1000).format(MODx.config.manager_date_format + ' ' + MODx.config.manager_time_format); } - ,filter: function(cb,r,i,name) { - if (!name) {return false;} - this.store.baseParams[name] = cb.getValue(); - this.getBottomToolbar().changePage(1); - return true; - } - ,clearFilter: function() { - this.store.baseParams = { - action: 'Workspace/Lexicon/GetList' - ,'namespace': 'core' - ,topic: 'default' - ,language: 'en' - }; - this.getBottomToolbar().changePage(1); - var tb = this.getTopToolbar(); - tb.getComponent('namespace').setValue('core'); - - var tcb = tb.getComponent('topic'); - tcb.store.baseParams['namespace'] = 'core'; - tcb.store.load(); - tcb.setValue('default'); - - var tcl = tb.getComponent('language'); - tcb.store.baseParams['namespace'] = 'core'; - tcb.store.load(); - tcl.setValue('en'); - - tb.getComponent('search').setValue(''); - } - ,changeNamespace: function(cb,nv,ov) { - this.setFilterParams(cb.getValue(),'default','en'); - } - ,changeTopic: function(cb,nv,ov) { - this.setFilterParams(null,cb.getValue()); - } - ,changeLanguage: function(cb,nv,ov) { - this.setFilterParams(null,null,cb.getValue()); - } - - ,setFilterParams: function(ns,t,l) { - var tb = this.getTopToolbar(); - if (!tb) {return false;} - - var tcb,tcl; - if (ns) { - tb.getComponent('namespace').setValue(ns); - - tcl = tb.getComponent('language'); - if (tcl) { - tcl.store.baseParams['namespace'] = ns; - tcl.store.load({ - callback: function() { - tcl.setValue(l || 'en'); - } - }); - } - tcb = tb.getComponent('topic'); - if (tcb) { - tcb.store.baseParams['namespace'] = ns; - tcb.store.baseParams['language'] = l ? l : (tcl ? tcl.getValue() : 'en'); - tcb.store.load({ - callback: function() { - tcb.setValue(t || 'default'); - } - }); - } - } else if (t) { - tcb = tb.getComponent('topic'); - if (tcb) {tcb.setValue(t);} - } - - var s = this.getStore(); - if (s) { - if (ns) {s.baseParams['namespace'] = ns;} - if (t) {s.baseParams['topic'] = t || 'default';} - if (l) {s.baseParams['language'] = l || 'en';} - s.removeAll(); - } - this.getBottomToolbar().changePage(1); - } ,loadWindow2: function(btn,e,o) { var tb = this.getTopToolbar(); this.menu.record = { @@ -298,27 +309,29 @@ Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ return m; } - ,createEntry: function(btn,e) { - var r = this.menu.record || {}; + ,createEntry: function(btn, e) { + const record = this.menu.record || {}; - var tb = this.getTopToolbar(); - r['namespace'] = tb.getComponent('namespace').getValue(); - r.language = tb.getComponent('language').getValue(); - r.topic = tb.getComponent('topic').getValue(); + record.namespace = this.getFilterComponent('filter-namespace').getValue(); + record.language = this.getFilterComponent('filter-language').getValue(); + record.topic = this.getFilterComponent('filter-topic').getValue(); if (!this.createEntryWindow) { this.createEntryWindow = MODx.load({ - xtype: 'modx-window-lexicon-entry-create' - ,record: r - ,listeners: { - 'success':{fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-lexicon-entry-create', + record: record, + listeners: { + success: { + fn: function(o) { + this.refresh(); + }, + scope: this + } } }); } this.createEntryWindow.reset(); - this.createEntryWindow.setValues(r); + this.createEntryWindow.setValues(record); this.createEntryWindow.show(e.target); } }); @@ -351,16 +364,6 @@ MODx.window.LexiconEntryCreate = function(config) { ,anchor: '100%' ,msgTarget: 'under' ,allowBlank: false - ,listeners: { - 'select': {fn: function(cb,r,i) { - cle = this.fp.getComponent('topic'); - if (cle) { - cle.store.baseParams['namespace'] = cb.getValue(); - cle.setValue(''); - cle.store.reload(); - } else {MODx.debug('cle not found');} - },scope:this} - } },{ xtype: 'modx-combo-lexicon-topic' ,fieldLabel: _('topic') From 9d8308c738af3c54e578ac1c9ff488aa24781a42 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 19:48:10 -0500 Subject: [PATCH 32/47] Update UserGroup Namespaces Access Grid Missed adding this change to commit 504015e --- .../modext/widgets/security/modx.grid.user.group.namespace.js | 1 + 1 file changed, 1 insertion(+) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index 1d086dc2c24..b526e308741 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -87,6 +87,7 @@ MODx.grid.UserGroupNamespace = function(config) { ,baseParams: { action: 'Workspace/PackageNamespace/GetList', isGridFilter: true, + targetGrid: 'MODx.grid.UserGroupNamespace', usergroup: config.usergroup } ,listeners: { From fafe72184d1af4250f8785ba243974b709a58e72 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 19 Feb 2023 19:53:33 -0500 Subject: [PATCH 33/47] Remove Ext Statefulness from TVs Panel Avoids conflicts with our new tab tracking functionality, and it's generally less confusing to have the tab panel initially show its first tab instead of the last one visited --- .../assets/modext/widgets/element/modx.panel.tv.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/manager/assets/modext/widgets/element/modx.panel.tv.js b/manager/assets/modext/widgets/element/modx.panel.tv.js index 51c998dd736..52cd8ded14b 100644 --- a/manager/assets/modext/widgets/element/modx.panel.tv.js +++ b/manager/assets/modext/widgets/element/modx.panel.tv.js @@ -502,18 +502,7 @@ MODx.panel.TV = function(config) { ,record: config.record }],{ id: 'modx-tv-editor-tabs' - ,forceLayout: true ,deferredRender: false - ,stateful: true - ,stateId: 'modx-tv-tabpanel-'+config.tv - ,stateEvents: ['tabchange'] - ,hideMode: 'offsets' - ,anchor: '100%' - ,getState: function() { - return { - activeTab: this.items.indexOf(this.getActiveTab()) - }; - } })] ,useLoadingMask: true ,listeners: { From 53e86ff9973a812bcba2b5bfda6ad63f03849032 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 14 Mar 2023 00:39:39 +0000 Subject: [PATCH 34/47] Minor refinements for better Extras suppport Changes make identification of grid objects within a tab panel more robust/flexible --- manager/assets/modext/widgets/core/modx.tabs.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index c8f1ee1a1a4..ac31bd5cd5a 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,5 +1,4 @@ -MODx.Tabs = function(config) { - config = config || {}; +MODx.Tabs = function(config = {}) { Ext.applyIf(config, { enableTabScroll: true, layoutOnTabChange: true, @@ -40,7 +39,6 @@ MODx.Tabs = function(config) { NOTE: The currentTab is the previous one being navigated away from */ - if (newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' @@ -108,8 +106,7 @@ Ext.extend(MODx.Tabs, Ext.TabPanel, { }); Ext.reg('modx-tabs', MODx.Tabs); -MODx.VerticalTabs = function(config) { - config = config || {}; +MODx.VerticalTabs = function(config = {}) { Ext.applyIf(config, { cls: 'vertical-tabs-panel', headerCfg: { tag: 'div', cls: 'x-tab-panel-header vertical-tabs-header' }, From ce7907f9f7732611e02ad7e79c6ec916015ab33b Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 4 Apr 2023 10:45:50 -0400 Subject: [PATCH 35/47] Fix code quality issues --- .../Processors/System/Settings/GetAreas.php | 5 +- .../Workspace/PackageNamespace/GetList.php | 5 +- core/src/Revolution/modLexicon.php | 49 ++++++++++++------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/core/src/Revolution/Processors/System/Settings/GetAreas.php b/core/src/Revolution/Processors/System/Settings/GetAreas.php index 9a27ee521f5..4864cd2bbe5 100644 --- a/core/src/Revolution/Processors/System/Settings/GetAreas.php +++ b/core/src/Revolution/Processors/System/Settings/GetAreas.php @@ -1,4 +1,5 @@ isGridFilter && $this->getProperty('targetGrid', false) === 'MODx.grid.SettingsGrid') { $settingsType = $this->getProperty('targetSettingsType', 'system'); - switch($settingsType) { + switch ($settingsType) { case 'context': $settingsClass = modContextSetting::class; $foreignKeyWhere = $foreignKey @@ -158,7 +159,7 @@ public function getQuery() $namespaceColumn = $this->modx->escape('namespace'); $joinAlias = 'settingsCount'; $joinAliasEscaped = $this->modx->escape($joinAlias); - + $c = $this->modx->newQuery($settingsClass); $c->setClassAlias($alias); $c->leftJoin($settingsClass, $joinAlias, [ diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index 78014f07a14..dd2e7297c62 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -17,7 +17,6 @@ use MODX\Revolution\Processors\Model\GetListProcessor; use xPDO\Om\xPDOObject; use xPDO\Om\xPDOQuery; - use MODX\Revolution\modContextSetting; use MODX\Revolution\modSystemSetting; use MODX\Revolution\modUserGroupSetting; @@ -79,11 +78,11 @@ public function prepareQueryBeforeCount(xPDOQuery $c) limit results to only those namespaces present in the current grid. */ if ($this->isGridFilter && $targetGrid = $this->getProperty('targetGrid', false)) { - switch($targetGrid) { + switch ($targetGrid) { case 'MODx.grid.SettingsGrid': $settingsType = $this->getProperty('targetSettingsType', 'system'); $alias = 'settingsNamespace'; - switch($settingsType) { + switch ($settingsType) { case 'context': $settingsClass = modContextSetting::class; $foreignKeyWhere = $foreignKey ? [ $alias . '.context_key' => $this->modx->sanitizeString($foreignKey) ] : null ; diff --git a/core/src/Revolution/modLexicon.php b/core/src/Revolution/modLexicon.php index 9811901c0ce..914a3cfeb6d 100644 --- a/core/src/Revolution/modLexicon.php +++ b/core/src/Revolution/modLexicon.php @@ -1,4 +1,5 @@ _lexicon)) { $this->_lexicon[$language] = []; } - $this->_lexicon[$language] = is_array($this->_lexicon[$language]) ? array_merge($this->_lexicon[$language], - $entries) : $entries; + $this->_lexicon[$language] = is_array($this->_lexicon[$language]) + ? array_merge($this->_lexicon[$language], $entries) + : $entries + ; } } } @@ -271,18 +273,29 @@ public function loadCache($namespace = 'core', $topic = 'default', $language = ' $language = $this->modx->getOption('cultureKey', null, 'en'); } $key = $this->getCacheKey($namespace, $topic, $language); - $enableCache = ($namespace != 'core' && !$this->modx->getOption('cache_noncore_lexicon_topics', null, - true)) ? false : true; - + $enableCache = ($namespace != 'core' && !$this->modx->getOption('cache_noncore_lexicon_topics', null, true)) + ? false + : true + ; if (!$this->modx->cacheManager) { $this->modx->getCacheManager(); } $cached = $this->modx->cacheManager->get($key, [ - xPDO::OPT_CACHE_KEY => $this->modx->getOption('cache_lexicon_topics_key', null, self::CACHE_DIRECTORY), - xPDO::OPT_CACHE_HANDLER => $this->modx->getOption('cache_lexicon_topics_handler', null, - $this->modx->getOption(xPDO::OPT_CACHE_HANDLER)), - xPDO::OPT_CACHE_FORMAT => (integer)$this->modx->getOption('cache_lexicon_topics_format', null, - $this->modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)), + xPDO::OPT_CACHE_KEY => $this->modx->getOption( + 'cache_lexicon_topics_key', + null, + self::CACHE_DIRECTORY + ), + xPDO::OPT_CACHE_HANDLER => $this->modx->getOption( + 'cache_lexicon_topics_handler', + null, + $this->modx->getOption(xPDO::OPT_CACHE_HANDLER) + ), + xPDO::OPT_CACHE_FORMAT => (int)$this->modx->getOption( + 'cache_lexicon_topics_format', + null, + $this->modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP) + ), ]); if (!$enableCache || $cached == null) { $results = false; @@ -319,8 +332,10 @@ public function loadCache($namespace = 'core', $topic = 'default', $language = ' } } if (empty($cached)) { - $this->modx->log(xPDO::LOG_LEVEL_DEBUG, - "An error occurred while trying to cache {$key} (lexicon/language/namespace/topic)"); + $this->modx->log( + xPDO::LOG_LEVEL_DEBUG, + "An error occurred while trying to cache {$key} (lexicon/language/namespace/topic)" + ); } return $cached; @@ -393,14 +408,14 @@ public function getNamespaceList($language = 'en', $topic = '') : array { $namespaceList = []; if ($namespaces = $this->modx->getCollection(modNamespace::class)) { - foreach($namespaces as $namespace) { + foreach ($namespaces as $namespace) { $name = $namespace->get('name'); $corePath = $name === 'core' ? $this->modx->getOption('core_path', null, MODX_CORE_PATH) : $namespace->getCorePath() ; $lexiconPath = str_replace('//', '/', $corePath . '/lexicon/' . $language . '/'); - + if (!is_dir($lexiconPath)) { continue; } @@ -592,11 +607,11 @@ public function getFilterLanguageList($namespace = 'core', $topic = '') if ($language->isDir()) { $languageKey = $language->getFilename(); - + /* Only show languages that contain the selected topic - Note that all custom (new, user-created) topics for core entries will only be + Note that all custom (new, user-created) topics for core entries will only be present in the database, which is queried below, so no need to assess whether a file for a particular language and topic exists in that scenario */ From 1ebaa55efb4a6e3ead8d36dd6826c145424025bc Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 4 Apr 2023 10:58:07 -0400 Subject: [PATCH 36/47] Fix one missed CQ problem --- core/src/Revolution/modLexicon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/Revolution/modLexicon.php b/core/src/Revolution/modLexicon.php index 914a3cfeb6d..c3d6f34d956 100644 --- a/core/src/Revolution/modLexicon.php +++ b/core/src/Revolution/modLexicon.php @@ -404,7 +404,7 @@ public function getNamespacePath($namespace = 'core') * * @return array An array of Namespace names */ - public function getNamespaceList($language = 'en', $topic = '') : array + public function getNamespaceList($language = 'en', $topic = ''): array { $namespaceList = []; if ($namespaces = $this->modx->getCollection(modNamespace::class)) { From 92265d12ab38e01816be4bda55786e6735b202dd Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 4 Apr 2023 11:18:44 -0400 Subject: [PATCH 37/47] Make requested review change --- core/lexicon/en/default.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php index fce1dd78c23..58145332f7f 100644 --- a/core/lexicon/en/default.inc.php +++ b/core/lexicon/en/default.inc.php @@ -315,7 +315,7 @@ $_lang['orm_container_rename'] = 'Rename Container'; $_lang['orm_container_remove'] = 'Delete Container'; $_lang['orm_container_remove_confirm'] = 'Are you sure you want to delete this container and all attributes below it? This is irreversible.'; -$_lang['pagetitle'] = 'Resource‘s Title'; +$_lang['pagetitle'] = 'Resource\'s Title'; $_lang['page_title'] = 'Resource Title'; $_lang['parameter'] = 'Parameter'; $_lang['parameters'] = 'Parameters'; From 90d00471ee90f5feb074c6b614d844b6ff55e0c8 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 10 Sep 2023 00:42:20 -0400 Subject: [PATCH 38/47] Update modx.panel.template.js Remove legacy filtering param --- manager/assets/modext/widgets/element/modx.panel.template.js | 1 - 1 file changed, 1 deletion(-) diff --git a/manager/assets/modext/widgets/element/modx.panel.template.js b/manager/assets/modext/widgets/element/modx.panel.template.js index 18d5e0d1e49..0a7baaf487e 100644 --- a/manager/assets/modext/widgets/element/modx.panel.template.js +++ b/manager/assets/modext/widgets/element/modx.panel.template.js @@ -478,7 +478,6 @@ MODx.panel.Template = function(config) { },{ xtype: 'modx-grid-template-tv' ,cls:'main-wrapper' - ,urlFilters: ['category', 'query'] ,preventRender: true ,anchor: '100%' ,template: config.template From 91219e45fc2c729cfdfaf2e9898785273fcea2b5 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 12 Sep 2023 22:06:54 -0400 Subject: [PATCH 39/47] Required base class and utilities updates New methods and fixes to address issues found in PR review --- manager/assets/modext/util/utilities.js | 2 +- .../assets/modext/widgets/core/modx.grid.js | 54 ++++++++++-- .../assets/modext/widgets/core/modx.tabs.js | 87 +++++++++++-------- 3 files changed, 96 insertions(+), 47 deletions(-) diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index 4e01f439d36..b4efdb7e7b5 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -543,7 +543,7 @@ MODx.util.url = { Object.entries(filterData).forEach(([param, value]) => { params.set(param, value); }); - let newUrl = url.toString().replace(/%2F/g, '/'); + const newUrl = url.toString().replace(/%2F/g, '/'); window.history.replaceState(stateData, document.title, newUrl); } }, diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index 21f95c376db..41b321ba951 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -867,9 +867,31 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ ,hasNestedFilters: false ,currentLanguage: MODx.config.cultureKey || 'en' // removed MODx.request.language + + /** + * Applies a value persisted via URL (MODx.request) for use in grid and filter params. Used when multiple + * grids make use of the same data point, but the request value should apply to only one of them. + * (Primary use-case is in the User Group Access Permissions area.) + * + * @param {Number} tabPanelIndex The zero-based index of the tab panel containing this grid + * @param {String} requestKey The data point (policy, namespace, etc) + * @param {String} tabPanelType The panel type this grid is a child of + * @param {Boolean} setEmptyToString - For some components, like combos, setting to null is better + * when no value is present. Set this to true for components that prefer an empty string + * @returns {Number|String} Decoded param value + */ + ,applyRequestFilter: function(tabPanelIndex, requestKey = 'policy', tabPanelType = 'vtab', setEmptyToString = false) { + const emptyVal = setEmptyToString ? '' : null ; + return Object.prototype.hasOwnProperty.call(MODx.request, tabPanelType) + && parseInt(MODx.request[tabPanelType], 10) === tabPanelIndex + && Object.prototype.hasOwnProperty.call(MODx.request, requestKey) + ? MODx.util.url.getParamValue(requestKey) + : emptyVal + ; + } /** - * @property {Function} applyGridFilter - Filters the grid data by the passed filter component (field) + * Filters the grid data by the passed filter component (field) * * @param {Object} cmp - The filter field's Ext.Component object * @param {String} param - The record index to apply the filter on; @@ -950,14 +972,15 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ bottomToolbar = this.getBottomToolbar(), data = Array.isArray(items) ? items : items.split(',') ; - data.forEach(item => {; + data.forEach(item => { const itemData = item.replace(/\s+/g, '').split(':'), itemId = itemData[0], itemDefaultVal = itemData.length == 2 ? itemData[1] : null , cmp = this.getFilterComponent(itemId), - param = MODx.util.url.getParamNameFromCmp(cmp) + param = MODx.util.url.getParamNameFromCmp(cmp), + isCombo = cmp?.xtype?.includes('combo') ; - if (cmp.xtype.includes('combo')) { + if (isCombo) { if (itemDefaultVal === '') { cmp.setValue(null); } else { @@ -978,6 +1001,8 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ break; } }); + } + if (isCombo) { cmp.getStore().load(); } store.baseParams[param] = itemDefaultVal; @@ -1068,17 +1093,28 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ /** * @property {Function} getQueryFilterField - Creates the query field component configuration * - * @param {String} filterId - Optional, itemId for the query filter; specify a unique id to avoid conflicts - * when multiple query fields may be present (e.g., when multiple tabs have a grid and query filter) + * @param {String} filterSpec - Optional, specifies a unique itemId and current request value to avoid conflicts when + * multiple query fields are present in the same view (e.g., when multiple tabs have a grid with a query filter). + * Format = 'id:value' * @param {String} implementation - Optional, an identifier used to assign grid-specific behavior * @return {Object} */ - ,getQueryFilterField: function(filterId = 'filter-query', implementation = 'default') { + ,getQueryFilterField: function(filterSpec = 'filter-query', implementation = 'default') { + let queryValue = ''; + const + filterSpecs = filterSpec.split(':'), + filterId = filterSpecs[0].trim() + ; + if (filterSpecs.length === 2) { + queryValue = filterSpecs[1]; + } else { + queryValue = MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '' ; + } return { xtype: 'textfield', - itemId: filterId.trim(), + itemId: filterId, emptyText: _('search'), - value: MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '', + value: queryValue, cls: 'filter-query', listeners: { change: { diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index ac31bd5cd5a..175c8c8c3ac 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -43,42 +43,7 @@ MODx.Tabs = function(config = {}) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; - let itemsSource, - gridObj = null - ; - if (resetVerticalTabPanelFilters) { - itemsSource = changedBetweenVtabs - ? currentTab.items - : currentTab.items.items[0].activeTab.items; - } else { - itemsSource = currentTab.items; - } - if (itemsSource.length > 0) { - gridObj = this.findGridObject(itemsSource); - /* - Grids placed in an atypical structure, such as the ACLs User Group grid that - is activated via the User Groups tree, require further searching - */ - if (!gridObj && itemsSource?.map['modx-tree-panel-usergroup']) { - itemsSource = itemsSource.map['modx-tree-panel-usergroup'].items; - gridObj = this.findGridObject(itemsSource); - } - } - if (gridObj) { - const toolbar = gridObj.getTopToolbar(), - filterIds = [] - ; - if (toolbar && toolbar.items.items.length > 0) { - toolbar.items.items.forEach(cmp => { - if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { - filterIds.push(cmp.itemId); - } - }); - } - if (filterIds.length > 0) { - gridObj.clearGridFilters(filterIds); - } - } + this.clearFiltersBeforeChange(currentTab, resetVerticalTabPanelFilters, changedBetweenVtabs); } } }); @@ -87,7 +52,7 @@ MODx.Tabs = function(config = {}) { }; Ext.extend(MODx.Tabs, Ext.TabPanel, { /** - * @property {Function} findGridObject - Search for and return a grid object with a given items array + * Search for and return a grid object based on a given items array * * @param {String} itemsSource - The config items array to search within * @return {MODx.grid.Grid|undefined} @@ -102,6 +67,54 @@ Ext.extend(MODx.Tabs, Ext.TabPanel, { this.findGridObject(nextItemsSource); } return undefined; + }, + + /** + * Sets a TabPanel grid and its toolbar to their default states + * + * @param {MODx.TabPanel} tabObj The tab panel containing filters and grid query to clear + * @param {Boolean} resetVtabFilters Whether the targeted tab for clearing is a vertical tab + * @param {Boolean} changedVtabs Whether both tab being moved away from and tab that is the current target are vertical tabs + */ + clearFiltersBeforeChange: function(tabObj, resetVtabFilters, changedVtabs) { + let itemsSource, + gridObj = null + ; + if (resetVtabFilters) { + itemsSource = changedVtabs + ? tabObj.items + : tabObj.items.items[0].activeTab.items + ; + } else { + itemsSource = tabObj.items; + } + if (itemsSource.length > 0) { + gridObj = this.findGridObject(itemsSource); + /* + Grids placed in an atypical structure, such as the ACLs User Group grid that + is activated via the User Groups tree, require further searching + */ + if (!gridObj && itemsSource?.map['modx-tree-panel-usergroup']) { + itemsSource = itemsSource.map['modx-tree-panel-usergroup'].items; + gridObj = this.findGridObject(itemsSource); + } + } + if (gridObj) { + const + toolbar = gridObj.getTopToolbar(), + filterIds = [] + ; + if (toolbar && toolbar.items.items.length > 0) { + toolbar.items.items.forEach(cmp => { + if (cmp.xtype && (cmp.xtype.includes('combo') || cmp.xtype === 'textfield') && cmp.itemId) { + filterIds.push(cmp.itemId); + } + }); + } + if (filterIds.length > 0) { + gridObj.clearGridFilters(filterIds); + } + } } }); Ext.reg('modx-tabs', MODx.Tabs); From 560f48eea2b6cdc4785dcbd897934b6577775b3c Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 12 Sep 2023 22:13:51 -0400 Subject: [PATCH 40/47] Settings grid updates Fixes and tweaks to address PR review issues --- .../modext/widgets/core/modx.grid.settings.js | 26 +++++++++---------- .../security/modx.grid.user.group.settings.js | 8 +++--- .../security/modx.grid.user.settings.js | 1 + .../system/modx.grid.context.settings.js | 1 + 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.grid.settings.js b/manager/assets/modext/widgets/core/modx.grid.settings.js index f1eeccc1ce1..734995fecab 100644 --- a/manager/assets/modext/widgets/core/modx.grid.settings.js +++ b/manager/assets/modext/widgets/core/modx.grid.settings.js @@ -7,14 +7,21 @@ * @xtype modx-grid-settings */ MODx.grid.SettingsGrid = function(config = {}) { + const + settingsType = this.settingsType || 'system', + queryValue = this.querySpec ? this.applyRequestFilter(...this.querySpec) : MODx.util.url.getParamValue('query', true) + ; this.exp = new Ext.grid.RowExpander({ tpl: new Ext.XTemplate( '

    {[MODx.util.safeHtml(values.description_trans)]}

    ' ) }); this.areaFilterValue = MODx.util.url.getParamValue('area'); - this.namespaceFilterValue = MODx.util.url.getParamValue('ns'); - + // Settings grid in User Group ACLs view needs special handling when applying filter via request param + this.namespaceFilterValue = settingsType === 'usergroup' + ? this.applyRequestFilter(3, 'ns', 'tab') + : MODx.util.url.getParamValue('ns') + ; if (!config.tbar) { config.tbar = [{ text: _('create') @@ -39,8 +46,6 @@ MODx.grid.SettingsGrid = function(config = {}) { config.tbar.push( '->', { - // xtype: 'modx-combo-namespace' - // ,itemId: 'filter-namespace' /** * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 * @@ -57,7 +62,7 @@ MODx.grid.SettingsGrid = function(config = {}) { ,minChars: 2 ,forceSelection: true ,width: 200 - ,value: MODx.request.ns || null + ,value: this.namespaceFilterValue ,baseParams: { action: 'Workspace/PackageNamespace/GetList', area: this.areaFilterValue, @@ -94,8 +99,6 @@ MODx.grid.SettingsGrid = function(config = {}) { } }, { - // xtype: 'modx-combo-area' - // ,itemId: 'filter-area' /** * @deprecated use of id config property deprecated in 3.0, to be removed in 3.1 * @@ -143,7 +146,7 @@ MODx.grid.SettingsGrid = function(config = {}) { } } }, - this.getQueryFilterField(), + this.getQueryFilterField(`filter-query:${queryValue}`), this.getClearFiltersButton( 'filter-ns:, filter-area:, filter-query', 'filter-area:namespace, filter-ns:area' @@ -449,7 +452,6 @@ MODx.combo.Area = function(config = {}) { Ext.extend(MODx.combo.Area,MODx.combo.ComboBox); Ext.reg('modx-combo-area',MODx.combo.Area); - MODx.window.CreateSetting = function(config = {}) { config.keyField = config.keyField || {}; Ext.applyIf(config,{ @@ -529,7 +531,7 @@ MODx.window.CreateSetting = function(config = {}) { ,fieldLabel: _('namespace') ,name: 'namespace' ,id: 'modx-cs-namespace' - ,value: config.grid.getTopToolbar().getComponent('filter-namespace').getValue() + ,value: config.grid.getTopToolbar().getComponent('filter-ns').getValue() ,anchor: '100%' },{ xtype: 'label' @@ -565,7 +567,7 @@ MODx.window.CreateSetting = function(config = {}) { this.on('show',function() { this.reset(); this.setValues({ - namespace: config.grid.getTopToolbar().getComponent('filter-namespace').value + namespace: config.grid.getTopToolbar().getComponent('filter-ns').value ,area: config.grid.getTopToolbar().getComponent('filter-area').value }); },this); @@ -573,7 +575,6 @@ MODx.window.CreateSetting = function(config = {}) { Ext.extend(MODx.window.CreateSetting,MODx.Window); Ext.reg('modx-window-setting-create',MODx.window.CreateSetting); - MODx.combo.xType = function(config = {}) { Ext.applyIf(config,{ store: new Ext.data.SimpleStore({ @@ -613,7 +614,6 @@ MODx.combo.xType = function(config = {}) { Ext.extend(MODx.combo.xType,Ext.form.ComboBox); Ext.reg('modx-combo-xtype-spec',MODx.combo.xType); - MODx.window.UpdateSetting = function(config = {}) { this.ident = config.ident || 'modx-uss-'+Ext.id(); Ext.applyIf(config,{ diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js index 07ec14a1444..704d2d5250c 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.settings.js @@ -7,6 +7,8 @@ * @xtype modx-grid-group-settings */ MODx.grid.GroupSettings = function(config = {}) { + this.settingsType = 'usergroup'; + this.querySpec = [3, 'query', 'tab', true]; Ext.applyIf(config,{ title: _('user_group_settings') ,id: 'modx-grid-group-settings' @@ -14,8 +16,8 @@ MODx.grid.GroupSettings = function(config = {}) { ,baseParams: { action: 'Security/Group/Setting/GetList', group: config.group, - namespace: MODx.util.url.getParamValue('ns'), - area: MODx.util.url.getParamValue('area') + namespace: this.applyRequestFilter(3, 'ns', 'tab'), + area: MODx.util.url.getParamValue('area') || null } ,saveParams: { group: config.group @@ -24,7 +26,7 @@ MODx.grid.GroupSettings = function(config = {}) { ,fk: config.group ,tbar: [{ text: _('create') - ,cls:'primary-button' + ,cls: 'primary-button' ,scope: this ,handler: { xtype: 'modx-window-setting-create' diff --git a/manager/assets/modext/widgets/security/modx.grid.user.settings.js b/manager/assets/modext/widgets/security/modx.grid.user.settings.js index 8b75005abf0..15443505cd3 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.settings.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.settings.js @@ -7,6 +7,7 @@ * @xtype modx-grid-user-settings */ MODx.grid.UserSettings = function(config = {}) { + this.settingsType = 'user'; Ext.applyIf(config,{ title: _('user_settings') ,id: 'modx-grid-user-settings' diff --git a/manager/assets/modext/widgets/system/modx.grid.context.settings.js b/manager/assets/modext/widgets/system/modx.grid.context.settings.js index 625188bcb04..f66cc90d93b 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.settings.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.settings.js @@ -7,6 +7,7 @@ * @xtype modx-grid-context-settings */ MODx.grid.ContextSettings = function(config = {}) { + this.settingsType = 'context'; Ext.applyIf(config,{ title: _('context_settings') ,id: 'modx-grid-context-settings' From 68265c2502968873c50d106c518b0a59beb1ab69 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 12 Sep 2023 22:19:51 -0400 Subject: [PATCH 41/47] ACL tab content updates Fixes to address PR review issue --- .../security/modx.grid.access.policy.js | 5 +++-- .../modx.grid.access.policy.template.js | 5 +++-- .../security/modx.panel.groups.roles.js | 4 ++-- .../widgets/security/modx.panel.user.group.js | 18 ++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.js index 16fe5c2899e..4631ca43d44 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.js @@ -41,6 +41,7 @@ Ext.reg('modx-panel-access-policies',MODx.panel.AccessPolicies); * @xtype modx-grid-access-policy */ MODx.grid.AccessPolicy = function(config = {}) { + const queryValue = this.applyRequestFilter(2, 'query', 'tab', true); this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy' @@ -122,8 +123,8 @@ MODx.grid.AccessPolicy = function(config = {}) { }] }, '->', - this.getQueryFilterField('filter-query-policy'), - this.getClearFiltersButton() + this.getQueryFilterField(`filter-query-policy:${queryValue}`), + this.getClearFiltersButton('filter-query-policy') ] }); MODx.grid.AccessPolicy.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js index 190034c52a0..8fee8c4f595 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.template.js @@ -41,6 +41,7 @@ Ext.reg('modx-panel-access-policy-templates',MODx.panel.AccessPolicyTemplates); * @xtype modx-grid-access-policy */ MODx.grid.AccessPolicyTemplate = function(config = {}) { + const queryValue = this.applyRequestFilter(3, 'query', 'tab', true); this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ id: 'modx-grid-access-policy-template' @@ -121,8 +122,8 @@ MODx.grid.AccessPolicyTemplate = function(config = {}) { }] }, '->', - this.getQueryFilterField('filter-query-policy-template'), - this.getClearFiltersButton() + this.getQueryFilterField(`filter-query-policy-template:${queryValue}`), + this.getClearFiltersButton('filter-query-policy-template') ] }); MODx.grid.AccessPolicyTemplate.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js index 7b0a3496e7e..3d09269c6dd 100644 --- a/manager/assets/modext/widgets/security/modx.panel.groups.roles.js +++ b/manager/assets/modext/widgets/security/modx.panel.groups.roles.js @@ -49,9 +49,9 @@ MODx.panel.GroupsRoles = function(config = {}) { }); if (MODx.perm.usergroup_user_list) { - usergroupTree.on('click', function(node, e){ + usergroupTree.on('click', function(node, e) { this.currentGroupId = MODx.util.tree.getGroupIdFromNode(node); - Ext.getCmp('modx-usergroup-users').clearGridFilters('filter-query'); + Ext.getCmp('modx-usergroup-users').clearGridFilters('filter-query-users'); if (this.currentGroupId > 0) { MODx.util.url.setParams({ group: this.currentGroupId, diff --git a/manager/assets/modext/widgets/security/modx.panel.user.group.js b/manager/assets/modext/widgets/security/modx.panel.user.group.js index 23707b3b639..0224cce67d5 100644 --- a/manager/assets/modext/widgets/security/modx.panel.user.group.js +++ b/manager/assets/modext/widgets/security/modx.panel.user.group.js @@ -165,7 +165,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,usergroup: config.record.id ,autoHeight: true - ,width: '97%' ,listeners: { 'afterRemoveRow': {fn:this.markDirty,scope:this} ,'afteredit': {fn:this.markDirty,scope:this} @@ -187,7 +186,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,usergroup: config.record.id ,autoHeight: true - ,width: '97%' ,listeners: { 'afterRemoveRow': {fn:this.markDirty,scope:this} ,'afteredit': {fn:this.markDirty,scope:this} @@ -209,7 +207,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,usergroup: config.record.id ,autoHeight: true - ,width: '97%' ,listeners: { 'afterRemoveRow': {fn:this.markDirty,scope:this} ,'afteredit': {fn:this.markDirty,scope:this} @@ -231,7 +228,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,usergroup: config.record.id ,autoHeight: true - ,width: '97%' }] }] ,listeners: { @@ -261,7 +257,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,usergroup: config.record.id ,autoHeight: true - ,width: '97%' ,listeners: { 'afterRemoveRow': {fn:this.markDirty,scope:this} ,'updateRole': {fn:this.markDirty,scope:this} @@ -281,7 +276,6 @@ MODx.panel.UserGroup = function(config) { ,preventRender: true ,group: config.record.id ,autoHeight: true - ,width: '97%' }] }] ,listeners: { @@ -341,8 +335,12 @@ Ext.reg('modx-panel-user-group',MODx.panel.UserGroup); * @param {Object} config An object of configuration properties * @xtype modx-grid-user-group-users */ -MODx.grid.UserGroupUsers = function(config) { - config = config || {}; +MODx.grid.UserGroupUsers = function(config = {}) { + const + /** @var targetTab This grid shows in one of two places as of 3.0.x, in the ACLs summary view, and within a specific group’s ACLs view (in different tabs) */ + targetTab = MODx.request.a === 'security/permission' ? 0 : 2 , + queryValue = this.applyRequestFilter(targetTab, 'query', 'tab', true) + ; Ext.applyIf(config,{ title: '' ,id: 'modx-grid-user-group-users' @@ -402,8 +400,8 @@ MODx.grid.UserGroupUsers = function(config) { ,hidden: MODx.perm.usergroup_user_edit == 0 }, '->', - this.getQueryFilterField('filter-query', 'user-group-users'), - this.getClearFiltersButton() + this.getQueryFilterField(`filter-query-users:${queryValue}`, 'user-group-users'), + this.getClearFiltersButton('filter-query-users') ] }); MODx.grid.UserGroupUsers.superclass.constructor.call(this,config); From c1c0d2308c246df9e6e7a13924b9f59d25ae4c47 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 12 Sep 2023 22:22:11 -0400 Subject: [PATCH 42/47] Dashboards grid fixes Fixes to address PR review issues --- .../modext/widgets/system/modx.grid.dashboard.widgets.js | 3 ++- .../assets/modext/widgets/system/modx.panel.dashboards.js | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js index a21e8b3c3be..aaea2ab9710 100644 --- a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js +++ b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js @@ -5,6 +5,7 @@ * @xtype modx-grid-dashboard-widgets */ MODx.grid.DashboardWidgets = function(config = {}) { + const queryValue = this.applyRequestFilter(1, 'query', 'tab', true); this.exp = new Ext.grid.RowExpander({ tpl: new Ext.Template( '

    {description_trans}

    ' @@ -76,7 +77,7 @@ MODx.grid.DashboardWidgets = function(config = {}) { }] }, '->', - this.getQueryFilterField('filter-query-dashboardWidgets'), + this.getQueryFilterField(`filter-query-dashboardWidgets:${queryValue}`), this.getClearFiltersButton('filter-query-dashboardWidgets') ] }); diff --git a/manager/assets/modext/widgets/system/modx.panel.dashboards.js b/manager/assets/modext/widgets/system/modx.panel.dashboards.js index 940f47487d6..1be48b36c5d 100644 --- a/manager/assets/modext/widgets/system/modx.panel.dashboards.js +++ b/manager/assets/modext/widgets/system/modx.panel.dashboards.js @@ -49,9 +49,8 @@ Ext.reg('modx-panel-dashboards',MODx.panel.Dashboards); * @param {Object} config An object of configuration properties * @xtype modx-grid-dashboards */ -MODx.grid.Dashboards = function(config) { - config = config || {}; - +MODx.grid.Dashboards = function(config = {}) { + const queryValue = this.applyRequestFilter(0, 'query', 'tab', true); this.sm = new Ext.grid.CheckboxSelectionModel(); Ext.applyIf(config,{ url: MODx.config.connector_url @@ -125,7 +124,7 @@ MODx.grid.Dashboards = function(config) { } } }, - this.getQueryFilterField(), + this.getQueryFilterField(`filter-query:${queryValue}`), this.getClearFiltersButton('filter-usergroup, filter-query') ] }); From d6b3bda559725039955f1f64f6474e5a0ee3883a Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 12 Sep 2023 22:23:53 -0400 Subject: [PATCH 43/47] ACLs - User Group Permissions fixes Fixes to address PR review issues --- .../security/modx.grid.user.group.category.js | 4 +-- .../security/modx.grid.user.group.context.js | 7 ++--- .../modx.grid.user.group.namespace.js | 26 +++++++++---------- .../security/modx.grid.user.group.resource.js | 4 +-- .../security/modx.grid.user.group.source.js | 4 +-- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index a21d6ad2808..74a87aec28a 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -17,7 +17,7 @@ MODx.grid.UserGroupCategory = function(config = {}) { action: 'Security/Access/UserGroup/Category/GetList' ,usergroup: config.usergroup ,category: MODx.request.category || null - ,policy: MODx.request.policy || null + ,policy: this.applyRequestFilter(2) } ,fields: [ 'id', @@ -109,7 +109,7 @@ MODx.grid.UserGroupCategory = function(config = {}) { ,emptyText: _('filter_by_policy') ,width: 180 ,allowBlank: true - ,value: MODx.request.policy || null + ,value: this.applyRequestFilter(2) ,baseParams: { action: 'Security/Access/Policy/GetList', group: 'Element,Object', diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js index 9adffa56c0b..fe6398cf10d 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.context.js @@ -18,7 +18,7 @@ MODx.grid.UserGroupContext = function(config) { action: 'Security/Access/UserGroup/Context/GetList' ,usergroup: config.usergroup ,context: MODx.request.context || null - ,policy: MODx.request.policy || null + ,policy: this.applyRequestFilter(0) } ,fields: [ 'id', @@ -46,7 +46,6 @@ MODx.grid.UserGroupContext = function(config) { { header: _('context') ,dataIndex: 'target' - ,width: 120 ,sortable: true ,xtype: 'templatecolumn' ,tpl: this.getLinkTemplate('context/update', 'target', { @@ -55,13 +54,11 @@ MODx.grid.UserGroupContext = function(config) { },{ header: _('minimum_role') ,dataIndex: 'authority_name' - ,width: 100 ,xtype: 'templatecolumn' ,tpl: this.getLinkTemplate('security/permission', 'authority_name') },{ header: _('policy') ,dataIndex: 'policy_name' - ,width: 200 ,sortable: true ,xtype: 'templatecolumn' ,tpl: this.getLinkTemplate('security/access/policy/update', 'policy_name', { @@ -105,7 +102,7 @@ MODx.grid.UserGroupContext = function(config) { ,emptyText: _('filter_by_policy') ,width: 180 ,allowBlank: true - ,value: MODx.request.policy || null + ,value: this.applyRequestFilter(0) ,baseParams: { action: 'Security/Access/Policy/GetList', group: 'Administrator,Context,Object', diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js index b526e308741..d2abd6d63da 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.namespace.js @@ -17,21 +17,21 @@ MODx.grid.UserGroupNamespace = function(config) { ,baseParams: { action: 'Security/Access/UserGroup/AccessNamespace/GetList' ,usergroup: config.usergroup - ,namespace: MODx.request.ns || null - ,policy: MODx.request.policy || null + ,namespace: this.applyRequestFilter(4, 'ns') + ,policy: this.applyRequestFilter(4) } ,fields: [ 'id', - 'target', - 'name', - 'principal', - 'authority', - 'authority_name', + 'target', + 'name', + 'principal', + 'authority', + 'authority_name', 'policy', 'policy_name', 'context_key', - 'permissions', - 'cls' + 'permissions', + 'cls' ] ,paging: true ,hideMode: 'offsets' @@ -83,7 +83,7 @@ MODx.grid.UserGroupNamespace = function(config) { ,editable: false ,width: 200 ,allowBlank: true - ,value: MODx.request.ns || null + ,value: this.applyRequestFilter(4, 'ns') ,baseParams: { action: 'Workspace/PackageNamespace/GetList', isGridFilter: true, @@ -96,8 +96,8 @@ MODx.grid.UserGroupNamespace = function(config) { this.updateDependentFilter('filter-policy-namespace', 'namespace', record.data.name); /* There's an odd conflict in the processor when using 'namespace' as the - query param, therefor the alternate param 'ns' is used this listener, its component value, and in the value of - this grid's main baseParams config + query param, therefor the alternate param 'ns' is used for this listener, its component value, + and in the value of this grid's main baseParams config */ this.applyGridFilter(cmp, 'ns'); }, @@ -110,7 +110,7 @@ MODx.grid.UserGroupNamespace = function(config) { ,emptyText: _('filter_by_policy') ,width: 180 ,allowBlank: true - ,value: MODx.request.policy || null + ,value: this.applyRequestFilter(4) ,baseParams: { action: 'Security/Access/Policy/GetList', group: 'Namespace', diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js index 788048fbb23..3802cba4ffa 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.resource.js @@ -18,7 +18,7 @@ MODx.grid.UserGroupResourceGroup = function(config) { action: 'Security/Access/UserGroup/ResourceGroup/GetList' ,usergroup: config.usergroup ,resourceGroup: MODx.request.resourceGroup || null - ,policy: MODx.request.policy || null + ,policy: this.applyRequestFilter(1) } ,fields: [ 'id', @@ -112,7 +112,7 @@ MODx.grid.UserGroupResourceGroup = function(config) { ,emptyText: _('filter_by_policy') ,width: 180 ,allowBlank: true - ,value: MODx.request.policy || null + ,value: this.applyRequestFilter(1) ,baseParams: { action: 'Security/Access/Policy/GetList', group: 'Resource,Object', diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js index 46efb9bdcdc..75e4a4b91a9 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.source.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.source.js @@ -18,7 +18,7 @@ MODx.grid.UserGroupSource = function(config) { action: 'Security/Access/UserGroup/Source/GetList' ,usergroup: config.usergroup ,source: MODx.request.source || null - ,policy: MODx.request.policy || null + ,policy: this.applyRequestFilter(3) } ,fields: [ 'id', @@ -105,7 +105,7 @@ MODx.grid.UserGroupSource = function(config) { ,emptyText: _('filter_by_policy') ,width: 180 ,allowBlank: true - ,value: MODx.request.policy || null + ,value: this.applyRequestFilter(3) ,baseParams: { action: 'Security/Access/Policy/GetList', group: 'MediaSource', From 1fc4312b85f428bc0b4d74dc293a9604e1949983 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 25 Oct 2023 10:29:53 -0400 Subject: [PATCH 44/47] Post-review fixes for system settings grids --- manager/assets/modext/widgets/core/modx.grid.settings.js | 2 +- .../modext/widgets/system/modx.grid.system.event.js | 8 ++++---- .../modext/widgets/system/modx.panel.system.settings.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.grid.settings.js b/manager/assets/modext/widgets/core/modx.grid.settings.js index 734995fecab..ba4ebeef9ff 100644 --- a/manager/assets/modext/widgets/core/modx.grid.settings.js +++ b/manager/assets/modext/widgets/core/modx.grid.settings.js @@ -284,7 +284,7 @@ MODx.grid.SettingsGrid = function(config = {}) { this.addEvents('createSetting', 'updateSetting'); const gridFilterData = [ - { filterId: 'filter-namespace', dependentParams: ['area'] }, + { filterId: 'filter-ns', dependentParams: ['area'] }, { filterId: 'filter-area', dependentParams: ['namespace'] } ]; diff --git a/manager/assets/modext/widgets/system/modx.grid.system.event.js b/manager/assets/modext/widgets/system/modx.grid.system.event.js index b53797db675..6588a9dc64f 100644 --- a/manager/assets/modext/widgets/system/modx.grid.system.event.js +++ b/manager/assets/modext/widgets/system/modx.grid.system.event.js @@ -6,8 +6,8 @@ * @param {Object} config An object of options. * @xtype modx-grid-system-event */ -MODx.grid.SystemEvent = function(config) { - config = config || {}; +MODx.grid.SystemEvent = function(config = {}) { + const queryValue = this.applyRequestFilter(1, 'query', 'tab', true); Ext.applyIf(config,{ title: _('system_events') ,url: MODx.config.connector_url @@ -60,8 +60,8 @@ MODx.grid.SystemEvent = function(config) { } }, '->', - this.getQueryFilterField(), - this.getClearFiltersButton() + this.getQueryFilterField(`filter-query-events:${queryValue}`), + this.getClearFiltersButton('filter-query-events') ] }); MODx.grid.SystemEvent.superclass.constructor.call(this,config); diff --git a/manager/assets/modext/widgets/system/modx.panel.system.settings.js b/manager/assets/modext/widgets/system/modx.panel.system.settings.js index d853be2c84f..e2250fe9762 100644 --- a/manager/assets/modext/widgets/system/modx.panel.system.settings.js +++ b/manager/assets/modext/widgets/system/modx.panel.system.settings.js @@ -62,8 +62,8 @@ Ext.reg('modx-panel-system-settings',MODx.panel.SystemSettings); * @param {Object} config An object of options. * @xtype modx-grid-system-settings */ -MODx.grid.SystemSettings = function(config) { - config = config || {}; +MODx.grid.SystemSettings = function(config = {}) { + this.querySpec = [0, 'query', 'tab', true]; MODx.grid.SystemSettings.superclass.constructor.call(this, config); }; Ext.extend(MODx.grid.SystemSettings, MODx.grid.SettingsGrid); From dd91b49b133b45a071027211d32fedc9d3dae908 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 25 Oct 2023 10:31:46 -0400 Subject: [PATCH 45/47] Post-review fixes for packages grid Adds new component id for Package grid that matches the pattern of all others ('modx-grid-[x]') --- manager/assets/modext/workspace/package.containers.js | 3 ++- manager/assets/modext/workspace/package.grid.js | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/manager/assets/modext/workspace/package.containers.js b/manager/assets/modext/workspace/package.containers.js index 07dc9fdaacb..12aab289468 100644 --- a/manager/assets/modext/workspace/package.containers.js +++ b/manager/assets/modext/workspace/package.containers.js @@ -20,7 +20,8 @@ MODx.panel.Packages = function(config) { } ,activeItem: 0 ,items:[{ - xtype:'modx-package-grid' + xtype: 'modx-grid-package' + // Deprecate id, change to modx-grid-package in future release ,id:'modx-package-grid' ,bodyCssClass: 'grid-with-buttons' },{ diff --git a/manager/assets/modext/workspace/package.grid.js b/manager/assets/modext/workspace/package.grid.js index 69abd8aa225..c25d19b2098 100644 --- a/manager/assets/modext/workspace/package.grid.js +++ b/manager/assets/modext/workspace/package.grid.js @@ -4,10 +4,9 @@ * @class MODx.grid.Package * @extends MODx.grid.Grid * @param {Object} config An object of options. - * @xtype modx-package-grid + * @xtype modx-grid-package */ -MODx.grid.Package = function(config) { - config = config || {}; +MODx.grid.Package = function(config = {}) { this.exp = new Ext.grid.RowExpander({ tpl : new Ext.XTemplate( '

    {readme}

    ' @@ -81,6 +80,7 @@ MODx.grid.Package = function(config) { Ext.applyIf(config,{ title: _('packages') + // Deprecate id, change to modx-grid-package in future release ,id: 'modx-package-grid' ,url: MODx.config.connector_url ,action: 'Workspace/Packages/GetList' @@ -484,7 +484,9 @@ Ext.extend(MODx.grid.Package,MODx.grid.Grid,{ return this.console; } }); -Ext.reg('modx-package-grid',MODx.grid.Package); +Ext.reg('modx-grid-package', MODx.grid.Package); +// Deprecated, keeping for backward compatability; remove in future version +Ext.reg('modx-package-grid', MODx.grid.Package); /** * @class MODx.window.PackageUpdate From 38992d1ffa4b640ab206501ffcd093845ecd8f78 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 29 Oct 2023 23:07:59 -0400 Subject: [PATCH 46/47] UG Category tweaks Minor changes made to the category filter combo to make it correctly display category names when persisted via state/URL --- core/src/Revolution/Processors/Element/Category/GetList.php | 2 +- manager/assets/modext/widgets/core/modx.combo.js | 1 - .../modext/widgets/security/modx.grid.user.group.category.js | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/Revolution/Processors/Element/Category/GetList.php b/core/src/Revolution/Processors/Element/Category/GetList.php index fd4357da755..6b420ff3786 100644 --- a/core/src/Revolution/Processors/Element/Category/GetList.php +++ b/core/src/Revolution/Processors/Element/Category/GetList.php @@ -181,7 +181,7 @@ public function prepareQueryBeforeCount(xPDOQuery $c) public function prepareQueryAfterCount(xPDOQuery $c) { - if ($this->getProperty('sort') == 'category') { + if (!$this->isGridFilter && $this->getProperty('sort') == 'category') { $c->sortby('parent', $this->getProperty('dir', 'ASC')); } $id = $this->getProperty('id', ''); diff --git a/manager/assets/modext/widgets/core/modx.combo.js b/manager/assets/modext/widgets/core/modx.combo.js index fb63f9b9ffd..931c06756ac 100644 --- a/manager/assets/modext/widgets/core/modx.combo.js +++ b/manager/assets/modext/widgets/core/modx.combo.js @@ -434,7 +434,6 @@ MODx.combo.Category = function(config) { ,hiddenName: 'category' ,displayField: 'name' ,valueField: 'id' - ,mode: 'remote' ,fields: ['id','category','parent','name'] ,forceSelection: true ,typeAhead: false diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js index 74a87aec28a..b0c8680fa7b 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.category.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.category.js @@ -88,6 +88,7 @@ MODx.grid.UserGroupCategory = function(config = {}) { ,emptyText: _('filter_by_category') ,width: 200 ,allowBlank: true + ,displayField: 'category' ,value: MODx.request.category || null ,baseParams: { action: 'Element/Category/GetList', From 334d553950149b8aebe3cde093f3647aa9ee3d81 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 29 Oct 2023 23:09:06 -0400 Subject: [PATCH 47/47] Base class updates Additional logic to fix issues raised in PR review --- .../assets/modext/widgets/core/modx.grid.js | 13 +++- .../assets/modext/widgets/core/modx.tabs.js | 74 +++++++++++++++---- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index 41b321ba951..c1a6c83abdf 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -580,6 +580,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ case 0: md.css = 'red'; return _('no'); + // no default } } @@ -909,7 +910,14 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ activeParentTabIdx ; if (!Ext.isEmpty(filterValue)) { + // Add param to URL when filter has a value urlParams[param] = filterValue; + } else if (MODx.request[param]) { + /* + Maintain params in URL when already present in URL. Prevents removal of + filter params when reloading or navigating to a URL that includes filter params. + */ + urlParams[param] = MODx.request[param]; } else { MODx.util.url.clearParam(cmp); } @@ -918,7 +926,6 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } else { store.baseParams[param] = filterValue; } - if (tabPanel) { /* Determine if this is a vertical tab panel; if so there will also be a @@ -1003,7 +1010,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ }); } if (isCombo) { - cmp.getStore().load(); + if (cmp.mode !== 'local') { + cmp.getStore().load(); + } } store.baseParams[param] = itemDefaultVal; }); diff --git a/manager/assets/modext/widgets/core/modx.tabs.js b/manager/assets/modext/widgets/core/modx.tabs.js index 175c8c8c3ac..7decfebbf64 100644 --- a/manager/assets/modext/widgets/core/modx.tabs.js +++ b/manager/assets/modext/widgets/core/modx.tabs.js @@ -1,3 +1,31 @@ +/** + * Override of onStripMouseDown (private method) made to update new 'tabClicked' property when + * a tab is clicked. There is no built-in click event for the Ext.TabPanel component and adding one + * an instance listener (via a vanilla addEventListener) it ends up firing the event too late in the chain + * of events. Thus, notification of tab clicks is made in this way (which sets the 'tabClicked' value + * very early in the process). + */ +Ext.override(Ext.TabPanel, { + onStripMouseDown: function(e) { + if (e.button !== 0) { + return; + } + e.preventDefault(); + const t = this.findTargets(e); + if (t.close) { + if (t.item.fireEvent('beforeclose', t.item) !== false) { + t.item.fireEvent('close', t.item); + this.remove(t.item); + } + return; + } + if (t.item && t.item !== this.activeTab) { + this.tabClicked = true; + this.setActiveTab(t.item); + } + } +}); + MODx.Tabs = function(config = {}) { Ext.applyIf(config, { enableTabScroll: true, @@ -13,6 +41,7 @@ MODx.Tabs = function(config = {}) { bodyCssClass: 'tab-panel-wrapper' }, activeTab: 0, + tabClicked: false, border: false, autoScroll: true, autoHeight: true, @@ -34,15 +63,25 @@ MODx.Tabs = function(config = {}) { tabPanel.on({ beforetabchange: function(tabPanelCmp, newTab, currentTab) { /* - Only proceed with the clearing process if the tab has changed. + Only proceed with the clearing process if the tab has changed (via click). This is needed to prevent clearing when a URL has been typed in. NOTE: The currentTab is the previous one being navigated away from */ - if (newTab && currentTab && newTab.id !== currentTab.id) { + if (this.tabClicked && newTab && currentTab && newTab.id !== currentTab.id) { const resetVerticalTabPanelFilters = (currentTab.items?.items[0]?.xtype === 'modx-vtabs') || currentTab.ownerCt?.xtype === 'modx-vtabs', changedBetweenVtabs = newTab.ownerCt?.xtype === 'modx-vtabs' && currentTab.ownerCt?.xtype === 'modx-vtabs' ; + /* + When navigating back to Access Permissions and the TabPanel is not stateful, + ensure that the first vertical tab is activated + */ + if (newTab.itemId === 'modx-usergroup-permissions-panel' && !this.stateful) { + const vTabPanel = newTab.items?.items[0]; + if (vTabPanel && vTabPanel.xtype === 'modx-vtabs') { + vTabPanel.setActiveTab(0); + } + } this.clearFiltersBeforeChange(currentTab, resetVerticalTabPanelFilters, changedBetweenVtabs); } } @@ -90,13 +129,20 @@ Ext.extend(MODx.Tabs, Ext.TabPanel, { } if (itemsSource.length > 0) { gridObj = this.findGridObject(itemsSource); - /* - Grids placed in an atypical structure, such as the ACLs User Group grid that - is activated via the User Groups tree, require further searching - */ - if (!gridObj && itemsSource?.map['modx-tree-panel-usergroup']) { - itemsSource = itemsSource.map['modx-tree-panel-usergroup'].items; - gridObj = this.findGridObject(itemsSource); + + // Grids placed in an atypical structure require further searching + if (!gridObj) { + let customItemsSource = null; + if (itemsSource?.map['modx-tree-panel-usergroup']) { + // ACLs User Group grid that is activated via the User Groups tree + customItemsSource = itemsSource.map['modx-tree-panel-usergroup'].items; + } else if (itemsSource?.map['packages-breadcrumbs']) { + // (Installed) Packages grid + customItemsSource = itemsSource.map['card-container'].items.map['modx-panel-packages'].items; + } + if (customItemsSource) { + gridObj = this.findGridObject(customItemsSource); + } } } if (gridObj) { @@ -134,10 +180,12 @@ MODx.VerticalTabs = function(config = {}) { }); MODx.VerticalTabs.superclass.constructor.call(this, config); this.config = config; - this.on('afterrender', function() { - if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { - const tabId = parseInt(MODx.request.vtab, 10); - this.setActiveTab(tabId); + this.on({ + afterrender: function() { + if (MODx.request && Object.prototype.hasOwnProperty.call(MODx.request, 'vtab')) { + const tabId = parseInt(MODx.request.vtab, 10); + this.setActiveTab(tabId); + } } }); };