diff --git a/_build/templates/default/sass/_forms.scss b/_build/templates/default/sass/_forms.scss index 77c9e97b32c..af942847acb 100644 --- a/_build/templates/default/sass/_forms.scss +++ b/_build/templates/default/sass/_forms.scss @@ -76,7 +76,7 @@ textarea.x-form-field, border-radius: $borderRadius; border: 1px solid $borderColor; position: relative; - transition: border-color .25s; + transition: border-color 0.25s; } .x-viewport .x-trigger-wrap-focus, @@ -142,7 +142,7 @@ input::-moz-focus-inner { padding: 0 0 0 3px; top: 0; right: 0; - transition: all .25s; + transition: all 0.25s; width: 16px; height: 16px; @@ -288,7 +288,7 @@ input::-moz-focus-inner { border: 1px solid $borderColor; border-radius: $borderRadius; padding: 5px; - transition: all .25s; + transition: all 0.25s; &:focus { border: 1px solid $borderColorFocus; @@ -409,7 +409,7 @@ input::-moz-focus-inner { } &.toggle-slider-above { - margin: .3em 0; + margin: 0.3em 0; padding-left: 3.9em; } @@ -421,15 +421,15 @@ input::-moz-focus-inner { .example-list { ul { - margin: .4em 0; + margin: 0.4em 0; li { position: relative; - margin-bottom: .25em; + margin-bottom: 0.25em; padding-left: 1.25em; &::before { @extend %pseudo-font; position: absolute; - left: .2em; + left: 0.2em; top: 0; content: fa-content($fa-var-angle-double-right); color: scale-color($mediumGray, $lightness: 20%); @@ -439,7 +439,7 @@ input::-moz-focus-inner { } .example-input, .copy-this { - padding: 0 .3em; + padding: 0 0.3em; border-radius: 2px; transition: width 1s; } @@ -477,7 +477,14 @@ input::-moz-focus-inner { } } } - } + &:active { + color: $darkGray; + &::after { + color: $darkGray; + } + } + } + .feedback { margin-left: 1.4rem; color: scale-color($blue, $lightness: -35%); @@ -506,7 +513,7 @@ input::-moz-focus-inner { .fs-toggle { padding-top: 1em; margin-top: 2em; - margin-bottom: .5em; + margin-bottom: 0.5em; border-top: 1px dashed $borderColor; } @@ -635,7 +642,7 @@ input::-moz-focus-inner { transform: translate(-50%, -50%); text-align: center; width: 30px; - transition: opacity .25s; + transition: opacity 0.25s; } &.x-form-trigger-over, @@ -931,11 +938,11 @@ input::-moz-focus-inner { left: unset; } - &+.x-form-cb-label, - &+.x-fieldset-header-text { + & + .x-form-cb-label, + & + .x-fieldset-header-text { position: relative; padding-left: 3.6em; - padding-top: .2em; + padding-top: 0.2em; margin-left: 0; cursor: pointer; box-sizing: border-box; @@ -945,7 +952,7 @@ input::-moz-focus-inner { &:after { content: ''; position: absolute; - transition: all .2s ease; + transition: all 0.2s ease; font-size: inherit; } @@ -962,7 +969,7 @@ input::-moz-focus-inner { &:after { left: 0.1em; top: 0.8em; - margin-top: -.65em; + margin-top: -0.65em; height: 1.3em; width: 1.3em; border-radius: 50%; @@ -972,9 +979,8 @@ input::-moz-focus-inner { } &:checked { - - &+.x-form-cb-label, - &+.x-fieldset-header-text { + & + .x-form-cb-label, + & + .x-fieldset-header-text { &:after { left: 1.6em; top: 0.8em; @@ -988,9 +994,8 @@ input::-moz-focus-inner { } &.danger:checked { - - &+.x-form-cb-label, - &+.x-fieldset-header-text { + & + .x-form-cb-label, + & + .x-fieldset-header-text { &:before { background-color: $red; border-color: $red; @@ -999,9 +1004,8 @@ input::-moz-focus-inner { } &.warning:checked { - - &+.x-form-cb-label, - &+.x-fieldset-header-text { + & + .x-form-cb-label, + & + .x-fieldset-header-text { &:before { background-color: $orange; border-color: $orange; @@ -1088,13 +1092,13 @@ input::-moz-focus-inner { cursor: pointer; display: inline-block; /*font-size: 1px;*/ outline: 0; /* fix firefox dotted outlines */ - opacity: .6; + opacity: 0.6; filter: alpha(opacity=60); /* for IE <= 8 */ padding: 0; position: absolute; top: 0; right: 0; - transition: opacity .25s; + transition: opacity 0.25s; width: 16px; height: 100%; @@ -1225,21 +1229,22 @@ input::-moz-focus-inner { } .x-editor .x-form-check-wrap { - background-color: $white -} - -/* fix combo on grid editor bug */ -.x-grid-editor .x-form-field-wrap { - background: #f6f2f7 url($imgPath + 'modx-theme/form/combo-bck.png') repeat-x scroll 0 100%; + background-color: $white; } -.x-grid-editor .x-form-field-wrap input { +.x-grid-editor { + z-index: 9002 !important; + .x-form-field-wrap { + background: #f6f2f7 url($imgPath + "modx-theme/form/combo-bck.png") repeat-x + scroll 0 100%; + input { background-color: transparent !important; } - -.x-grid-editor .x-form-field-wrap img { + img { background-color: $white; - background-image: url($imgPath + 'modx-theme/form/trigger.png'); + background-image: url($imgPath + "modx-theme/form/trigger.png"); + } + } } .x-form-grow-sizer { @@ -1373,7 +1378,7 @@ input::-moz-focus-inner { .x-btn { padding: 1px; - transition: color .25s; + transition: color 0.25s; &.x-btn-over, &:hover, @@ -1388,7 +1393,7 @@ input::-moz-focus-inner { &.x-item-disabled { color: $buttonColor; - opacity: .4; + opacity: 0.4; } button:before { @@ -1432,7 +1437,11 @@ input::-moz-focus-inner { } /* the second text cell, "of X" */ - .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell { + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell { .xtb-text { display: inline-block; position: absolute; @@ -1444,7 +1453,15 @@ input::-moz-focus-inner { } /* the last regular button >>, yes, I know it's ugly but tell that Microsoft and say thanks for IE8 =) */ - .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell + .x-toolbar-cell { + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell + + .x-toolbar-cell { .x-btn { margin-right: 0; } @@ -1453,13 +1470,13 @@ input::-moz-focus-inner { /* the refresh button */ .x-toolbar-cell:last-child { opacity: 0; - transition: opacity .25s; + transition: opacity 0.25s; .x-btn { font-size: 12px; line-height: 1; margin: 0; - opacity: .4; + opacity: 0.4; padding: 0; position: absolute; bottom: 2px; @@ -1548,11 +1565,11 @@ input::-moz-focus-inner { .x-date-mp-ybtn a.x-date-mp-prev, .x-date-mp-ybtn a.x-date-mp-next { display: inline-block; - opacity: .6; + opacity: 0.6; filter: alpha(opacity=60); /* for IE <= 8 */ margin: 0 auto; position: relative; - transition: opacity .25s; + transition: opacity 0.25s; &:before { @extend %pseudo-font; @@ -1809,6 +1826,6 @@ td.x-date-mp-sep { bottom: 0; padding: 10px 20px; color: #fff; - background-color: rgba(0, 0, 0, .8); + background-color: rgba(0, 0, 0, 0.8); } } diff --git a/_build/templates/default/sass/_utility.scss b/_build/templates/default/sass/_utility.scss index 27c6b20ed18..b5ff5bc8cd4 100644 --- a/_build/templates/default/sass/_utility.scss +++ b/_build/templates/default/sass/_utility.scss @@ -145,7 +145,6 @@ } } - /* Instead of writing the same code for every nav bar */ @mixin navigation-list { list-style-type: none; @@ -212,3 +211,13 @@ } } } + +@mixin textLink { + color: $colorSplash; + text-decoration-style: dotted; + text-decoration-color: scale-color($colorSplash, $lightness: 50%); + &:hover { + color: $black; + border-bottom-color: scale-color($black, $lightness: 40%); + } +} \ No newline at end of file diff --git a/_build/templates/default/sass/index.scss b/_build/templates/default/sass/index.scss index 67a987c5d60..0d3d8812687 100644 --- a/_build/templates/default/sass/index.scss +++ b/_build/templates/default/sass/index.scss @@ -541,6 +541,14 @@ textarea.x-form-field { } /* grids */ + +.modx-protected-row { + .x-grid3-cell-inner { + font-style: italic; + color: $colorSplash; + } +} + .x-small-editor .x-form-field { font-size: 12px !important; } @@ -553,14 +561,15 @@ textarea.x-form-field { color: #999 !important; } -a.x-grid-link { - color: $colorSplash; - text-decoration: underline; -} - -a.x-grid-link:hover, -a.x-grid-link:focus { - text-decoration: none; +.x-grid-link { + @include textLink; + &.simulated-link { + cursor: pointer; + } + .grid-row-inactive & { + color: #999 !important; + text-decoration-color: #999 !important; + } } .x-editable-column { @@ -910,6 +919,37 @@ a.x-grid-link:focus { } /* rowactions */ +.x-grid3-row { + &.disable-selection, + &.disable-selection.x-grid3-row-selected { + .x-grid3-row-checker { + position: relative; + &::before, + &::after { + color: $disabledTextColor; + } + &::before { + content: '\f0c8'; + } + &::after { + content: '\f715'; + font-size: 6px; + position: absolute; + left: 50%; + top: 50%; + margin-left: 2px; + margin-top: 1px; + transform: translate(-50%, -50%) rotate(98deg); + font-weight: 600; + font-family: 'Font Awesome 5 Free'; + } + &:hover { + cursor: default; + } + } + } +} + .ux-row-action-cell .x-grid3-cell-inner { padding: 1px 0 0 0; } @@ -1883,6 +1923,12 @@ iframe[classname="x-hidden"] { user-select: text !important; } +.x-selectable { + &.simulated-link * { + @include textLink; + } +} + /* Lightbox */ #ux-lightbox { left: 0; diff --git a/core/lexicon/en/content_type.inc.php b/core/lexicon/en/content_type.inc.php index 62063f93228..a6411fed17a 100644 --- a/core/lexicon/en/content_type.inc.php +++ b/core/lexicon/en/content_type.inc.php @@ -1,4 +1,5 @@ Each setting will be available via the [[++key]] placeholder.'; $_lang['context_with_key_not_found'] = 'Context with key %s not found!'; diff --git a/core/lexicon/en/dashboards.inc.php b/core/lexicon/en/dashboards.inc.php index f6cb29fd2d2..e27f87976e6 100644 --- a/core/lexicon/en/dashboards.inc.php +++ b/core/lexicon/en/dashboards.inc.php @@ -7,16 +7,19 @@ * @language en */ $_lang['dashboard'] = 'Dashboard'; -$_lang['dashboard_desc_name'] = 'The name of the Dashboard.'; -$_lang['dashboard_desc_description'] = 'A short description of the Dashboard.'; -$_lang['dashboard_desc_hide_trees'] = 'Checking this will hide the left-hand trees when this Dashboard is rendered on the welcome page.'; -$_lang['dashboard_hide_trees'] = 'Hide Left-Hand Trees'; -$_lang['dashboard_desc_customizable'] = 'Allow users to customize this dashboard for their accounts: create, delete and change position or size of widgets.'; $_lang['dashboard_customizable'] = 'Customizable'; +$_lang['dashboard_customizable_desc'] = 'Allow users to customize this dashboard for their accounts: create, delete and change position or size of widgets.'; +$_lang['dashboard_description_desc'] = 'A short description of the Dashboard.'; +$_lang['dashboard_edit'] = 'Edit the settings and Widget placements for this Dashboard'; +$_lang['dashboard_hide_trees'] = 'Hide Left-Hand Trees'; +$_lang['dashboard_hide_trees_desc'] = 'Checking this will hide the left-hand trees when this Dashboard is rendered on the welcome page.'; +$_lang['dashboard_name_desc'] = 'The name of the Dashboard.'; $_lang['dashboard_remove_confirm'] = 'Are you sure you want to delete this Dashboard?'; $_lang['dashboard_remove_multiple_confirm'] = 'Are you sure you want to delete the selected Dashboards?'; +$_lang['dashboard_reserved_general_desc'] = 'Note that this is a protected, built-in Dashboard. Its general values are locked, but other specifications (such as assigned Widgets) are editable by users with the appropriate permissions.'; $_lang['dashboard_err_ae_name'] = 'A dashboard with the name "[[+name]]" already exists! Please try another name.'; $_lang['dashboard_err_duplicate'] = 'An error occurred while trying to duplicate the dashboard.'; +$_lang['dashboard_err_name_reserved'] = 'The dashboard name “[[+reservedName]]” is reserved. Please choose another name.'; $_lang['dashboard_err_nf'] = 'Dashboard not found.'; $_lang['dashboard_err_ns'] = 'Dashboard not specified.'; $_lang['dashboard_err_ns_name'] = 'Please specify a name for the widget.'; @@ -27,8 +30,9 @@ $_lang['dashboard_usergroup_remove'] = 'Delete Dashboard from User Group'; $_lang['dashboard_usergroup_remove_confirm'] = 'Are you sure you want to revert this User Group to using the default Dashboard?'; $_lang['dashboard_usergroups.intro_msg'] = 'Here is a list of all the User Groups using this Dashboard.'; +$_lang['dashboard_widget_edit'] = 'Edit this Widget’s specifications and properties'; $_lang['dashboard_widget_err_placed'] = 'This widget is already placed in this Dashboard!'; -$_lang['dashboard_widgets.intro_msg'] = 'Manage widgets in this dashboard. You can also drag and drop rows in the grid to rearrange them.

Please note: if a dashboard is "customizable", this settings will be applied only for the first load for every user. From here they will be able to create, delete and change the position or size of their widgets. User access to widgets can be limited by applying permissions.'; +$_lang['dashboard_widgets.intro_msg'] = 'Manage the widgets to be showin in this dashboard. Widgets may be re-ordered by changing their Rank or by dragging and dropping their grid rows into the desired position.

Note that if a dashboard is “Customizable,” its initial settings will only apply until a user adds, removes, or makes other changes to the dashboard’s widgets. User access to widgets can be limited by applying permissions.'; $_lang['dashboards'] = 'Dashboards'; $_lang['dashboards.intro_msg'] = 'Here you can manage all the available Dashboards for this MODX manager.'; $_lang['rank'] = 'Rank'; @@ -100,3 +104,9 @@ $_lang['w_whosonline_desc'] = 'Shows a list of online users.'; $_lang['w_view_all'] = 'View all'; $_lang['w_no_data'] = 'No data to display'; + +// Temporarily match old keys to new ones to ensure compatibility +$_lang['dashboard_desc_customizable'] = $_lang['dashboard_customizable_desc']; +$_lang['dashboard_desc_description'] = $_lang['dashboard_description_desc']; +$_lang['dashboard_desc_hide_trees'] = $_lang['dashboard_hide_trees_desc']; +$_lang['dashboard_desc_name'] = $_lang['dashboard_name_desc']; diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php index 93a8896ba0b..7226dd465c4 100644 --- a/core/lexicon/en/default.inc.php +++ b/core/lexicon/en/default.inc.php @@ -75,6 +75,7 @@ $_lang['confirm_delete_message'] = 'Are you sure you want to delete this message?'; $_lang['confirm_remove'] = 'Are you sure you want to delete this item?'; $_lang['confirm_remove_locks'] = 'Users sometimes close their browser while editing documents, templates, snippets or parsers, possibly leaving the item they were editing in locked state. By pressing OK you can delete ALL locks currently in place.

Proceed?'; +$_lang['confirm_remove_multiple'] = 'Are you sure you want to delete the selected items?'; $_lang['confirm_undelete'] = 'Any children documents deleted at the same time as this document will also be undeleted, but children documents deleted at an earlier time will still be deleted.'; $_lang['confirm_unpublish'] = 'Un-publishing this document now will delete any (un)publishing dates that may have been set. If you wish to set or keep publish or unpublish dates, please choose to edit the document instead.\n\nProceed?'; $_lang['console'] = 'Console'; @@ -92,6 +93,7 @@ $_lang['create_user_group'] = 'Create User Group'; $_lang['created'] = 'Created'; $_lang['createdon'] = 'Creation date'; +$_lang['creator'] = 'Creator'; $_lang['current'] = 'Current'; $_lang['dashboard'] = 'Dashboard'; $_lang['data_err_load'] = 'Error loading data.'; @@ -217,6 +219,8 @@ $_lang['general_information'] = 'General Information'; $_lang['general_settings'] = 'General Settings'; $_lang['go'] = 'Go'; +$_lang['grid_column_creator_header'] = $_lang['creator']; +$_lang['grid_column_creator_description'] = 'Indicates the entity that created the row’s data/setting (read-only)'; $_lang['group'] = 'Group'; $_lang['guid'] = 'GUID'; $_lang['handler'] = 'Handler'; @@ -316,6 +320,9 @@ $_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.'; +// "Extra(s)" below refers to a third-party software package. Translate to shortest length term possible. +$_lang['package_extra'] = 'Extra'; +$_lang['package_extras'] = 'Extras'; $_lang['pagetitle'] = 'Resource\'s Title'; $_lang['page_title'] = 'Resource Title'; $_lang['parameter'] = 'Parameter'; @@ -574,6 +581,13 @@ */ // All +// Templates (some entries also used in Form Customization) +$_lang['template_empty'] = '(empty)'; +$_lang['template_empty_desc'] = '(Note: A template has not been assigned to this set)'; +$_lang['template_missing'] = '(missing)'; +$_lang['template_missing_reassign'] = '(missing — please select a template or “empty” from this list)'; +$_lang['template_missing_desc'] = '(Note: The assigned template no longer exists)'; + // TVs $_lang['tv_type'] = 'Input Type'; $_lang['tv_default'] = 'Default Value'; diff --git a/core/lexicon/en/formcustomization.inc.php b/core/lexicon/en/formcustomization.inc.php index d9cc680cfad..56a977b3397 100644 --- a/core/lexicon/en/formcustomization.inc.php +++ b/core/lexicon/en/formcustomization.inc.php @@ -80,6 +80,7 @@ $_lang['set_change_template_confirm'] = 'Are you sure you want to do this? This will change the Template that these Rules apply to. If so, MODX will first save your changes before reloading the page to refresh the new TVs for the new Template.'; $_lang['set_constraint_field_desc'] = 'Setting the Constraint field will prevent the rules in this Set from executing unless the field for this Resource matches the "constraint" value.'; $_lang['set_constraint_desc'] = 'Set the value of the field (specified above) to restrict the rules in this Set from being executed unless the Resource has this value on the specified constraint field.'; +$_lang['set_edit'] = 'Edit this set’s rules'; $_lang['set_err_nfs'] = 'No Set found with ID [[+id]]'; $_lang['set_err_ns'] = 'No Set specified.'; $_lang['set_fields_msg'] = 'Here you can adjust the fields for this page, including their visibility, labels and default values. Just double-click on a row to edit its value. Leave a field empty to use the default setting.
Please note: when hiding an element inside this profile, it will be hidden in overlapping profiles too (even if Visible is checked).'; diff --git a/core/lexicon/en/namespace.inc.php b/core/lexicon/en/namespace.inc.php index e569bf8120a..ec549d219d6 100644 --- a/core/lexicon/en/namespace.inc.php +++ b/core/lexicon/en/namespace.inc.php @@ -18,7 +18,7 @@ $_lang['namespace_name_desc'] = 'Specify a name for the Namespace here.'; $_lang['namespace_path'] = 'Core Path'; $_lang['namespace_path_desc'] = 'Specify an absolute path to the core for this Namespace here. You may use placeholders like {core_path}. Example: {core_path}components/democomponent/'; -$_lang['namespace_remove_confirm'] = 'Are you sure you want to delete "[[+name]]" namespace and all related content?'; +$_lang['namespace_remove_confirm'] = 'Are you sure you want to delete the "[[+name]]" namespace and all related content?'; $_lang['namespace_remove_multiple_confirm'] = 'Are you sure you want to delete these namespaces and all their related content?'; $_lang['namespaces'] = 'Namespaces'; $_lang['namespaces_desc'] = 'Namespaces are global identifiers for packages and components, registering their vehicles, lexicon entries and resources all together.'; diff --git a/core/lexicon/en/policy.inc.php b/core/lexicon/en/policy.inc.php index 6d35864b353..3df209dbfe1 100644 --- a/core/lexicon/en/policy.inc.php +++ b/core/lexicon/en/policy.inc.php @@ -28,6 +28,7 @@ $_lang['policy_desc_template'] = 'The Policy Template used for this Policy. Policies get their Permission lists from their Template.'; $_lang['policy_desc_lexicon'] = 'Optional. The Lexicon Topic that this Policy uses to translate the Permissions it owns.'; $_lang['policy_duplicate_confirm'] = 'Are you sure you want to duplicate this policy and all of its data?'; +$_lang['policy_edit'] = 'Edit the permissions assigned to this Policy'; $_lang['policy_err_ae'] = 'A Policy already exists with the name `[[+name]]`. Please select another name.'; $_lang['policy_err_nf'] = 'Policy not found.'; $_lang['policy_err_ns'] = 'Policy not specified.'; @@ -47,6 +48,7 @@ $_lang['policy_template_desc'] = 'A Policy Template defines which Permissions will show up in the Permissions grid when editing a specific Policy. You can add or remove specific Permissions from this template below. Note that removing a Permission from a Template will remove it from any Policies that use this Template.'; $_lang['policy_template_desc_name'] = 'The name of the Access Policy Template'; $_lang['policy_template_desc_description'] = 'Optional. A short description of the Access Policy Template. Also you might use lexicon keys here.'; +$_lang['policy_template_edit'] = 'Edit the permissions assigned to this Policy Template'; $_lang['policy_template_lexicon'] = 'Lexicon Topic'; $_lang['policy_template_desc_lexicon'] = 'Optional. The Lexicon Topic that this Policy Template uses to translate the Permissions it owns.'; $_lang['policy_template_desc_template_group'] = 'The Policy Template Group to use. This is used when selecting Policies from a dropdown menu; usually they are filtered by template group. Select an appropriate group for your Policy Template.'; @@ -61,6 +63,10 @@ $_lang['policy_template_remove_confirm_in_use'] = 'Are you sure you want to delete this Policy Template? It will delete all Policies attached to this Template as well - this could break your MODX installation if any active Policies are attached to this Template.

This template is used by existing Policies ([[+count]] in total). Are you sure you want to delete this template and all attached policies?'; $_lang['policy_template_remove_multiple_confirm'] = 'Are you sure you want to delete these Policy Templates? It will delete all Policies attached to these Templates as well - this could break your MODX installation if any active Policies are attached to these Templates.'; $_lang['policy_template_remove_multiple_confirm_in_use'] = 'Are you sure you want to delete these Policy Templates? It will delete all Policies attached to these Templates as well - this could break your MODX installation if any active Policies are attached to these Templates.

Some of selected templates are still used by existing Policies ([[+count]] in total). Are you sure you want to delete these template and all attached policies?'; +$_lang['policy_template_remove_multiple_confirm_in_use_ignoring_protected'] = 'In addition to the [[+count-templates]] Policy Templates you have selected, [[+count-policies]] Access Policies (attached to one or more of these Policy Templates) will be deleted. If any of these Access Policies are currently assigned to a permissions rule, you could break your MODX installation by removing them. (Note that the [[+protected]] protected Templates in your selection will not be removed.) +

+Are you sure you want to continue? +'; $_lang['policy_templates'] = 'Policy Templates'; $_lang['policy_templates.intro_msg'] = 'This is a list of Policy Templates which define lists of Permissions that are checked or unchecked in specific Policies.'; $_lang['policy_template_administrator_desc'] = 'Context administration policy template with all permissions.'; diff --git a/core/lexicon/en/source.inc.php b/core/lexicon/en/source.inc.php index 13a1f4932aa..371c0f54de5 100644 --- a/core/lexicon/en/source.inc.php +++ b/core/lexicon/en/source.inc.php @@ -20,7 +20,9 @@ $_lang['source_access_remove_confirm'] = 'Are you sure you want to delete Access to this Source for this User Group?'; $_lang['source_access_update'] = 'Edit Access'; $_lang['source_description_desc'] = 'A short description of the Media Source.'; +$_lang['source_edit'] = 'Edit the settings and User Group access for this Media Source'; $_lang['source_err_ae_name'] = 'A Media Source with that name already exists! Please specify a new name.'; +$_lang['source_err_name_reserved'] = 'The source name “[[+reservedName]]” is reserved. Please choose another name.'; $_lang['source_err_nf'] = 'Media Source not found!'; $_lang['source_err_init'] = 'Could not initialize "[[+source]]" Media Source!'; $_lang['source_err_nfs'] = 'No Media Source can be found with the id: [[+id]].'; @@ -30,6 +32,7 @@ $_lang['source_properties.intro_msg'] = 'Manage the properties for this Source below.'; $_lang['source_remove_confirm'] = 'Are you sure you want to delete this Media Source? This might break any TVs you have assigned to this source.'; $_lang['source_remove_multiple_confirm'] = 'Are you sure you want to delete these Media Sources? This might break any TVs you have assigned to these sources.'; +$_lang['source_reserved_general_desc'] = 'Note that this is a protected, built-in Media Source. The values shown below are for informational purposes only. Its properties and assigned User Group(s) are, however, editable by users with the appropriate permissions.'; $_lang['source_type'] = 'Source Type'; $_lang['source_type_desc'] = 'The type, or driver, of the Media Source. The Source will use this driver to connect to when gathering its data. For example: File System will grab files from the file system. S3 will get files from an S3 bucket.'; $_lang['source_type.file'] = 'File System'; diff --git a/core/lexicon/en/template.inc.php b/core/lexicon/en/template.inc.php index 738b49ea204..eae311fade3 100644 --- a/core/lexicon/en/template.inc.php +++ b/core/lexicon/en/template.inc.php @@ -18,7 +18,6 @@ $_lang['template_description_desc'] = 'Usage information for this Template shown in search results and as a tooltip in the Elements tree.'; $_lang['template_duplicate_confirm'] = 'Are you sure you want to duplicate this template?'; $_lang['template_edit_tab'] = 'Edit Template'; -$_lang['template_empty'] = '(empty)'; $_lang['template_err_default_template'] = 'This template is set as the default template. Please choose a different default template in the MODX configuration before deleting this template.
'; $_lang['template_err_delete'] = 'An error occurred while trying to delete the template.'; $_lang['template_err_duplicate'] = 'An error occurred while duplicating the template.'; diff --git a/core/lexicon/en/user.inc.php b/core/lexicon/en/user.inc.php index 984b0326fe0..92cfec9fff2 100644 --- a/core/lexicon/en/user.inc.php +++ b/core/lexicon/en/user.inc.php @@ -1,4 +1,5 @@
Also, should you choose to generate a new password for yourself, it will be sent to you through email.'; $_lang['user_email'] = 'Email address'; $_lang['user_err_access_permissions_save'] = 'An error occurred while saving user access permissions.'; @@ -178,7 +181,7 @@ $_lang['user_remove_confirm'] = 'Are you sure you want to delete this user? This is irreversible!'; $_lang['user_remove_multiple_confirm'] = 'Are you sure you want to delete these users? This is irreversible!'; $_lang['user_remote_data_msg'] = 'Edit remote user data here.'; -$_lang['user_role_update'] = 'Edit User Role'; +$_lang['user_role_update'] = 'Change User’s Role'; $_lang['user_setting_err_remove'] = 'An error occurred while trying to delete user settings.'; $_lang['user_setting_err_save'] = 'An error occurred while saving user settings.'; $_lang['user_settings'] = 'User Settings'; @@ -200,3 +203,6 @@ $_lang['user_createdon_desc'] = 'The date the user was created.'; $_lang['user_password_email_subject'] = 'Set up your password'; $_lang['user_password_email'] = '

Set up your password

We received a request to set up your MODX Revolution password. You can set up your password by clicking the button below and following the instructions on screen.

Set up my password

If you did not send this request, please ignore this email.

'; + +// Aliases +$_lang['user_group_user_create'] = $_lang['user_group_user_add']; diff --git a/core/src/Revolution/Processors/Context/Get.php b/core/src/Revolution/Processors/Context/Get.php index 624c8a77086..cc5626f407f 100644 --- a/core/src/Revolution/Processors/Context/Get.php +++ b/core/src/Revolution/Processors/Context/Get.php @@ -1,4 +1,5 @@ classKey::getCoreContexts(); + $contextKey = $this->object->get('key'); + if (in_array($contextKey, $coreContexts)) { + $contextData = $this->object->toArray(); + $reserved = $contextKey === 'mgr'; + $this->object->set('isProtected', true); + $this->object->set('reserved', $reserved); + } + } } diff --git a/core/src/Revolution/Processors/Context/GetList.php b/core/src/Revolution/Processors/Context/GetList.php index 56aafadbe55..7e6827b3015 100644 --- a/core/src/Revolution/Processors/Context/GetList.php +++ b/core/src/Revolution/Processors/Context/GetList.php @@ -22,8 +22,8 @@ /** * Grabs a list of contexts. * - * @property integer $start (optional) The record to start at. Defaults to 0. - * @property integer $limit (optional) The number of records to limit to. Defaults + * @property int $start (optional) The record to start at. Defaults to 0. + * @property int $limit (optional) The number of records to limit to. Defaults * to 20. * @property string $sort (optional) The column to sort by. Defaults to key. * @property string $dir (optional) The direction of the sort. Defaults to ASC. @@ -36,31 +36,36 @@ class GetList extends GetListProcessor public $permission = 'view_context'; public $languageTopics = ['context']; public $defaultSortField = 'key'; - /** @var boolean $canEdit Determines whether or not the user can edit a Context */ + + /** @var bool $canCreate Determines whether or not the user can create a context (/duplicate one) */ + public $canCreate = false; + /** @var bool $canEdit Determines whether or not the user can edit a Context */ public $canEdit = false; - /** @var boolean $canRemove Determines whether or not the user can remove a Context */ + /** @var bool $canRemove Determines whether or not the user can remove a Context */ public $canRemove = false; - /** @var boolean $canCreate Determines whether or not the user can create a context (/duplicate one) */ - public $canCreate = false; - /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $coreContexts; + + /** @var bool $isGridFilter Indicates the target of this list data is a filter field */ protected $isGridFilter = false; /** * {@inheritDoc} - * @return boolean + * @return bool */ public function initialize() { $initialized = parent::initialize(); $this->setDefaultProperties([ 'query' => '', - 'exclude' => '', + 'exclude' => 'creator' ]); $this->canCreate = $this->modx->hasPermission('new_context'); $this->canEdit = $this->modx->hasPermission('edit_context'); $this->canRemove = $this->modx->hasPermission('delete_context'); $this->isGridFilter = $this->getProperty('isGridFilter', false); + $this->coreContexts = $this->classKey::getCoreContexts(); + return $initialized; } @@ -76,7 +81,8 @@ public function prepareQueryBeforeCount(xPDOQuery $c) if (!empty($query)) { $c->where([ 'key:LIKE' => '%' . $query . '%', - 'OR:description:LIKE' => '%' . $query . '%', + 'OR:name:LIKE' => '%' . $query . '%', + 'OR:description:LIKE' => '%' . $query . '%' ]); } $exclude = $this->getProperty('exclude'); @@ -149,24 +155,30 @@ public function prepareQueryAfterCount(xPDOQuery $c) /** * {@inheritDoc} - * @param xPDOObject $object - * + * @param xPDOObject|modContext $object * @return array */ public function prepareRow(xPDOObject $object) { - $contextArray = $object->toArray(); - $contextArray['perm'] = []; - if ($this->canCreate) { - $contextArray['perm'][] = 'pnew'; - } - if ($this->canEdit) { - $contextArray['perm'][] = 'pedit'; - } - if (!in_array($object->get('key'), $this->classKey::RESERVED_KEYS) && $this->canRemove) { - $contextArray['perm'][] = 'premove'; + $permissions = [ + 'create' => $this->canCreate && $object->checkPolicy('save'), + 'duplicate' => $this->canCreate && $object->checkPolicy('copy'), + 'update' => $this->canEdit && $object->checkPolicy('save'), + 'delete' => $this->canRemove && $object->checkPolicy('remove') + ]; + + $contextData = $object->toArray(); + $contextKey = $contextData['key']; + $isCoreContext = $object->isCoreContext($contextKey); + + $contextData['reserved'] = ['key' => $this->coreContexts, 'name' => ['Manager']]; + $contextData['isProtected'] = $isCoreContext; + $contextData['creator'] = $isCoreContext ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCoreContext) { + unset($permissions['delete']); } + $contextData['permissions'] = $permissions; - return $contextArray; + return $contextData; } } diff --git a/core/src/Revolution/Processors/Element/PropertySet/Get.php b/core/src/Revolution/Processors/Element/PropertySet/Get.php index 7b04a021a7b..64137141435 100644 --- a/core/src/Revolution/Processors/Element/PropertySet/Get.php +++ b/core/src/Revolution/Processors/Element/PropertySet/Get.php @@ -1,4 +1,5 @@ default = $this->getDefaultSet(); $id = $this->getProperty($this->primaryKeyField); + + $canSave = $this->modx->hasPermission('save_propertyset'); + $this->canCreate = $canSave && $this->modx->hasPermission('new_propertyset'); + $this->canEdit = $canSave && $this->modx->hasPermission('edit_propertyset'); + $this->canRemove = $this->modx->hasPermission('delete_propertyset'); + if ($id == 0) { if (empty($this->default)) { return $this->modx->lexicon($this->objectType . '_err_nfs', ['id' => $id]); @@ -158,6 +168,11 @@ public function setData(array $properties, array &$data, $isDefault = false) $property['desc_trans'], !empty($property['area']) ? $property['area'] : '', !empty($property['area_trans']) ? $property['area_trans'] : ($isDefault ? '' : $property['area']), + [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ] ]; } } diff --git a/core/src/Revolution/Processors/Element/PropertySet/GetList.php b/core/src/Revolution/Processors/Element/PropertySet/GetList.php index ee2ce443cb5..3bed14e4e12 100644 --- a/core/src/Revolution/Processors/Element/PropertySet/GetList.php +++ b/core/src/Revolution/Processors/Element/PropertySet/GetList.php @@ -1,4 +1,5 @@ node = explode('_', $id); - /* check permissions */ - $this->has = [ - 'save' => $this->modx->hasPermission('save_propertyset'), - 'remove' => $this->modx->hasPermission('delete_propertyset'), - 'new' => $this->modx->hasPermission('new_propertyset'), - ]; + $this->canCreate = $this->modx->hasPermission('save_propertyset') && $this->modx->hasPermission('new_propertyset'); + $this->canEdit = $this->modx->hasPermission('save_propertyset') && $this->modx->hasPermission('edit_propertyset'); + $this->canRemove = $this->modx->hasPermission('delete_propertyset'); return true; } @@ -65,7 +64,7 @@ public function initialize() * * @return string */ - function getNodeIcon($elementIdentifier = '') + public function getNodeIcon($elementIdentifier = '') { $elementIdentifier = strtolower($elementIdentifier); $defaults = [ @@ -134,7 +133,7 @@ public function getCategoryNode($category) /** @var modPropertySet $set */ foreach ($sets as $set) { $menu = []; - if ($this->has['save']) { + if ($this->canEdit) { $menu[] = [ 'text' => $this->modx->lexicon($this->objectType . '_element_add'), 'handler' => 'function(itm,e) { @@ -149,7 +148,7 @@ public function getCategoryNode($category) }', ]; } - if ($this->has['new'] && $this->has['save']) { + if ($this->canCreate) { $menu[] = [ 'text' => $this->modx->lexicon($this->objectType . '_duplicate'), 'handler' => 'function(itm,e) { @@ -157,7 +156,7 @@ public function getCategoryNode($category) }', ]; } - if ($this->has['remove']) { + if ($this->canRemove) { $menu[] = '-'; $menu[] = [ 'text' => $this->modx->lexicon($this->objectType . '_remove'), @@ -218,7 +217,10 @@ public function getPropertySetNode() continue; } $menu = []; - if ($this->has['remove']) { + /* + Note that this action removes (detaches) an Element from a given Property Set, which is really an edit of that Set + */ + if ($this->canEdit) { $menu[] = [ 'text' => $this->modx->lexicon($this->objectType . '_element_remove'), 'handler' => 'function(itm,e) { diff --git a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php index b32d6c4d63e..eee08d166c5 100644 --- a/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php +++ b/core/src/Revolution/Processors/Element/Template/TemplateVar/GetList.php @@ -42,6 +42,9 @@ class GetList extends GetListProcessor protected $query = ''; protected $isFiltered = false; + public $canEdit = false; + public $canEditTv = false; + /** * {@inheritDoc} */ @@ -50,6 +53,9 @@ public function initialize() $this->category = (int)$this->getProperty('category', 0); $this->query = $this->getProperty('query', ''); $this->isFiltered = $this->category > 0 || $this->query; + $this->canEdit = $this->modx->hasPermission('edit_template') && $this->modx->hasPermission('save_template'); + $this->canEditTv = $this->modx->hasPermission('edit_tv') && $this->modx->hasPermission('save_tv'); + return parent::initialize(); } @@ -156,16 +162,17 @@ public function prepareQueryBeforeCount(xPDOQuery $c) /** * {@inheritDoc} + * @param xPDOObject|modTemplateVar $object */ public function prepareRow(xPDOObject $object) { + $permissions = [ + 'update' => $this->canEdit, + 'updateTv' => $this->canEditTv && $object->checkPolicy('save') + ]; $tvArray = $object->get(['id', 'name', 'caption', 'tv_rank', 'category_name']); $tvArray['access'] = (bool)$object->get('access'); - - $tvArray['perm'] = []; - if ($this->modx->hasPermission('edit_tv')) { - $tvArray['perm'][] = 'pedit'; - } + $tvArray['permissions'] = $permissions; return $tvArray; } diff --git a/core/src/Revolution/Processors/Element/TemplateVar/ResourceGroup/GetList.php b/core/src/Revolution/Processors/Element/TemplateVar/ResourceGroup/GetList.php index cd2ccd45ec7..29927770f5a 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/ResourceGroup/GetList.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/ResourceGroup/GetList.php @@ -1,4 +1,5 @@ toArray(); $resourceGroupArray['access'] = $rgtv ? true : false; - $resourceGroupArray['menu'] = []; return $resourceGroupArray; } diff --git a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php index 0b995785b5d..7a623b6ab2e 100644 --- a/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php +++ b/core/src/Revolution/Processors/Element/TemplateVar/Template/GetList.php @@ -30,6 +30,9 @@ */ class GetList extends Processor { + public $canEdit = false; + public $canEditTemplate = false; + public function checkPermissions() { return $this->modx->hasPermission('view_tv'); @@ -49,8 +52,10 @@ public function initialize() 'dir' => 'ASC', 'tv' => false, ]); + $this->canEdit = $this->modx->hasPermission('edit_tv') && $this->modx->hasPermission('save_tv'); + $this->canEditTemplate = $this->modx->hasPermission('edit_template') && $this->modx->hasPermission('save_template'); - return true; + return parent::initialize(); } public function process() @@ -133,9 +138,14 @@ public function getData() */ public function prepareRow(modTemplate $template) { + $permissions = [ + 'update' => $this->canEdit, + 'updateTemplate' => $this->canEditTemplate && $template->checkPolicy('save') + ]; $templateArray = $template->toArray(); $templateArray['category_name'] = $template->get('category_name'); unset($templateArray['content']); + $templateArray['permissions'] = $permissions; return $templateArray; } diff --git a/core/src/Revolution/Processors/Model/GetListProcessor.php b/core/src/Revolution/Processors/Model/GetListProcessor.php index f9ff359eb16..e7cff26f76d 100644 --- a/core/src/Revolution/Processors/Model/GetListProcessor.php +++ b/core/src/Revolution/Processors/Model/GetListProcessor.php @@ -25,7 +25,9 @@ */ abstract class GetListProcessor extends ModelProcessor { + /** @deprecated as of MODX 3.1.0; new permissions handling replaces css class-based specifiers */ public const CLASS_ALLOW_EDIT = 'pedit'; + /** @deprecated as of MODX 3.1.0; new permissions handling replaces css class-based specifiers */ public const CLASS_ALLOW_REMOVE = 'premove'; /** @var string $defaultSortField The default field to sort by */ diff --git a/core/src/Revolution/Processors/Resource/Trash/GetList.php b/core/src/Revolution/Processors/Resource/Trash/GetList.php index 79234f46ac6..52766c890e1 100644 --- a/core/src/Revolution/Processors/Resource/Trash/GetList.php +++ b/core/src/Revolution/Processors/Resource/Trash/GetList.php @@ -40,11 +40,21 @@ class GetList extends GetListProcessor public $permission = 'view'; + public $canPurge = false; + public $canUndelete = false; + public $canUPublish = false; + private modManagerDateFormatter $formatter; public function initialize() { $this->formatter = $this->modx->services->get(modManagerDateFormatter::class); + + $canChange = $this->modx->hasPermission('save_document') && $this->modx->hasPermission('edit_document'); + $this->canPurge = $canChange && $this->modx->hasPermission('purge_deleted'); + $this->canUndelete = $canChange && $this->modx->hasPermission('undelete_document'); + $this->canUPublish = $canChange && $this->modx->hasPermission('publish_document'); + return parent::initialize(); } @@ -138,16 +148,22 @@ public function prepareRow(xPDOObject $object) return []; } + $permissions = [ + 'purge' => $this->canPurge && $object->checkPolicy('purge_deleted'), + 'undelete' => $this->canUndelete && $object->checkPolicy('undelete_document'), + 'publish' => $this->canUPublish && $object->checkPolicy('publish_document') + ]; + $charset = $this->modx->getOption('modx_charset', null, 'UTF-8'); - $objectArray = $object->toArray(); - $objectArray['pagetitle'] = htmlentities($objectArray['pagetitle'], ENT_COMPAT, $charset); - $objectArray['content'] = htmlentities($objectArray['content'], ENT_COMPAT, $charset); + $resourceData = $object->toArray(); + $resourceData['pagetitle'] = htmlentities($resourceData['pagetitle'], ENT_COMPAT, $charset); + $resourceData['content'] = htmlentities($resourceData['content'], ENT_COMPAT, $charset); // to enable a better detection of the resource's location, we also construct the // parent-child path to the resource $parents = []; - $parent = $objectArray['parent']; + $parent = $resourceData['parent']; while ($parent != 0) { $parentObject = $this->modx->getObject(modResource::class, $parent); @@ -163,46 +179,11 @@ public function prepareRow(xPDOObject $object) foreach ($parents as $parent) { $parentPath = $parent->get('pagetitle') . ' (' . $parent->get('id') . ') > ' . $parentPath; } - $objectArray['parentPath'] = '[' . $objectArray['context_key'] . '] ' . $parentPath; - - // TODO implement permission checks for every resource and return only resources user is allowed to see - - // show the permissions for the context - $canView = $this->modx->hasPermission('view_document'); - $canPurge = $this->modx->hasPermission('purge_deleted'); - $canUndelete = $this->modx->hasPermission('undelete_document'); - $canPublish = $this->modx->hasPermission('publish_document'); - $canSave = $this->modx->hasPermission('save_document'); - $canEdit = $this->modx->hasPermission('edit_document'); - $canList = $this->modx->hasPermission('list'); - $canLoad = $this->modx->hasPermission('load'); - - $objectArray['iconCls'] = $this->modx->getOption('mgr_source_icon', null, 'icon-folder-open-o'); - - $cls = []; - $cls[] = 'restore'; - $cls[] = 'purge'; - $cls[] = 'undelete_document'; - - $cls = []; - if ($object->checkPolicy('purge_deleted') && $canSave && $canEdit && $canPurge) { - $cls[] = 'trashpurge'; - } - if ($object->checkPolicy('undelete_document') && $canSave && $canEdit) { - $cls[] = 'trashundelete'; - } - if ($object->checkPolicy('save') && $canSave && $canEdit) { - $cls[] = 'trashsave'; - } - if ($object->checkPolicy('edit') && $canSave && $canEdit) { - $cls[] = 'trashedit'; - } - $cls[] = 'trashrow'; - - $objectArray['cls'] = implode(' ', $cls); + $resourceData['parentPath'] = '[' . $resourceData['context_key'] . '] ' . $parentPath; - $objectArray['deletedon'] = $this->formatter->formatDateTime($objectArray['deletedon']); + $resourceData['deletedon'] = $this->formatter->formatDateTime($resourceData['deletedon']); + $resourceData['permissions'] = $permissions; - return $objectArray; + return $resourceData; } } diff --git a/core/src/Revolution/Processors/Security/Access/GetList.php b/core/src/Revolution/Processors/Security/Access/GetList.php index e1d4da8362d..847f018ba4f 100644 --- a/core/src/Revolution/Processors/Security/Access/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/GetList.php @@ -1,4 +1,5 @@ modx->lexicon('access_type_err_ns'); } + $canManage = $this->modx->hasPermission('access_permissions'); + $this->canCreate = $canManage; + $this->canEdit = $canManage; + $this->canRemove = $canManage; + $this->canEditGroups = $this->modx->hasPermission('usergroup_edit'); + $this->canEditPolicies = $this->modx->hasPermission('policy_edit'); + return parent::initialize(); } @@ -135,7 +149,7 @@ public function prepareRow(xPDOObject $object) $targetName = $this->getAnonymName(); } - $objArray = [ + $accessData = [ 'id' => $object->get('id'), 'target' => $object->get('target'), 'target_name' => $targetName, @@ -148,13 +162,24 @@ public function prepareRow(xPDOObject $object) ]; if (isset($object->_fieldMeta['context_key'])) { - $objArray['context_key'] = $object->get('context_key'); + $accessData['context_key'] = $object->get('context_key'); } // Prevent default Admin ACL from edit and remove - $objArray['cls'] = (($object->get('target') === 'mgr') && ($principal->get('name') === 'Administrator') && ($policyName === 'Administrator') && ($object->get('authority') === 0)) ? '' : 'pedit premove'; + if (($object->get('target') === 'mgr') && ($principal->get('name') === 'Administrator') && ($policyName === 'Administrator') && ($object->get('authority') === 0)) { + $accessData['permissions'] = []; + $accessData['isProtected'] = true; + } else { + $accessData['permissions'] = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + } + $accessData['canEditGroups'] = $this->canEditGroups; + $accessData['canEditPolicies'] = $this->canEditPolicies; - return $objArray; + return $accessData; } /** diff --git a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php index 94c420f6b5b..7c15d59a28e 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php @@ -44,6 +44,13 @@ class GetList extends GetListProcessor /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ protected $isGridFilter = false; + public $canCreate = false; + public $canEdit = false; + public $canEditTemplate = false; + public $canRemove = false; + protected $corePolicies; + protected $corePolicyTemplates; + // private $templatesTranslated = []; /** * @return bool @@ -56,8 +63,17 @@ public function initialize() 'group' => false, 'combo' => false, 'query' => '', + 'exclude' => 'creator' ]); $this->isGridFilter = $this->getProperty('isGridFilter', false); + + $this->canCreate = $this->modx->hasPermission('policy_new') && $this->modx->hasPermission('policy_save'); + $this->canEdit = $this->modx->hasPermission('policy_edit'); + $this->canEditTemplate = $this->modx->hasPermission('policy_template_edit'); + $this->canRemove = $this->modx->hasPermission('policy_delete'); + $this->corePolicies = $this->classKey::getCorePolicies(); + $this->corePolicyTemplates = modAccessPolicyTemplate::getCoreTemplates(); + return $initialized; } @@ -206,45 +222,60 @@ public function beforeIteration(array $list) } /** - * @param xPDOObject $object + * @param xPDOObject|modAccessPolicy $object * @return array */ public function prepareRow(xPDOObject $object) { - $policy = $object->toArray(); + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'updateTemplate' => $this->canEditTemplate, + 'delete' => $this->canRemove + ]; + $policyData = $object->toArray(); + $policyName = $object->get('name'); + $policyPermissions = $object->get('data'); + $isCorePolicy = $object->isCorePolicy($policyName); + $this->setActivePermissionsCount($policyData, $policyPermissions); + + $policyData['reserved'] = ['name' => $this->corePolicies]; + $policyData['isProtected'] = $isCorePolicy; + $policyData['creator'] = $isCorePolicy ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCorePolicy) { + unset($permissions['delete']); + } + $policyData['permissions'] = $permissions; + $policyData['policyPermissions'] = array_keys($policyPermissions, 1); + $policyData['description_trans'] = $this->modx->lexicon($policyData['description']); + unset($policyData['data']); - $policy['cls'] = $this->prepareRowClasses($object); + return $policyData; + } - $permissions = []; + protected function setActivePermissionsCount(array &$policy, array $data) + { if (!empty($policy['total_permissions'])) { - $data = $object->get('data'); - $ct = 0; + $n = 0; if (!empty($data)) { foreach ($data as $k => $v) { if (!empty($v)) { - $permissions[] = $k; - $ct++; + $n++; } } } - $policy['active_permissions'] = $ct; + $policy['active_permissions'] = $n; $policy['active_of'] = $this->modx->lexicon('active_of', [ 'active' => $policy['active_permissions'], 'total' => $policy['total_permissions'], ]); - $policy['permissions'] = $permissions; } - - unset($policy['data']); - - $policy['description_trans'] = $this->modx->lexicon($policy['description']); - - return $policy; } /** * @param xPDOObject|modAccessPolicy $object - * + * @deprecated as of MODX 3.1.0; new permissions handling replaces css class-based specifiers * @return string */ protected function prepareRowClasses(xPDOObject $object) 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 877a7b5f32f..5008e29bb92 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/Template/GetList.php @@ -36,6 +36,14 @@ class GetList extends GetListProcessor public $permission = 'policy_template_view'; public $languageTopics = ['policy', 'en:policy']; + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + protected $isGridFilter = false; + public $canCreate = false; + public $canEdit = false; + public $canRemove = false; + protected $corePolicyTemplates; + protected $corePolicyTemplateGroups; + /** * @return bool */ @@ -45,7 +53,16 @@ public function initialize() $this->setDefaultProperties([ 'sortAlias' => 'modAccessPolicyTemplate', 'query' => '', + 'exclude' => 'creator' ]); + $this->isGridFilter = $this->getProperty('isGridFilter', false); + + $this->canCreate = $this->modx->hasPermission('policy_template_new') && $this->modx->hasPermission('policy_template_save'); + $this->canEdit = $this->modx->hasPermission('policy_template_edit'); + $this->canRemove = $this->modx->hasPermission('policy_template_delete'); + $this->corePolicyTemplates = $this->classKey::getCoreTemplates(); + $this->corePolicyTemplateGroups = modAccessPolicyTemplateGroup::getCoreGroups(); + return $initialized; } @@ -104,22 +121,36 @@ public function prepareQueryAfterCount(xPDOQuery $c) } /** - * @param xPDOObject $object + * @param xPDOObject|modAccessPolicyTemplate $object * @return array */ public function prepareRow(xPDOObject $object) { - $template = $object->toArray(); - - $template['description_trans'] = $this->modx->lexicon($template['description']); - $template['cls'] = $this->prepareRowClasses($object); + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + $templateData = $object->toArray(); + $templateName = $object->get('name'); + $isCoreTemplate = $object->isCoreTemplate($templateName); + + $templateData['reserved'] = ['name' => $this->corePolicyTemplates]; + $templateData['isProtected'] = $isCoreTemplate; + $templateData['creator'] = $isCoreTemplate ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCoreTemplate) { + unset($permissions['delete']); + } + $templateData['permissions'] = $permissions; + $templateData['description_trans'] = $this->modx->lexicon($templateData['description']); - return $template; + return $templateData; } /** * @param xPDOObject|modAccessPolicyTemplate $object - * + * @deprecated as of MODX 3.1.0; new permissions handling replaces css class-based specifiers * @return string */ protected function prepareRowClasses(xPDOObject $object) diff --git a/core/src/Revolution/Processors/Security/Access/Policy/Template/Group/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/Template/Group/GetList.php index 9ba6a537f4e..74691b65303 100644 --- a/core/src/Revolution/Processors/Security/Access/Policy/Template/Group/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/Policy/Template/Group/GetList.php @@ -1,4 +1,5 @@ toArray(); - - $group['cls'] = static::CLASS_ALLOW_EDIT; $group['description'] = $this->modx->lexicon($group['description']); return $group; diff --git a/core/src/Revolution/Processors/Security/Access/UserGroup/AccessNamespace/GetList.php b/core/src/Revolution/Processors/Security/Access/UserGroup/AccessNamespace/GetList.php index 10ed7a3fd00..02361ce4679 100644 --- a/core/src/Revolution/Processors/Security/Access/UserGroup/AccessNamespace/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/UserGroup/AccessNamespace/GetList.php @@ -42,6 +42,13 @@ class GetList extends GetListProcessor /** @var modUserGroup $userGroup */ public $userGroup; + /** @var bool $canCreate Whether user can assign a new Category ACL entry for a given User Group */ + public $canCreate = false; + /** @var bool $canEdit Whether user can change a Category ACL entry for a given User Group */ + public $canEdit = false; + /** @var bool $canRemove Whether user can remove a Category ACL entry for a given User Group */ + public $canRemove = false; + /** * @return bool */ @@ -64,6 +71,15 @@ public function initialize() if ($this->getProperty('sort') == 'role_display') { $this->setProperty('sort', 'authority'); } + /* + Currently, all actions essentially relate to editing a User Group. + Nonetheless, we maintain each separately to remain consistent with how permissions + are relayed throughout the MODX app + */ + $canChange = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canCreate = $canChange; + $this->canEdit = $canChange; + $this->canRemove = $canChange; return $initialized; } @@ -142,33 +158,31 @@ public function useSecondaryGroupCondition(string $sortBy, string $groupBy, stri */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - if (empty($objectArray['name'])) { - $objectArray['name'] = '(' . $this->modx->lexicon('none') . ')'; + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $aclData = $object->toArray(); + if (empty($aclData['name'])) { + $aclData['name'] = '(' . $this->modx->lexicon('none') . ')'; } - $objectArray['authority_name'] = !empty($objectArray['role_name']) - ? $objectArray['role_name'] . ' - ' . $objectArray['authority'] - : $objectArray['authority'] + $aclData['authority_name'] = !empty($aclData['role_name']) + ? $aclData['role_name'] . ' - ' . $aclData['authority'] + : $aclData['authority'] ; /* get permissions list */ - $data = $objectArray['policy_data']; - unset($objectArray['policy_data']); + $aclData['policyPermissions'] = []; + $data = $aclData['policy_data']; + unset($aclData['policy_data']); $data = $this->modx->fromJSON($data); if (!empty($data)) { - $permissions = []; - foreach ($data as $permission => $enabled) { - if (!$enabled) { - continue; - } - $permissions[] = $permission; - } - $objectArray['permissions'] = implode(', ', $permissions); + $aclData['policyPermissions'] = array_keys($data, 1); } + $aclData['permissions'] = $permissions; - $cls = 'pedit premove'; - $objectArray['cls'] = $cls; - - return $objectArray; + return $aclData; } } diff --git a/core/src/Revolution/Processors/Security/Access/UserGroup/Category/GetList.php b/core/src/Revolution/Processors/Security/Access/UserGroup/Category/GetList.php index a65e1f1198f..ccfdf0ff795 100644 --- a/core/src/Revolution/Processors/Security/Access/UserGroup/Category/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/UserGroup/Category/GetList.php @@ -43,6 +43,13 @@ class GetList extends GetListProcessor /** @var modUserGroup $userGroup */ public $userGroup; + /** @var bool $canCreate Whether user can assign a new Category ACL entry for a given User Group */ + public $canCreate = false; + /** @var bool $canEdit Whether user can change a Category ACL entry for a given User Group */ + public $canEdit = false; + /** @var bool $canRemove Whether user can remove a Category ACL entry for a given User Group */ + public $canRemove = false; + /** * @return bool */ @@ -65,6 +72,15 @@ public function initialize() if ($this->getProperty('sort') == 'role_display') { $this->setProperty('sort', 'authority'); } + /* + Currently, all actions essentially relate to editing a User Group. + Nonetheless, we maintain each separately to remain consistent with how permissions + are relayed throughout the MODX app + */ + $canChange = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canCreate = $canChange; + $this->canEdit = $canChange; + $this->canRemove = $canChange; return $initialized; } @@ -143,33 +159,35 @@ public function useSecondaryGroupCondition(string $sortBy, string $groupBy, stri */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - if (empty($objectArray['name'])) { - $objectArray['name'] = '(' . $this->modx->lexicon('none') . ')'; + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $aclData = $object->toArray(); + if (empty($aclData['name'])) { + $aclData['name'] = '(' . $this->modx->lexicon('none') . ')'; } /* get permissions list */ - $data = $objectArray['policy_data']; - unset($objectArray['policy_data']); + $aclData['policyPermissions'] = []; + $data = $aclData['policy_data']; + unset($aclData['policy_data']); $data = $this->modx->fromJSON($data); if (!empty($data)) { - $permissions = []; - foreach ($data as $permission => $enabled) { - if (!$enabled) { - continue; - } - $permissions[] = $permission; - } - $objectArray['permissions'] = implode(', ', $permissions); + $aclData['policyPermissions'] = array_keys($data, 1); } - - $cls = ''; - if (($objectArray['target'] === 'web' || $objectArray['target'] === 'mgr') && $objectArray['policy_name'] === 'Administrator' && ($this->userGroup && $this->userGroup->get('name') === 'Administrator')) { - } else { - $cls .= 'pedit premove'; + if ( + in_array($aclData['target'], ['web', 'mgr']) + && $aclData['policy_name'] === 'Administrator' + && ($this->userGroup && $this->userGroup->get('name') === 'Administrator') + ) { + $permissions['edit'] = false; + $permissions['delete'] = false; } - $objectArray['cls'] = $cls; + $aclData['permissions'] = $permissions; - return $objectArray; + return $aclData; } } diff --git a/core/src/Revolution/Processors/Security/Access/UserGroup/Context/GetList.php b/core/src/Revolution/Processors/Security/Access/UserGroup/Context/GetList.php index 26ced21f3b9..ea97f0989ea 100644 --- a/core/src/Revolution/Processors/Security/Access/UserGroup/Context/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/UserGroup/Context/GetList.php @@ -41,6 +41,13 @@ class GetList extends GetListProcessor /** @var modUserGroup $userGroup */ public $userGroup; + /** @var bool $canCreate Whether user can assign a new Context ACL entry for a given User Group */ + public $canCreate = false; + /** @var bool $canEdit Whether user can change a Context ACL entry for a given User Group */ + public $canEdit = false; + /** @var bool $canRemove Whether user can remove a Context ACL entry for a given User Group */ + public $canRemove = false; + /** * @return mixed */ @@ -63,6 +70,15 @@ public function initialize() if ($this->getProperty('sort') == 'role_display') { $this->setProperty('sort', 'authority'); } + /* + Currently, all actions essentially relate to editing a User Group. + Nonetheless, we maintain each separately to remain consistent with how permissions + are relayed throughout the MODX app + */ + $canChange = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canCreate = $canChange; + $this->canEdit = $canChange; + $this->canRemove = $canChange; return $initialized; } @@ -135,37 +151,35 @@ public function useSecondaryGroupCondition(string $sortBy, string $groupBy, stri */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - if (empty($objectArray['name'])) { - $objectArray['name'] = '(' . $this->modx->lexicon('none') . ')'; + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $aclData = $object->toArray(); + if (empty($aclData['name'])) { + $aclData['name'] = '(' . $this->modx->lexicon('none') . ')'; } /* get permissions list */ - $data = $objectArray['policy_data']; - unset($objectArray['policy_data']); + $aclData['policyPermissions'] = []; + $data = $aclData['policy_data']; + unset($aclData['policy_data']); $data = $this->modx->fromJSON($data); if (!empty($data)) { - $permissions = []; - foreach ($data as $permission => $enabled) { - if (!$enabled) { - continue; - } - $permissions[] = $permission; - } - $objectArray['permissions'] = implode(', ', $permissions); + $aclData['policyPermissions'] = array_keys($data, 1); } - - $cls = ''; if ( - ($objectArray['target'] === 'web' || $objectArray['target'] === 'mgr') - && $objectArray['policy_name'] === 'Administrator' + in_array($aclData['target'], ['web', 'mgr']) + && $aclData['policy_name'] === 'Administrator' && ($this->userGroup && $this->userGroup->get('name') === 'Administrator') ) { - } else { - $cls .= 'pedit premove'; + $permissions['edit'] = false; + $permissions['delete'] = false; } - $objectArray['cls'] = $cls; + $aclData['permissions'] = $permissions; - return $objectArray; + return $aclData; } } diff --git a/core/src/Revolution/Processors/Security/Access/UserGroup/ResourceGroup/GetList.php b/core/src/Revolution/Processors/Security/Access/UserGroup/ResourceGroup/GetList.php index 2b951b2392a..2e49ace8927 100644 --- a/core/src/Revolution/Processors/Security/Access/UserGroup/ResourceGroup/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/UserGroup/ResourceGroup/GetList.php @@ -43,6 +43,13 @@ class GetList extends GetListProcessor /** @var modUserGroup $userGroup */ public $userGroup; + /** @var bool $canCreate Whether user can assign a new Resource Group ACL entry for a given User Group */ + public $canCreate = false; + /** @var bool $canEdit Whether user can change a Resource Group ACL entry for a given User Group */ + public $canEdit = false; + /** @var bool $canRemove Whether user can remove a Resource Group ACL entry for a given User Group */ + public $canRemove = false; + /** * @return bool */ @@ -65,6 +72,16 @@ public function initialize() if ($this->getProperty('sort') == 'role_display') { $this->setProperty('sort', 'authority'); } + /* + Currently, all actions essentially relate to editing a User Group. + Nonetheless, we maintain each separately to remain consistent with how permissions + are relayed throughout the MODX app + */ + $canChange = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canCreate = $canChange; + $this->canEdit = $canChange; + $this->canRemove = $canChange; + return $initialized; } @@ -143,36 +160,35 @@ public function useSecondaryGroupCondition(string $sortBy, string $groupBy, stri */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - if (empty($objectArray['name'])) { - $objectArray['name'] = '(' . $this->modx->lexicon('none') . ')'; + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $aclData = $object->toArray(); + if (empty($aclData['name'])) { + $aclData['name'] = '(' . $this->modx->lexicon('none') . ')'; } /* get permissions list */ - $data = $objectArray['policy_data']; - unset($objectArray['policy_data']); + $aclData['policyPermissions'] = []; + $data = $aclData['policy_data']; + unset($aclData['policy_data']); $data = $this->modx->fromJSON($data); if (!empty($data)) { - foreach ($data as $permission => $enabled) { - if (!$enabled) { - continue; - } - $permissions[] = $permission; - } - $objectArray['permissions'] = implode(', ', $permissions); + $aclData['policyPermissions'] = array_keys($data, 1); } - - $cls = ''; if ( - ($objectArray['target'] === 'web' || $objectArray['target'] == 'mgr') - && $objectArray['policy_name'] === 'Administrator' + in_array($aclData['target'], ['web', 'mgr']) + && $aclData['policy_name'] === 'Administrator' && ($this->userGroup && $this->userGroup->get('name') === 'Administrator') ) { - } else { - $cls .= 'pedit premove'; + $permissions['edit'] = false; + $permissions['delete'] = false; } - $objectArray['cls'] = $cls; + $aclData['permissions'] = $permissions; - return $objectArray; + return $aclData; } } diff --git a/core/src/Revolution/Processors/Security/Access/UserGroup/Source/GetList.php b/core/src/Revolution/Processors/Security/Access/UserGroup/Source/GetList.php index dc3269c4ad9..a77f844e89a 100644 --- a/core/src/Revolution/Processors/Security/Access/UserGroup/Source/GetList.php +++ b/core/src/Revolution/Processors/Security/Access/UserGroup/Source/GetList.php @@ -42,6 +42,13 @@ class GetList extends GetListProcessor /** @var modUserGroup $userGroup */ public $userGroup; + /** @var bool $canCreate Whether user can assign a new Category ACL entry for a given User Group */ + public $canCreate = false; + /** @var bool $canEdit Whether user can change a Category ACL entry for a given User Group */ + public $canEdit = false; + /** @var bool $canRemove Whether user can remove a Category ACL entry for a given User Group */ + public $canRemove = false; + /** * @return mixed */ @@ -64,6 +71,15 @@ public function initialize() if ($this->getProperty('sort') == 'role_display') { $this->setProperty('sort', 'authority'); } + /* + Currently, all actions essentially relate to editing a User Group. + Nonetheless, we maintain each separately to remain consistent with how permissions + are relayed throughout the MODX app + */ + $canChange = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canCreate = $canChange; + $this->canEdit = $canChange; + $this->canRemove = $canChange; return $initialized; } @@ -149,41 +165,39 @@ public function useSecondaryGroupCondition(string $sortBy, string $groupBy, stri */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - if (empty($objectArray['name'])) { - $objectArray['name'] = '(' . $this->modx->lexicon('none') . ')'; + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $aclData = $object->toArray(); + if (empty($aclData['name'])) { + $aclData['name'] = '(' . $this->modx->lexicon('none') . ')'; } - $objectArray['authority_name'] = !empty($objectArray['role_name']) - ? $objectArray['role_name'] . ' - ' . $objectArray['authority'] - : $objectArray['authority'] + $aclData['authority_name'] = !empty($aclData['role_name']) + ? $aclData['role_name'] . ' - ' . $aclData['authority'] + : $aclData['authority'] ; /* get permissions list */ - $data = $objectArray['policy_data']; - unset($objectArray['policy_data']); + $aclData['policyPermissions'] = []; + $data = $aclData['policy_data']; + unset($aclData['policy_data']); $data = $this->modx->fromJSON($data); if (!empty($data)) { - $permissions = []; - foreach ($data as $permission => $enabled) { - if (!$enabled) { - continue; - } - $permissions[] = $permission; - } - $objectArray['permissions'] = implode(', ', $permissions); + $aclData['policyPermissions'] = array_keys($data, 1); } - - $cls = ''; if ( - ($objectArray['target'] === 'web' || $objectArray['target'] === 'mgr') - && $objectArray['policy_name'] === 'Administrator' + in_array($aclData['target'], ['web', 'mgr']) + && $aclData['policy_name'] === 'Administrator' && ($this->userGroup && $this->userGroup->get('name') === 'Administrator') ) { - } else { - $cls .= 'pedit premove'; + $permissions['edit'] = false; + $permissions['delete'] = false; } - $objectArray['cls'] = $cls; + $aclData['permissions'] = $permissions; - return $objectArray; + return $aclData; } } diff --git a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php index 907f2106a03..32c354b730c 100644 --- a/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Profile/GetList.php @@ -28,6 +28,8 @@ class GetList extends GetListProcessor public $classKey = modFormCustomizationProfile::class; public $languageTopics = ['formcustomization']; public $permission = 'customize_forms'; + + public $canCreate = false; public $canEdit = false; public $canRemove = false; @@ -36,8 +38,12 @@ class GetList extends GetListProcessor */ public function initialize() { - $this->setDefaultProperties(['query' => '']); - $this->canEdit = $this->modx->hasPermission('save'); + $this->setDefaultProperties([ + 'query' => '' + ]); + $canSave = $this->modx->hasPermission('save'); + $this->canCreate = $canSave; + $this->canEdit = $canSave; $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); } @@ -74,14 +80,12 @@ public function getData() */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectArray['perm'] = []; - if ($this->canEdit) { - $objectArray['perm'][] = 'pedit'; - } - if ($this->canRemove) { - $objectArray['perm'][] = 'premove'; - } - return $objectArray; + $profileArray = $object->toArray(); + $profileArray['permissions'] = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + return $profileArray; } } diff --git a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php index 1fcd77a4cf9..4ceffddd44d 100644 --- a/core/src/Revolution/Processors/Security/Forms/Set/GetList.php +++ b/core/src/Revolution/Processors/Security/Forms/Set/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties(['profile' => 0, 'query' => '']); - $this->canEdit = $this->modx->hasPermission('save'); + $this->setDefaultProperties([ + 'profile' => 0, + 'query' => '' + ]); + $canSave = $this->modx->hasPermission('save'); + $this->canCreate = $canSave; + $this->canEdit = $canSave; $this->canRemove = $this->modx->hasPermission('remove'); return parent::initialize(); } @@ -83,7 +91,7 @@ public function prepareQueryAfterCount(xPDOQuery $c) */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); + $fcSetArray = $object->toArray(); $constraint_field = $object->get('constraint_field'); $constraint = $object->get('constraint'); @@ -91,16 +99,14 @@ public function prepareRow(xPDOObject $object) if ($constraint === '') { $constraint = "'{$constraint}'"; } - $objectArray['constraint_data'] = $object->get('constraint_class') . '.' . $constraint_field . ' = ' . $constraint; - } - $objectArray['perm'] = []; - if ($this->canEdit) { - $objectArray['perm'][] = 'pedit'; - } - if ($this->canRemove) { - $objectArray['perm'][] = 'premove'; + $fcSetArray['constraint_data'] = $object->get('constraint_class') . '.' . $constraint_field . ' = ' . $constraint; } + $fcSetArray['permissions'] = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; - return $objectArray; + return $fcSetArray; } } diff --git a/core/src/Revolution/Processors/Security/Group/GetList.php b/core/src/Revolution/Processors/Security/Group/GetList.php index cb2e4e7f3b8..4405ec86887 100644 --- a/core/src/Revolution/Processors/Security/Group/GetList.php +++ b/core/src/Revolution/Processors/Security/Group/GetList.php @@ -1,4 +1,5 @@ false, 'combo' => false, ]); + $this->canEditGroups = $this->modx->hasPermission('usergroup_edit'); + $this->canEditRoles = $this->modx->hasPermission('edit_role'); return $initialized; } @@ -111,4 +118,17 @@ public function prepareQueryBeforeCount(xPDOQuery $c) return $c; } + + /** + * @param xPDOObject $object + * @return array + */ + public function prepareRow(xPDOObject $object) + { + $userGroupData = $object->toArray('', false, true); + $userGroupData['canEditGroups'] = $this->canEditGroups; + $userGroupData['canEditRoles'] = $this->canEditRoles; + + return $userGroupData; + } } diff --git a/core/src/Revolution/Processors/Security/Group/User/GetList.php b/core/src/Revolution/Processors/Security/Group/User/GetList.php index 628b8cf32d6..20920c2e896 100644 --- a/core/src/Revolution/Processors/Security/Group/User/GetList.php +++ b/core/src/Revolution/Processors/Security/Group/User/GetList.php @@ -35,6 +35,11 @@ class GetList extends GetListProcessor public $permission = 'usergroup_user_list'; public $languageTopics = ['user']; + protected $canEditGroups = false; + protected $canEditGroupUsers = false; + protected $canEditRoles = false; + protected $canEditUsers = false; + /** * @return bool */ @@ -44,6 +49,10 @@ public function initialize() 'usergroup' => false, 'query' => '' ]); + $this->canEditGroups = $this->modx->hasPermission('usergroup_edit') && $this->modx->hasPermission('usergroup_save'); + $this->canEditGroupUsers = $this->modx->hasPermission('usergroup_user_edit'); + $this->canEditRoles = $this->modx->hasPermission('edit_role') && $this->modx->hasPermission('save_role'); + $this->canEditUsers = $this->modx->hasPermission('edit_user') && $this->modx->hasPermission('save_user'); return parent::initialize(); } @@ -99,9 +108,15 @@ public function prepareQueryAfterCount(xPDOQuery $c) */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray('', false, true); - $objectArray['role_name'] .= ' - ' . $objectArray['authority']; + $groupUserData = $object->toArray('', false, true); + $groupUserData['role_name'] .= ' - ' . $groupUserData['authority']; + $groupUserData['permissions'] = [ + 'updateGroups' => $this->canEditGroups, + 'updateGroupUsers' => $this->canEditGroupUsers, + 'updateRoles' => $this->canEditRoles, + 'updateUsers' => $this->canEditUsers + ]; - return $objectArray; + return $groupUserData; } } diff --git a/core/src/Revolution/Processors/Security/Role/GetList.php b/core/src/Revolution/Processors/Security/Role/GetList.php index 69693afbed5..fc857e393a2 100644 --- a/core/src/Revolution/Processors/Security/Role/GetList.php +++ b/core/src/Revolution/Processors/Security/Role/GetList.php @@ -33,7 +33,11 @@ class GetList extends GetListProcessor public $languageTopics = ['user']; public $permission = 'view_role'; public $defaultSortField = 'authority'; + + public $canCreate = false; + public $canEdit = false; public $canRemove = false; + protected $coreRoles; /** * {@inheritDoc} @@ -49,7 +53,10 @@ public function initialize() $this->setProperty('sort', 'name'); } + $this->canCreate = $this->modx->hasPermission('new_role') && $this->modx->hasPermission('save_role'); + $this->canEdit = $this->modx->hasPermission('edit_role') && $this->modx->hasPermission('save_role'); $this->canRemove = $this->modx->hasPermission('delete_role'); + $this->coreRoles = $this->classKey::getCoreRoles(); return $initialized; } @@ -98,30 +105,32 @@ public function isAssigned(int $id) /** * {@inheritDoc} - * @param xPDOObject $object + * @param xPDOObject|modUserGroupRole $object * @return array */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectId = $object->get('id'); + // Note: Role does not have a checkPolicy() method + $permissions = [ + 'create' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $roleData = $object->toArray(); + $roleId = $object->get('id'); $roleName = $object->get('name'); - $isCoreRole = in_array($objectId, [1, 2]) || in_array($roleName, ['Super User', 'Member']); - - $perm = []; - if (!$isCoreRole) { - $perm[] = 'edit'; - if ($this->isAssigned($objectId)) { - $objectArray['isAssigned'] = 1; - } - if ($this->canRemove) { - $perm[] = 'remove'; - } - } else { - $objectArray['isProtected'] = 1; + $isCoreRole = $object->isCoreRole($roleName); + + if ($this->isAssigned($roleId)) { + $roleData['isAssigned'] = 1; } - $objectArray['perm'] = implode(' ', $perm); - return $objectArray; + $roleData['reserved'] = ['name' => $this->coreRoles]; + $roleData['isProtected'] = $isCoreRole; + $roleData['creator'] = $isCoreRole ? 'modx' : strtolower($this->modx->lexicon('user')) ; + $roleData['permissions'] = !$isCoreRole ? $permissions : [] ; + + return $roleData; } } diff --git a/core/src/Revolution/Processors/Security/User/Get.php b/core/src/Revolution/Processors/Security/User/Get.php index 98e86362546..ce891c4fd06 100644 --- a/core/src/Revolution/Processors/Security/User/Get.php +++ b/core/src/Revolution/Processors/Security/User/Get.php @@ -85,6 +85,9 @@ public function getGroups() $this->object->get('primary_group') === $member->get('user_group'), $member->get('rank'), $member->get('user_group_desc'), + $this->modx->hasPermission('usergroup_edit'), + $this->modx->hasPermission('usergroup_user_edit'), + $this->modx->hasPermission('edit_role') ]; } $this->object->set('groups', '(' . $this->modx->toJSON($data) . ')'); diff --git a/core/src/Revolution/Processors/Security/User/GetList.php b/core/src/Revolution/Processors/Security/User/GetList.php index a138ff2fd8b..af324b86afa 100644 --- a/core/src/Revolution/Processors/Security/User/GetList.php +++ b/core/src/Revolution/Processors/Security/User/GetList.php @@ -1,4 +1,5 @@ getProperty('sort') === 'id') { $this->setProperty('sort', $this->modx->getAlias($this->classKey) . '.id'); } + + $this->canCreate = $this->modx->hasPermission('new_user') && $this->modx->hasPermission('save_user'); + $this->canEdit = $this->modx->hasPermission('edit_user') && $this->modx->hasPermission('save_user'); + $this->canRemove = $this->modx->hasPermission('delete_user'); + return $initialized; } @@ -115,11 +125,16 @@ public function prepareQueryAfterCount(xPDOQuery $c) */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectArray['blocked'] = $object->get('blocked') ? true : false; - $objectArray['cls'] = 'pupdate premove pcopy'; - unset($objectArray['password'], $objectArray['cachepwd'], $objectArray['salt']); + $userData = $object->toArray(); + $userData['blocked'] = $object->get('blocked') ? true : false; + $userData['permissions'] = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + unset($userData['password'], $userData['cachepwd'], $userData['salt']); - return $objectArray; + return $userData; } } diff --git a/core/src/Revolution/Processors/Security/User/GetRecentlyEditedResources.php b/core/src/Revolution/Processors/Security/User/GetRecentlyEditedResources.php index f2a8955cb4e..ca047f0cb5b 100644 --- a/core/src/Revolution/Processors/Security/User/GetRecentlyEditedResources.php +++ b/core/src/Revolution/Processors/Security/User/GetRecentlyEditedResources.php @@ -81,7 +81,6 @@ public function prepareQueryBeforeCount(xPDOQuery $c) return $c; } - /** * Prepare the row for iteration * @param xPDOObject $object @@ -110,6 +109,7 @@ public function prepareRow(xPDOObject $object) /** @var modUser $user */ if ($user = $object->getOne('User')) { + /** @disregard P1013 Intelephense can not find this User instance method (getPhoto), but it does exist and is available here */ $row = array_merge( $row, $user->get(['username']), @@ -119,33 +119,6 @@ public function prepareRow(xPDOObject $object) /** @var modUserGroup $group */ $row['group'] = ($group = $user->getOne('PrimaryGroup')) ? $group->get('name') : ''; } - - $row['menu'] = []; - $row['menu'][] = [ - 'text' => $this->modx->lexicon('resource_overview'), - 'params' => [ - 'a' => 'resource/data', - 'id' => $resource->get('id'), - 'type' => 'view', - ], - ]; - if ($this->modx->hasPermission('edit_document')) { - $row['menu'][] = [ - 'text' => $this->modx->lexicon('resource_edit'), - 'params' => [ - 'a' => 'resource/update', - 'id' => $resource->get('id'), - 'type' => 'edit', - ], - ]; - } - - $row['menu'][] = '-'; - $row['menu'][] = [ - 'text' => $this->modx->lexicon('resource_view'), - 'handler' => 'this.preview', - ]; - $row['link'] = $this->modx->makeUrl($resource->get('id'), $resource->get('context_key')); return $row; diff --git a/core/src/Revolution/Processors/Source/GetList.php b/core/src/Revolution/Processors/Source/GetList.php index 33cd1fbf571..2413aaf111b 100644 --- a/core/src/Revolution/Processors/Source/GetList.php +++ b/core/src/Revolution/Processors/Source/GetList.php @@ -33,9 +33,15 @@ class GetList extends GetListProcessor public $languageTopics = ['source']; public $permission = 'source_view'; - /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ + /** @var bool $isGridFilter Indicates the target of this list data is a filter field */ protected $isGridFilter = false; + public $canCreate = false; + public $canEdit = false; + public $canRemove = false; + + protected $coreSources; + /** * {@inheritDoc} * @return boolean @@ -47,8 +53,15 @@ public function initialize() 'showNone' => false, 'query' => '', 'streamsOnly' => false, + 'exclude' => 'creator' ]); $this->isGridFilter = $this->getProperty('isGridFilter', false); + + $this->canCreate = $this->modx->hasPermission('source_save'); + $this->canEdit = $this->modx->hasPermission('source_edit'); + $this->canRemove = $this->modx->hasPermission('source_delete'); + $this->coreSources = $this->classKey::getCoreSources(); + return $initialized; } @@ -143,31 +156,32 @@ public function getSortClassKey() */ public function prepareRow(xPDOObject $object) { - $canEdit = $this->modx->hasPermission('source_edit'); - $canSave = $this->modx->hasPermission('source_save'); - $canRemove = $this->modx->hasPermission('source_delete'); + $permissions = [ + 'create' => $this->canCreate && $object->checkPolicy('save'), + 'duplicate' => $this->canCreate && $object->checkPolicy('copy'), + 'update' => $this->canEdit && $object->checkPolicy('save'), + 'delete' => $this->canRemove && $object->checkPolicy('remove') + ]; + + $sourceData = $object->toArray(); + $sourceName = $object->get('name'); + $isCoreSource = $object->isCoreSource($sourceName); + + $sourceData['reserved'] = ['name' => $this->coreSources]; + $sourceData['isProtected'] = $isCoreSource; + $sourceData['creator'] = $isCoreSource ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCoreSource) { + unset($permissions['delete']); + } + $sourceData['permissions'] = $permissions; - $objectArray = $object->toArray(); - $objectArray['iconCls'] = $this->modx->getOption('mgr_source_icon', null, 'icon-folder-open-o'); + $sourceData['iconCls'] = $this->modx->getOption('mgr_source_icon', null, 'icon-folder-open-o'); $props = $object->getPropertyList(); if (isset($props['iconCls']) && !empty($props['iconCls'])) { - $objectArray['iconCls'] = $props['iconCls']; + $sourceData['iconCls'] = $props['iconCls']; } - $cls = []; - if ($canSave && $canEdit && $object->checkPolicy('save')) { - $cls[] = 'pupdate'; - } - if ($canRemove && $object->checkPolicy('remove')) { - $cls[] = 'premove'; - } - if ($canSave && $object->checkPolicy('copy')) { - $cls[] = 'pduplicate'; - } - - $objectArray['cls'] = implode(' ', $cls); - - return $objectArray; + return $sourceData; } } diff --git a/core/src/Revolution/Processors/Source/Update.php b/core/src/Revolution/Processors/Source/Update.php index 0b49c6944dc..e2066161995 100644 --- a/core/src/Revolution/Processors/Source/Update.php +++ b/core/src/Revolution/Processors/Source/Update.php @@ -1,4 +1,5 @@ object->get('name'); + $id = $this->object->get('id'); + + if (empty($name)) { + $this->addFieldError('name', $this->modx->lexicon('source_err_ns_name')); + } elseif ($this->alreadyExists($name, $id)) { + $this->addFieldError('name', $this->modx->lexicon('source_err_ae_name', [ + 'name' => $name, + ])); + } $this->setSourceProperties(); + return parent::beforeSave(); } + /** + * Check to see if a Media Source with the specified name already exists + * @param string $name + * @return boolean + */ + public function alreadyExists($name, $id) + { + return $this->modx->getCount(modMediaSource::class, [ + 'name' => $name, + 'id:!=' => $id + ]) > 0; + } + /** * Sets the properties on the source * @return void diff --git a/core/src/Revolution/Processors/System/ContentType/GetList.php b/core/src/Revolution/Processors/System/ContentType/GetList.php index 33dc6a825a0..1179a03c94f 100644 --- a/core/src/Revolution/Processors/System/ContentType/GetList.php +++ b/core/src/Revolution/Processors/System/ContentType/GetList.php @@ -1,4 +1,5 @@ modx->hasPermission('content_types'); + $this->canCreate = $canManage; + $this->canEdit = $canManage; + $this->canRemove = $canManage; + $this->coreContentTypes = $this->classKey::getCoreContentTypes(); + + return $initialized; + } + /** * Filter the query by the valueField of MODx.combo.ContentType to get the initially value displayed right * @param xPDOQuery $c @@ -42,4 +65,30 @@ public function prepareQueryAfterCount(xPDOQuery $c) } return $c; } + + /** + * @param xPDOObject|modContentType $object + * @return array + */ + public function prepareRow(xPDOObject $object) + { + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + $contentTypeData = $object->toArray(); + $dashboardName = $object->get('name'); + $isCoreContentType = $object->isCoreContentType($dashboardName); + + $contentTypeData['isProtected'] = $isCoreContentType; + $contentTypeData['creator'] = $isCoreContentType ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCoreContentType) { + unset($permissions['delete']); + } + $contentTypeData['permissions'] = $permissions; + + return $contentTypeData; + } } diff --git a/core/src/Revolution/Processors/System/Dashboard/GetList.php b/core/src/Revolution/Processors/System/Dashboard/GetList.php index 199b1b00913..7f24bb9164b 100644 --- a/core/src/Revolution/Processors/System/Dashboard/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties([ + 'query' => '', + 'exclude' => 'creator' + ]); + $canManage = $this->modx->hasPermission('dashboards'); + $this->canCreate = $canManage; + $this->canEdit = $canManage; + $this->canRemove = $canManage; + $this->coreDashboards = $this->classKey::getCoreDashboards(); + + return $initialized; + } + /** * @param xPDOQuery $c * @return xPDOQuery @@ -61,13 +87,29 @@ public function prepareQueryAfterCount(xPDOQuery $c) } /** - * @param xPDOObject $object + * @param xPDOObject|modDashboard $object * @return array */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectArray['cls'] = 'pupdate premove pduplicate'; - return $objectArray; + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + $dashboardData = $object->toArray(); + $dashboardName = $object->get('name'); + $isCoreDashboard = $object->isCoreDashboard($dashboardName); + + $dashboardData['reserved'] = ['name' => $this->coreDashboards]; + $dashboardData['isProtected'] = $isCoreDashboard; + $dashboardData['creator'] = $isCoreDashboard ? 'modx' : strtolower($this->modx->lexicon('user')) ; + if ($isCoreDashboard) { + unset($permissions['delete']); + } + $dashboardData['permissions'] = $permissions; + + return $dashboardData; } } diff --git a/core/src/Revolution/Processors/System/Dashboard/Update.php b/core/src/Revolution/Processors/System/Dashboard/Update.php index baa62021335..7ce6ee217eb 100644 --- a/core/src/Revolution/Processors/System/Dashboard/Update.php +++ b/core/src/Revolution/Processors/System/Dashboard/Update.php @@ -1,4 +1,5 @@ object->get('name'); + $id = $this->object->get('id'); + + if (empty($name)) { + $this->addFieldError('name', $this->modx->lexicon('dashboard_err_ns_name')); + } elseif ($this->alreadyExists($name, $id)) { + $this->addFieldError('name', $this->modx->lexicon('dashboard_err_ae_name', [ + 'name' => $name, + ])); + } + + return parent::beforeSave(); + } + + /** + * Check to see if a Dashboard with the specified name already exists + * @param string $name + * @return boolean + */ + public function alreadyExists($name, $id) + { + return $this->modx->getCount(modDashboard::class, [ + 'name' => $name, + 'id:!=' => $id + ]) > 0; + } + /** * @return bool */ @@ -45,8 +76,11 @@ public function afterSave() public function setWidgets() { /** @var modDashboardWidgetPlacement[] $previousWidgets */ - $previousWidgets = $this->modx->getCollection(modDashboardWidgetPlacement::class, ['dashboard' => $this->object->id, 'user' => 0]); - $previousWidgets = array_map(function($item){ + $previousWidgets = $this->modx->getCollection(modDashboardWidgetPlacement::class, [ + 'dashboard' => $this->object->id, + 'user' => 0 + ]); + $previousWidgets = array_map(function ($item) { return $item->widget; }, $previousWidgets); @@ -63,7 +97,6 @@ public function setWidgets() ]); foreach ($widgets as $data) { $newWidgets[] = $data['widget']; - $key = [ 'dashboard' => $this->object->get('id'), 'user' => 0, @@ -79,7 +112,6 @@ public function setWidgets() $widget->set('rank', $data['rank']); $widget->save(); } - $addedWidgets = array_values(array_diff($newWidgets, $previousWidgets)); $removedWidgets = array_values(array_diff($previousWidgets, $newWidgets)); @@ -88,9 +120,7 @@ public function setWidgets() $userDashboardsQuery->distinct(true); $userDashboardsQuery->select('user'); $userDashboardsQuery->prepare(); - $userDashboardsQuery->stmt->execute(); - $userDashboards = $userDashboardsQuery->stmt->fetchAll(\PDO::FETCH_COLUMN, 0); $userDashboards = array_map('intval', $userDashboards); @@ -108,11 +138,9 @@ public function setWidgets() } } } - if (!empty($removedWidgets)) { $this->modx->removeCollection(modDashboardWidgetPlacement::class, ['dashboard' => $this->object->id, 'widget:IN' => $removedWidgets]); } - $this->object->sortWidgets(); } } diff --git a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php index 63f300c9dbb..1525224c8ca 100644 --- a/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php +++ b/core/src/Revolution/Processors/System/Dashboard/Widget/GetList.php @@ -1,4 +1,5 @@ setDefaultProperties([ + 'query' => '', + 'exclude' => 'creator' + ]); + $canManage = $this->modx->hasPermission('dashboards'); + $this->canCreate = $canManage; + $this->canEdit = $canManage; + $this->canRemove = $canManage; + $this->extrasNamespaces = modNamespace::class::getExtrasNamespaces($this->modx); + + return $initialized; + } + /** * {@inheritDoc} * @param xPDOQuery $c @@ -70,8 +97,36 @@ public function prepareQueryAfterCount(xPDOQuery $c) */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectArray['cls'] = 'pupdate premove'; - return $objectArray; + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + $widgetData = $object->toArray(); + $widgetNamespace = $object->get('namespace'); + $isCoreWidget = strpos($widgetData['content'], '[[++manager_path]]') === 0; + $widgetData['isExtrasWidget'] = in_array($widgetNamespace, $this->extrasNamespaces); + $widgetData['isProtected'] = true; + + switch (true) { + case $widgetData['isExtrasWidget']: + $widgetData['creator'] = $this->modx->lexicon('package_extra'); + break; + case $isCoreWidget: + $widgetData['creator'] = 'MODX'; + break; + default: + $widgetData['creator'] = $this->modx->lexicon('user'); + $widgetData['isProtected'] = false; + } + $widgetData['creator'] = strtolower($widgetData['creator']); + + if ($isCoreWidget) { + unset($permissions['delete']); + } + $widgetData['permissions'] = $permissions; + + return $widgetData; } } diff --git a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php index 39ac2efb410..ceca4fadd26 100644 --- a/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Lexicon/GetList.php @@ -138,7 +138,7 @@ function parseArray($needle, array $haystack = []) ksort($entries); $entries = array_slice($entries, $this->getProperty('start'), $this->getProperty('limit'), true); - /* loop through */ + // Note that for Lexicons, the 'edit' permission correlates with the ability to revert a customized entry $list = []; foreach ($entries as $name => $value) { $editedOn = null; @@ -151,6 +151,9 @@ function parseArray($needle, array $haystack = []) 'createdon' => null, 'editedon' => null, 'overridden' => 0, + 'permissions' => [ + 'edit' => false + ] ]; /* if override in db, load */ if (array_key_exists($name, $dbEntries)) { @@ -159,6 +162,9 @@ function parseArray($needle, array $haystack = []) } $editedOn = $entryArray['editedon'] ?: $entryArray['createdon'] ; $entryArray['overridden'] = 1; + $entryArray['permissions'] = [ + 'edit' => true + ]; } $entryArray['editedon'] = $this->formatter->formatDateTime($editedOn); $list[] = $entryArray; diff --git a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php index dd2e7297c62..a1f0dbe2369 100644 --- a/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php +++ b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php @@ -14,6 +14,7 @@ use MODX\Revolution\modAccessNamespace; use MODX\Revolution\modNamespace; use MODX\Revolution\modUserGroup; +use MODX\Revolution\Transport\modTransportPackage; use MODX\Revolution\Processors\Model\GetListProcessor; use xPDO\Om\xPDOObject; use xPDO\Om\xPDOQuery; @@ -37,9 +38,16 @@ class GetList extends GetListProcessor public $languageTopics = ['namespace', 'workspace']; public $permission = 'namespaces'; + public $canCreate = false; + public $canEdit = false; + public $canRemove = false; + /** @param boolean $isGridFilter Indicates the target of this list data is a filter field */ protected $isGridFilter = false; + protected $coreNamespaces; + protected $extrasNamespaces = []; + /** * {@inheritDoc} * @return boolean @@ -47,10 +55,27 @@ class GetList extends GetListProcessor public function initialize() { $initialized = parent::initialize(); + $this->isGridFilter = $this->getProperty('isGridFilter', false); $this->setDefaultProperties([ - 'query' => '' + 'query' => '', + 'exclude' => 'creator' ]); - $this->isGridFilter = $this->getProperty('isGridFilter', false); + + /* + Normally these would access permission like this: + $this->canCreate = $this->modx->hasPermission('[object type]_save'); + Namespaces do not currently have changeable policy permissions, so + setting each to true; consider adding new permissions settings for + - namespace_save + - namespace_edit + - namespace_delete + */ + $this->canCreate = $this->modx->hasPermission('namespaces'); + $this->canEdit = $this->modx->hasPermission('namespaces'); + $this->canRemove = $this->modx->hasPermission('namespaces'); + $this->coreNamespaces = $this->classKey::getCoreNamespaces(); + $this->extrasNamespaces = modNamespace::class::getExtrasNamespaces($this->modx); + return $initialized; } @@ -170,16 +195,54 @@ public function prepareQueryAfterCount(xPDOQuery $c) /** * Prepare the Namespace for listing - * @param xPDOObject $object + * @param xPDOObject|modNamespace $object * @return array */ public function prepareRow(xPDOObject $object) { - $objectArray = $object->toArray(); - $objectArray['perm'] = []; - $objectArray['perm'][] = 'pedit'; - $objectArray['perm'][] = 'premove'; + /* + If policy permissions get added for namespaces, change to: + $permissions = [ + 'create' => $this->canCreate && $object->checkPolicy('save'), + 'duplicate' => $this->canCreate && $object->checkPolicy('copy'), + 'update' => $this->canEdit && $object->checkPolicy('save'), + 'delete' => $this->canRemove && $object->checkPolicy('remove') + ]; + */ + $permissions = [ + 'create' => $this->canCreate, + 'duplicate' => $this->canCreate, + 'update' => $this->canEdit, + 'delete' => $this->canRemove + ]; + + $namespaceData = $object->toArray(); + $namespaceName = $object->get('name'); + $isCoreNamespace = $object->isCoreNamespace($namespaceName); + + $namespaceData['reserved'] = ['name' => $this->coreNamespaces]; + $namespaceData['isProtected'] = true; + $namespaceData['isExtrasNamespace'] = in_array($namespaceName, $this->extrasNamespaces); + + switch (true) { + case $namespaceData['isExtrasNamespace']: + $namespaceData['creator'] = $this->modx->lexicon('package_extra'); + break; + case $isCoreNamespace: + $namespaceData['creator'] = 'modx'; + break; + default: + $namespaceData['creator'] = $this->modx->lexicon('user'); + $namespaceData['isProtected'] = false; + } + $namespaceData['creator'] = strtolower($namespaceData['creator']); + + // Core and Extras paths should only be editable via the installation process + if ($isCoreNamespace || $namespaceData['isExtrasNamespace']) { + $permissions = []; + } + $namespaceData['permissions'] = $permissions; - return $objectArray; + return $namespaceData; } } diff --git a/core/src/Revolution/Processors/Workspace/Packages/Version/GetList.php b/core/src/Revolution/Processors/Workspace/Packages/Version/GetList.php index b32b184dff6..fa97a6119f2 100644 --- a/core/src/Revolution/Processors/Workspace/Packages/Version/GetList.php +++ b/core/src/Revolution/Processors/Workspace/Packages/Version/GetList.php @@ -31,6 +31,7 @@ class GetList extends GetListProcessor public $languageTopics = ['workspace']; private modManagerDateFormatter $formatter; + public $canRemove = false; /** * @return bool @@ -45,6 +46,8 @@ public function initialize() 'workspace' => 1, 'signature' => false, ]); + $this->canRemove = $this->modx->hasPermission('packages'); + return parent::initialize(); } @@ -77,9 +80,10 @@ public function getData() */ public function prepareRow(xPDOObject $object) { - if ($object->get('installed') === '0000-00-00 00:00:00') { - $object->set('installed', null); - } + $permissions = [ + 'delete' => $this->canRemove + ]; + $installed = !in_array($object->get('installed'), [null, '0000-00-00 00:00:00']); $packageArray = $object->toArray(); $packageArray = $this->parseVersion($object, $packageArray); @@ -88,9 +92,14 @@ public function prepareRow(xPDOObject $object) $packageArray = $this->prepareMenu($object, $packageArray); /* setup description, using either metadata or readme */ - if ($object->get('installed') === null) { + if (!$installed) { $this->currentIndex--; } + if ($this->currentIndex === 0) { + $permissions['delete'] = false; + } + $packageArray['permissions'] = $permissions; + return $packageArray; } @@ -144,8 +153,8 @@ public function getMetaData(modTransportPackage $package, array $packageArray) foreach ($metadata as $row) { if (!empty($row['name']) && $row['name'] === 'description') { $packageArray['readme'] = str_replace( - [PHP_EOL, '

'], - ['', '
'], + [PHP_EOL, '

'], + ['', '
'], nl2br($row['text']) ); break; @@ -157,8 +166,8 @@ public function getMetaData(modTransportPackage $package, array $packageArray) if ($transport) { $packageArray['readme'] = $transport->getAttribute('readme'); $packageArray['readme'] = str_replace( - [PHP_EOL, '

'], - ['', '
'], + [PHP_EOL, '

'], + ['', '
'], nl2br($packageArray['readme']) ); } @@ -175,11 +184,11 @@ public function getMetaData(modTransportPackage $package, array $packageArray) */ public function prepareMenu(modTransportPackage $package, array $packageArray) { - $notInstalled = $package->get('installed') === null || $package->get('installed') === '0000-00-00 00:00:00'; - $packageArray['iconaction'] = $notInstalled ? 'icon-install' : 'icon-uninstall'; - $packageArray['textaction'] = $notInstalled ? $this->modx->lexicon('install') : $this->modx->lexicon('uninstall'); + $installed = !in_array($package->get('installed'), [null, '0000-00-00 00:00:00']); + $packageArray['iconaction'] = !$installed ? 'icon-install' : 'icon-uninstall'; + $packageArray['textaction'] = !$installed ? $this->modx->lexicon('install') : $this->modx->lexicon('uninstall'); - if ($this->currentIndex > 0 || !$package->get('installed')) { + if ($this->currentIndex > 0 || !$installed) { $packageArray['menu'] = []; $packageArray['menu'][] = [ 'text' => $this->modx->lexicon('package_version_remove'), diff --git a/core/src/Revolution/Sources/modMediaSource.php b/core/src/Revolution/Sources/modMediaSource.php index 1c743e3dff4..355b15ce397 100644 --- a/core/src/Revolution/Sources/modMediaSource.php +++ b/core/src/Revolution/Sources/modMediaSource.php @@ -70,6 +70,7 @@ abstract class modMediaSource extends modAccessibleSimpleObject implements modMe /** @var Filesystem */ protected $filesystem; + public const SOURCE_FILESYSTEM = 'Filesystem'; /** * Get the default MODX filesystem source @@ -441,7 +442,6 @@ public function getContainerList($path) foreach ($directories as $dir) { $ls[] = $dir; } - array_multisort($fileNames, SORT_ASC, SORT_STRING, $files); foreach ($files as $file) { $ls[] = $file; @@ -1255,7 +1255,6 @@ public function setVisibility($path, $visibility) return false; } - /** * @param string $object * @@ -1749,10 +1748,26 @@ public function clearCache(array $options = []) $c->select($this->xpdo->escape('key')); $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_media_sources_key', $options, 'media_sources'); - $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_media_sources_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options)); - $options[xPDO::OPT_CACHE_FORMAT] = (int)$this->getOption('cache_media_sources_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP)); - $options[xPDO::OPT_CACHE_ATTEMPTS] = (int)$this->getOption('cache_media_sources_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10)); - $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (int)$this->getOption('cache_media_sources_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000)); + $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption( + 'cache_media_sources_handler', + $options, + $this->getOption(xPDO::OPT_CACHE_HANDLER, $options) + ); + $options[xPDO::OPT_CACHE_FORMAT] = (int)$this->getOption( + 'cache_media_sources_format', + $options, + $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP) + ); + $options[xPDO::OPT_CACHE_ATTEMPTS] = (int)$this->getOption( + 'cache_media_sources_attempts', + $options, + $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10) + ); + $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (int)$this->getOption( + 'cache_media_sources_attempt_delay', + $options, + $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000) + ); if ($c->prepare() && $c->stmt->execute()) { while ($row = $c->stmt->fetch(PDO::FETCH_ASSOC)) { @@ -2062,7 +2077,7 @@ protected function getAllowedExtensionsArray($properties = []) /** * @param array $properties * - * @return array|mixed|string + * @return array */ protected function getSkipExtensionsArray($properties = []) { @@ -2073,7 +2088,7 @@ protected function getSkipExtensionsArray($properties = []) $skipExtensions = explode(',', $skipExtensions); } - return !empty($skipExtensions) ? explode(',', $skipExtensions) : []; + return $skipExtensions; } @@ -2450,4 +2465,26 @@ protected function isFileImage($file, $image_extensions = []) return false; } + + /** + * Returns a list of core Media Sources + * + * @return array + */ + public static function getCoreSources() + { + return [ + self::SOURCE_FILESYSTEM + ]; + } + + /** + * @param string $name The name of the Media Source + * + * @return bool + */ + public function isCoreSource($name) + { + return in_array($name, static::getCoreSources(), true); + } } diff --git a/core/src/Revolution/modAccessPolicyTemplate.php b/core/src/Revolution/modAccessPolicyTemplate.php index 426706093fb..d4fc1359663 100644 --- a/core/src/Revolution/modAccessPolicyTemplate.php +++ b/core/src/Revolution/modAccessPolicyTemplate.php @@ -3,30 +3,33 @@ namespace MODX\Revolution; use xPDO\Om\xPDOSimpleObject; +use xPDO\xPDO; /** * A collection of modAccessPermission records that are used as a Template for custom modAccessPolicy objects. Is * grouped into Access Policy Template Groups to provide targeted policy access implementations. * - * @property int $template_group The group that this template is a part of, used for targeting usage of applied Policies - * @property string $name The name of the Policy Template - * @property string $description A description of the Policy Template - * @property string $lexicon Optional. A lexicon that may be loaded to provide translations for all included Permissions + * @property int $template_group The group that this template is a part of, used for targeting usage of applied Policies + * @property string $name The name of the Policy Template + * @property string $description A description of the Policy Template + * @property string $lexicon Optional. A lexicon that may be loaded to provide translations for all included Permissions * * @property modAccessPermission[] $Permissions - * @property modAccessPolicy[] $Policies + * @property modAccessPolicy[] $Policies + * + * @property modX|xPDO $xpdo * * @package MODX\Revolution */ class modAccessPolicyTemplate extends xPDOSimpleObject { - const TEMPLATE_ADMINISTRATOR = 'AdministratorTemplate'; - const TEMPLATE_CONTEXT = 'ContextTemplate'; - const TEMPLATE_ELEMENT = 'ElementTemplate'; - const TEMPLATE_MEDIA_SOURCE = 'MediaSourceTemplate'; - const TEMPLATE_NAMESPACE = 'NamespaceTemplate'; - const TEMPLATE_OBJECT = 'ObjectTemplate'; - const TEMPLATE_RESOURCE = 'ResourceTemplate'; + public const TEMPLATE_ADMINISTRATOR = 'AdministratorTemplate'; + public const TEMPLATE_CONTEXT = 'ContextTemplate'; + public const TEMPLATE_ELEMENT = 'ElementTemplate'; + public const TEMPLATE_MEDIA_SOURCE = 'MediaSourceTemplate'; + public const TEMPLATE_NAMESPACE = 'NamespaceTemplate'; + public const TEMPLATE_OBJECT = 'ObjectTemplate'; + public const TEMPLATE_RESOURCE = 'ResourceTemplate'; /** * Returns list of core Policy Templates diff --git a/core/src/Revolution/modContentType.php b/core/src/Revolution/modContentType.php index 4109e80c416..43ddf01e988 100644 --- a/core/src/Revolution/modContentType.php +++ b/core/src/Revolution/modContentType.php @@ -18,6 +18,17 @@ */ class modContentType extends xPDOSimpleObject { + public const CORE_TYPES = [ + 'HTML', + 'XML', + 'Text', + 'CSS', + 'JavaScript', + 'RSS', + 'JSON', + 'PDF' + ]; + /** * Returns the first extension of this Content Type. * @@ -33,4 +44,24 @@ public function getExtension() return $extension; } + + /** + * Returns a list of core Dashboards + * + * @return array + */ + public static function getCoreContentTypes(): array + { + return self::CORE_TYPES; + } + + /** + * @param string $name The name of the Dashboard + * + * @return bool + */ + public function isCoreContentType($name): bool + { + return in_array($name, static::getCoreContentTypes(), true); + } } diff --git a/core/src/Revolution/modContext.php b/core/src/Revolution/modContext.php index a86ca5c44e9..27eceef2cb3 100644 --- a/core/src/Revolution/modContext.php +++ b/core/src/Revolution/modContext.php @@ -29,6 +29,9 @@ class modContext extends modAccessibleObject * @var array RESERVED_KEYS */ public const RESERVED_KEYS = ['mgr', 'web', 'root']; + public const CONTEXT_MANAGER = 'mgr'; + public const CONTEXT_DEFAULT = 'web'; + public const CONTEXT_DEFAULT_NAME = 'Website'; /** * An array of configuration options for this context @@ -125,14 +128,26 @@ public function prepare($regenerate = false, array $options = []) if ($this->config === null || $regenerate) { if ($this->xpdo->getCacheManager()) { $context = []; - if ($regenerate || !($context = $this->xpdo->cacheManager->get($this->getCacheKey(), [ - xPDO::OPT_CACHE_KEY => $this->xpdo->getOption('cache_context_settings_key', null, - 'context_settings'), - xPDO::OPT_CACHE_HANDLER => $this->xpdo->getOption('cache_context_settings_handler', null, - $this->xpdo->getOption(xPDO::OPT_CACHE_HANDLER, null, 'xPDO\Cache\xPDOFileCache')), - xPDO::OPT_CACHE_FORMAT => (integer)$this->xpdo->getOption('cache_context_settings_format', null, - $this->xpdo->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)), - ]))) { + if ( + $regenerate || !($context = $this->xpdo->cacheManager->get($this->getCacheKey(), [ + xPDO::OPT_CACHE_KEY => $this->xpdo->getOption( + 'cache_context_settings_key', + null, + 'context_settings' + ), + xPDO::OPT_CACHE_HANDLER => $this->xpdo->getOption( + 'cache_context_settings_handler', + null, + $this->xpdo->getOption(xPDO::OPT_CACHE_HANDLER, null, 'xPDO\Cache\xPDOFileCache') + ), + xPDO::OPT_CACHE_FORMAT => (int)$this->xpdo->getOption( + 'cache_context_settings_format', + null, + $this->xpdo->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP) + ) + ])) + ) { + /** @disregard P1013 Intelephense can not find this modCacheManager instance method, but it does exist and is available here */ $context = $this->xpdo->cacheManager->generateContext($this->get('key'), $options); } if (!empty($context)) { @@ -202,9 +217,9 @@ public function findPolicy($context = '') $enabled = true; $context = !empty($context) ? $context : $this->xpdo->context->get('key'); if (!is_object($this->xpdo->context) || $context === $this->xpdo->context->get('key')) { - $enabled = (boolean)$this->xpdo->getOption('access_context_enabled', null, true); + $enabled = (bool)$this->xpdo->getOption('access_context_enabled', null, true); } elseif ($this->xpdo->getContext($context)) { - $enabled = (boolean)$this->xpdo->contexts[$context]->getOption('access_context_enabled', true); + $enabled = (bool)$this->xpdo->contexts[$context]->getOption('access_context_enabled', true); } if ($enabled) { if (empty($this->_policies) || !isset($this->_policies[$context])) { @@ -284,7 +299,7 @@ public function makeUrl($id, $args = '', $scheme = -1, array $options = []) } if ($config['friendly_urls'] == 1) { - if ((integer)$id === (integer)$config['site_start']) { + if ((int)$id === (int)$config['site_start']) { $alias = ($scheme === '' || $scheme === -1) ? $config['base_url'] : ''; $found = true; } else { @@ -480,4 +495,27 @@ public function getResourceURI($id) return $uri; } + + /** + * Returns a list of core Contexts + * + * @return array + */ + public static function getCoreContexts() + { + return [ + self::CONTEXT_MANAGER, + self::CONTEXT_DEFAULT + ]; + } + + /** + * @param string $key The key of the Context + * + * @return bool + */ + public function isCoreContext($key) + { + return in_array($key, static::getCoreContexts(), true); + } } diff --git a/core/src/Revolution/modDashboard.php b/core/src/Revolution/modDashboard.php index 4949054f1fa..d0fbd7243e1 100644 --- a/core/src/Revolution/modDashboard.php +++ b/core/src/Revolution/modDashboard.php @@ -19,6 +19,8 @@ */ class modDashboard extends xPDOSimpleObject { + public const DASHBOARD_DEFAULT = 'Default'; + /** * Get the default MODX dashboard * @@ -73,7 +75,6 @@ public function remove(array $ancestors = []) return $removed; } - /** * Render the Dashboard * @@ -129,7 +130,6 @@ public function render(modManagerController $controller, $user = null) return implode("\n", $output); } - /** * @param int $user * @param bool $force @@ -168,7 +168,6 @@ public function sortWidgets($user = 0, $force = false) } } - /** * @param modUser $user */ @@ -188,4 +187,26 @@ protected function addUserWidgets(modUser $user) $new->save(); } } + + /** + * Returns a list of core Dashboards + * + * @return array + */ + public static function getCoreDashboards(): array + { + return [ + self::DASHBOARD_DEFAULT + ]; + } + + /** + * @param string $name The name of the Dashboard + * + * @return bool + */ + public function isCoreDashboard($name): bool + { + return in_array($name, static::getCoreDashboards(), true); + } } diff --git a/core/src/Revolution/modNamespace.php b/core/src/Revolution/modNamespace.php index 37209bd136c..d559ddd0a9a 100644 --- a/core/src/Revolution/modNamespace.php +++ b/core/src/Revolution/modNamespace.php @@ -2,6 +2,7 @@ namespace MODX\Revolution; +use MODX\Revolution\Transport\modTransportPackage; use PDO; use xPDO\Cache\xPDOCacheManager; use xPDO\Om\xPDOCriteria; @@ -27,6 +28,8 @@ */ class modNamespace extends modAccessibleObject { + public const NAMESPACE_CORE = 'core'; + public function save($cacheFlag = null) { $saved = parent::save(); @@ -54,11 +57,21 @@ public static function loadCache(modX $modx) } $cacheKey = 'namespaces'; $cache = $modx->cacheManager->get($cacheKey, [ - xPDO::OPT_CACHE_KEY => $modx->getOption('cache_namespaces_key', null, 'namespaces'), - xPDO::OPT_CACHE_HANDLER => $modx->getOption('cache_namespaces_handler', null, - $modx->getOption(xPDO::OPT_CACHE_HANDLER)), - xPDO::OPT_CACHE_FORMAT => (integer)$modx->getOption('cache_namespaces_format', null, - $modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)), + xPDO::OPT_CACHE_KEY => $modx->getOption( + 'cache_namespaces_key', + null, + 'namespaces' + ), + xPDO::OPT_CACHE_HANDLER => $modx->getOption( + 'cache_namespaces_handler', + null, + $modx->getOption(xPDO::OPT_CACHE_HANDLER) + ), + xPDO::OPT_CACHE_FORMAT => (int)$modx->getOption( + 'cache_namespaces_format', + null, + $modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP) + ) ]); if (empty($cache)) { $cache = $modx->cacheManager->generateNamespacesCache($cacheKey); @@ -71,11 +84,21 @@ public static function clearCache(modX $modx) { $cacheKey = 'namespaces'; $cleared = $modx->cacheManager->delete($cacheKey, [ - xPDO::OPT_CACHE_KEY => $modx->getOption('cache_namespaces_key', null, 'namespaces'), - xPDO::OPT_CACHE_HANDLER => $modx->getOption('cache_namespaces_handler', null, - $modx->getOption(xPDO::OPT_CACHE_HANDLER)), - xPDO::OPT_CACHE_FORMAT => (integer)$modx->getOption('cache_namespaces_format', null, - $modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)), + xPDO::OPT_CACHE_KEY => $modx->getOption( + 'cache_namespaces_key', + null, + 'namespaces' + ), + xPDO::OPT_CACHE_HANDLER => $modx->getOption( + 'cache_namespaces_handler', + null, + $modx->getOption(xPDO::OPT_CACHE_HANDLER) + ), + xPDO::OPT_CACHE_FORMAT => (int)$modx->getOption( + 'cache_namespaces_format', + null, + $modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP) + ) ]); return $cleared; @@ -157,4 +180,44 @@ public function findPolicy($context = '') return $policy; } + + public static function getExtrasNamespaces(modX $modx): array + { + $namespaceList = []; + + $c = $modx->newQuery(modTransportPackage::class); + $c->select([ + 'name' => 'DISTINCT SUBSTRING_INDEX(`signature`,"-",1)' + ]); + $namespaces = $modx->getIterator(modTransportPackage::class, $c); + $namespaces->rewind(); + if ($namespaces->valid()) { + foreach ($namespaces as $namespace) { + $namespaceList[] = $namespace->get('name'); + } + } + return $namespaceList; + } + + /** + * Returns a list of core Namespaces + * + * @return array + */ + public static function getCoreNamespaces() + { + return [ + self::NAMESPACE_CORE + ]; + } + + /** + * @param string $key The key of the Context + * + * @return bool + */ + public function isCoreNamespace($key) + { + return in_array($key, static::getCoreNamespaces(), true); + } } diff --git a/core/src/Revolution/modUserGroupRole.php b/core/src/Revolution/modUserGroupRole.php index b08c5a10317..81d139b5a82 100644 --- a/core/src/Revolution/modUserGroupRole.php +++ b/core/src/Revolution/modUserGroupRole.php @@ -11,13 +11,39 @@ * For example, an Administrator with authority of 1 will automatically inherit any Permissions assigned to a Member * role with authority 9999, since 1 is less than 9999. However, the reverse will not be true. * - * @property string $name The name of the Role + * @property string $name The name of the Role * @property string $description A user-provided description of this Role - * @property int $authority The authority of the role. Lower authority numbers have more power than higher ones, and - * lower numbers will inherit the Permissions of higher numbers. + * @property int $authority The authority of the role. Lower authority numbers + * have more power than higher ones, and lower numbers will inherit + * the Permissions of higher numbers. * * @package MODX\Revolution */ class modUserGroupRole extends xPDOSimpleObject { + public const ROLE_SUPERUSER = 'Super User'; + public const ROLE_MEMBER = 'Member'; + + /** + * Returns a list of core Roles + * + * @return array + */ + public static function getCoreRoles() + { + return [ + self::ROLE_SUPERUSER, + self::ROLE_MEMBER + ]; + } + + /** + * @param string $name The name of the Role + * + * @return bool + */ + public function isCoreRole($name) + { + return in_array($name, static::getCoreRoles(), true); + } } diff --git a/manager/assets/modext/util/utilities.js b/manager/assets/modext/util/utilities.js index 00371760026..30c818ecb77 100644 --- a/manager/assets/modext/util/utilities.js +++ b/manager/assets/modext/util/utilities.js @@ -8,78 +8,75 @@ Ext.namespace('MODx.util.Format'); * @param {Object} config An object of configuration properties * @xtype modx-json-reader */ -MODx.util.JSONReader = function(config) { - config = config || {}; - Ext.applyIf(config,{ - successProperty:'success' - ,totalProperty: 'total' - ,root: 'data' +MODx.util.JSONReader = function(config = {}) { + Ext.applyIf(config, { + successProperty: 'success', + totalProperty: 'total', + root: 'data' }); - MODx.util.JSONReader.superclass.constructor.call(this,config,['id','msg']); + MODx.util.JSONReader.superclass.constructor.call(this, config, ['id', 'msg']); }; -Ext.extend(MODx.util.JSONReader,Ext.data.JsonReader); -Ext.reg('modx-json-reader',MODx.util.JSONReader); +Ext.extend(MODx.util.JSONReader, Ext.data.JsonReader); +Ext.reg('modx-json-reader', MODx.util.JSONReader); /** * @class MODx.util.Progress */ MODx.util.Progress = { - id: 0 - ,time: function(v,id,msg) { + id: 0, + time: function(v, id, msg) { msg = msg || _('saving'); if (MODx.util.Progress.id === id && v < 11) { - Ext.MessageBox.updateProgress(v/10,msg); + Ext.MessageBox.updateProgress(v / 10, msg); } - } - ,reset: function() { - MODx.util.Progress.id = MODx.util.Progress.id + 1; + }, + reset: function() { + MODx.util.Progress.id += 1; } }; - MODx.util.UrlParams = { get() { - return this.parse(window.location.search) + return this.parse(window.location.search); }, set(data) { - const params = decodeURIComponent(new URLSearchParams(data).toString()) + const params = decodeURIComponent(new URLSearchParams(data).toString()); if (params.length) { - window.history.pushState(params, '', document.location.pathname + '?' + params); + window.history.pushState(params, '', `${document.location.pathname}?${params}`); } else { window.history.pushState('', '', document.location.pathname); } }, add(key, val) { - const params = this.get() - params[key] = val - this.set(params) + const params = this.get(); + params[key] = val; + this.set(params); }, remove(key) { - const params = this.get() - delete params[key] - this.set(params) + const params = this.get(); + delete params[key]; + this.set(params); }, clear() { - this.set({}) + this.set({}); }, parse(str) { - const params = new URLSearchParams(str) - return Object.fromEntries(params.entries()) + const params = new URLSearchParams(str); + return Object.fromEntries(params.entries()); } -} +}; /** Adds a lock mask to an element */ -MODx.LockMask = function(config) { - config = config || {}; - Ext.applyIf(config,{ - msg: _('locked') - ,msgCls: 'modx-lockmask' +MODx.LockMask = function(config = {}) { + Ext.applyIf(config, { + msg: _('locked'), + msgCls: 'modx-lockmask' }); - MODx.LockMask.superclass.constructor.call(this,config.el,config); + MODx.LockMask.superclass.constructor.call(this, config.el, config); }; -Ext.extend(MODx.LockMask,Ext.LoadMask,{ - locked: false - ,toggle: function() { +Ext.extend(MODx.LockMask, Ext.LoadMask, { + locked: false, + toggle: function() { if (this.locked) { this.hide(); this.locked = false; @@ -87,20 +84,26 @@ Ext.extend(MODx.LockMask,Ext.LoadMask,{ this.show(); this.locked = true; } + }, + lock: function() { + this.locked = true; + this.show(); + }, + unlock: function() { + this.locked = false; + this.hide(); } - ,lock: function() { this.locked = true; this.show(); } - ,unlock: function() { this.locked = false; this.hide(); } }); -Ext.reg('modx-lockmask',MODx.LockMask); +Ext.reg('modx-lockmask', MODx.LockMask); /** * Adds a new config parameter to allow preservation of trailing zeros in decimal numbers */ Ext.override(Ext.form.NumberField, { strictDecimalPrecision: false, - fixPrecision : function(value){ - var nan = isNaN(value); - if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){ + fixPrecision: function(value) { + const nan = Number.isNaN(value); + if (!this.allowDecimals || this.decimalPrecision === -1 || nan || !value) { return nan ? '' : value; } return this.allowDecimals && this.strictDecimalPrecision @@ -111,22 +114,22 @@ Ext.override(Ext.form.NumberField, { }); /** add clearDirty to basicform */ -Ext.override(Ext.form.BasicForm,{ - clearDirty : function(nodeToRecurse){ +Ext.override(Ext.form.BasicForm, { + clearDirty: function(nodeToRecurse) { nodeToRecurse = nodeToRecurse || this; - nodeToRecurse?.items?.each?.(function(f){ - if (!f.getValue) return; - - if(f.items){ - this.clearDirty(f); - } else if(f.originalValue != f.getValue()){ - f.originalValue = f.getValue(); + nodeToRecurse?.items?.each?.(function(field) { + if (!field.getValue) { + return; + } + if (field.items) { + this.clearDirty(field); + } else if (field.originalValue !== field.getValue()) { + field.originalValue = field.getValue(); } - },this); + }, this); } }); - /** * Static Textfield */ @@ -139,7 +142,21 @@ MODx.StaticTextField = Ext.extend(Ext.form.TextField, { MODx.StaticTextField.superclass.onRender.apply(this, arguments); } }); -Ext.reg('statictextfield',MODx.StaticTextField); +Ext.reg('statictextfield', MODx.StaticTextField); + +/** + * Static Textarea + */ +MODx.StaticTextArea = Ext.extend(Ext.form.TextArea, { + fieldClass: 'x-static-text-field', + + onRender: function() { + this.readOnly = true; + this.disabled = !this.initialConfig.submitValue; + MODx.StaticTextArea.superclass.onRender.apply(this, arguments); + } +}); +Ext.reg('statictextarea', MODx.StaticTextArea); /** * Static Boolean @@ -151,10 +168,10 @@ MODx.StaticBoolean = Ext.extend(Ext.form.TextField, { this.readOnly = true; this.disabled = !this.initialConfig.submitValue; MODx.StaticBoolean.superclass.onRender.apply(this, arguments); - this.on('change',this.onChange,this); - } + this.on('change', this.onChange, this); + }, - ,setValue: function(v) { + setValue: function(v) { if (v === 1) { this.addClass('green'); v = _('yes'); @@ -165,33 +182,38 @@ MODx.StaticBoolean = Ext.extend(Ext.form.TextField, { MODx.StaticBoolean.superclass.setValue.apply(this, arguments); } }); -Ext.reg('staticboolean',MODx.StaticBoolean); +Ext.reg('staticboolean', MODx.StaticBoolean); // This method strips not allowed html tags/attributes, html comments and php tags, // replaces javascript invocation in a href attribute and masks html event attributes // in an input string - assuming the result is safe to be displayed by a browser -MODx.util.safeHtml = function (input, allowedTags, allowedAttributes) { - var strip = function(input, allowedTags, allowedAttributes) { - return input.replace(tags, function ($0, $1) { - return allowedTags.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; - }).replace(attributes, function ($0, $1) { - return allowedAttributes.indexOf($1.toLowerCase() + ',') > -1 ? $0 : ''; - }); - }; - allowedTags = (((allowedTags || '
') + '') - .toLowerCase() - .match(/<[a-z][a-z0-9]*>/g) || []) - .join(''); // making sure the allowedTags arg is a string containing only tags in lowercase (
) - allowedAttributes = (((allowedAttributes || 'href,class') + '') - .toLowerCase() - .match(/[a-z\-,]*/g) || []) - .join('').concat(','); // making sure the allowedAttributes arg is a comma separated string containing only attributes in lowercase (a,b,c) - var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, +MODx.util.safeHtml = (input, allowedTags, allowedAttributes) => { + const + tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, attributes = /([a-z][a-z0-9]*)\s*=\s*".*?"/gi, eventAttributes = /on([a-z][a-z0-9]*\s*=)/gi, commentsAndPhpTags = /|<\?(?:php)?[\s\S]*?\?>/gi, hrefJavascript = /href(\s*?=\s*?(["'])javascript:.*?\2|\s*?=\s*?javascript:.*?(?![^> ]))/gi, - length; + strip = (string, allowedTagsDef, allowedAttrDef) => { + const + tagsReplacer = (match, group1) => (allowedTagsDef.indexOf(`<${group1.toLowerCase()}>`) > -1 ? match : ''), + attrReplacer = (match, group1) => (allowedAttrDef.indexOf(`${group1.toLowerCase()},`) > -1 ? match : '') + ; + return string.replace(tags, tagsReplacer).replace(attributes, attrReplacer); + } + ; + let length; + // making sure the allowedTags arg is a string containing only tags in lowercase () + allowedTags = ((`${allowedTags || '
'}`) + .toLowerCase() + .match(/<[a-z][a-z0-9]*>/g) || []) + .join(''); + // making sure the allowedAttributes arg is a comma separated string containing only attributes in lowercase (a,b,c) + allowedAttributes = ((`${allowedAttributes || 'href,class'}`) + .toLowerCase() + .match(/[a-z\-,]*/g) || []) + .join('') + .concat(','); input = input.replace(commentsAndPhpTags, '').replace(hrefJavascript, 'href="javascript:void(0)"'); do { length = input.length; @@ -200,226 +222,247 @@ MODx.util.safeHtml = function (input, allowedTags, allowedAttributes) { return input.replace(eventAttributes, 'on​$1'); }; -/**************************************************************************** - * Ext-specific overrides/extensions * - ****************************************************************************/ +// *** Ext-specific overrides/extensions *** /* add helper method to set checkbox boxLabel */ Ext.override(Ext.form.Checkbox, { - setBoxLabel: function(boxLabel){ + setBoxLabel: function(boxLabel) { this.boxLabel = boxLabel; - if(this.rendered){ + if (this.rendered) { this.wrap.child('.x-form-cb-label').update(boxLabel); } } }); -var FieldSetonRender = Ext.form.FieldSet.prototype.onRender; -Ext.override(Ext.form.FieldSet, { - onRender : function(ct, position){ - FieldSetonRender.call(this, ct, position); +const FieldSetOnRender = Ext.form.FieldSet.prototype.onRender; - if(this.checkboxToggle){ - var trigger = this.el.dom.getElementsByClassName(this.headerTextCls)[0]; - var elem = this; +Ext.override(Ext.form.FieldSet, { + onRender: function(ct, position) { + FieldSetOnRender.call(this, ct, position); + if (this.checkboxToggle) { + const + trigger = this.el.dom.getElementsByClassName(this.headerTextCls)[0], + elem = this + ; if (trigger) { - trigger.addEventListener('click', function(e) { + trigger.addEventListener('click', e => { elem.checkbox.dom.click(e); }, false); } } - }, + } }); - -Array.prototype.in_array = function(p_val) { - for(var i=0,l=this.length;i 0) { + if (fields.length > 0) { this.items.addAll(fields); - for(var f=0;f', - elbowMarkup = n.attributes.pseudoroot ? - '' : - '', - - buf = ['
  • ', - '',this.indentMarkup,"", + iconClass = a.iconCls ? ` ${a.iconCls}` : '', + iconMarkup = ``, + elbowMarkup = n.attributes.pseudoroot + ? '' + : '', + checkboxMarkup = hasCheckbox ? (`' : '>'}`) : '', + targetMarkup = a.hrefTarget ? `target="${a.hrefTarget}"` : '', + buf = [ + '
  • ', + `", + checkboxMarkup, + ``, + `${renderer(a)}`, + '', + '', '', - "
  • "].join(''); - - if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){ - this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf); - }else{ - this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf); + '' + ].join('') + ; + let nel; + // eslint-disable-next-line no-cond-assign + if (bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())) { + this.wrap = Ext.DomHelper.insertHtml('beforeBegin', nel, buf); + } else { + this.wrap = Ext.DomHelper.insertHtml('beforeEnd', targetNode, buf); } + /* eslint-disable prefer-destructuring */ this.elNode = this.wrap.childNodes[0]; this.ctNode = this.wrap.childNodes[1]; - var cs = this.elNode.childNodes; + const cs = this.elNode.childNodes; this.indentNode = cs[0]; this.ecNode = cs[1]; this.iconNode = cs[2]; - var index = 3; - if(cb){ + let index = 3; + if (hasCheckbox) { this.checkbox = cs[3]; - - this.checkbox.defaultChecked = this.checkbox.checked; index++; } this.anchor = cs[index]; this.textNode = cs[index].firstChild; - } + /* eslint-enable prefer-destructuring */ + }, /** * Renders the item text as a XSS-safe value. Can be overridden with a renderItemText method on the Tree. * @param text * @returns string */ - ,renderItemText: function(item) { - return Ext.util.Format.htmlEncode(item.text) - } - ,getChildIndent : function(){ - if(!this.childIndent){ - var buf = [], - p = this.node; - while(p){ - if((!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)) && !p.attributes.pseudoroot){ - if(!p.isLast()) { - buf.unshift(''); + renderItemText: function(item) { + return Ext.util.Format.htmlEncode(item.text); + }, + getChildIndent: function() { + if (!this.childIndent) { + const buf = []; + let p = this.node; + while (p) { + if ((!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)) && !p.attributes.pseudoroot) { + if (!p.isLast()) { + buf.unshift(``); } else { - buf.unshift(''); + buf.unshift(``); } } p = p.parentNode; } - this.childIndent = buf.join(""); + this.childIndent = buf.join(''); } return this.childIndent; } }); - /* allows for messages in JSON responses */ -Ext.override(Ext.form.Action.Submit,{ - handleResponse : function(response){ - var m = Ext.decode(response.responseText); /* shaun 7/11/07 */ +Ext.override(Ext.form.Action.Submit, { + handleResponse: function(response) { + const messageData = Ext.decode(response.responseText); if (this.form.errorReader) { - var rs = this.form.errorReader.read(response); - var errors = []; - if (rs.records) { - for(var i = 0, len = rs.records.length; i < len; i=i+1) { - var r = rs.records[i]; - errors[i] = r.data; + const + responseData = this.form.errorReader.read(response), + errors = [] + ; + if (responseData.records) { + for (let i = 0, len = responseData.records.length; i < len; i++) { + const record = responseData.records[i]; + errors[i] = record.data; } } - if (errors.length < 1) { errors = null; } return { - success : rs.success - ,message : m.message /* shaun 7/11/07 */ - ,object : m.object /* shaun 7/18/07 */ - ,errors : errors + success: responseData.success, + message: messageData.message, + object: messageData.object, + errors: errors.length < 1 ? null : errors }; } return Ext.decode(response.responseText); @@ -428,40 +471,43 @@ Ext.override(Ext.form.Action.Submit,{ /* QTips to form fields */ Ext.form.Field.prototype.afterRender = Ext.form.Field.prototype.afterRender.createSequence(function() { - if (this.description && parseInt(MODx.config.manager_tooltip_enable)) { + if (this.description && parseInt(MODx.config.manager_tooltip_enable, 10)) { Ext.QuickTips.register({ - target: this.getEl() - ,text: this.description - ,enabled: true - ,dismissDelay: MODx.config.manager_tooltip_delay + target: this.getEl(), + text: this.description, + enabled: true, + dismissDelay: MODx.config.manager_tooltip_delay }); - var label = Ext.form.Field.findLabel(this); - if(label){ + const label = Ext.form.Field.findLabel(this); + if (label) { Ext.QuickTips.register({ - target: label - ,text: this.description - ,enabled: true - ,dismissDelay: MODx.config.manager_tooltip_delay + target: label, + text: this.description, + enabled: true, + dismissDelay: MODx.config.manager_tooltip_delay }); } } }); -Ext.applyIf(Ext.form.Field,{ + +Ext.applyIf(Ext.form.Field, { findLabel: function(field) { - var wrapDiv = null; - var label = null; + let + wrapDiv = null, + label = null + ; wrapDiv = field.getEl().up('div.x-form-element'); - if(wrapDiv){ + if (wrapDiv) { label = wrapDiv.child('label'); } - if(label){ + if (label) { return label; } wrapDiv = field.getEl().up('div.x-form-item'); - if(wrapDiv) { + if (wrapDiv) { label = wrapDiv.child('label'); } - if(label){ + if (label) { return label; } } @@ -469,23 +515,29 @@ Ext.applyIf(Ext.form.Field,{ MODx.util.Format = { dateFromTimestamp: function(timestamp, date, time, defaultValue) { - if (date === undefined) date = true; - if (time === undefined) time = true; - if (defaultValue === undefined) defaultValue = ''; - - timestamp = parseInt(timestamp); - if (!(timestamp > 0)) return defaultValue; - + if (date === undefined) { + date = true; + } + if (time === undefined) { + time = true; + } + if (defaultValue === undefined) { + defaultValue = ''; + } + timestamp = parseInt(timestamp, 10); + if (!(timestamp > 0)) { + return defaultValue; + } if (timestamp.toString().length === 10) { timestamp *= 1000; } - var format = []; + let format = []; - if (date === true) format.push(MODx.config.manager_date_format); - if (time === true) format.push(MODx.config.manager_time_format); + if (date === true) { format.push(MODx.config.manager_date_format); } + if (time === true) { format.push(MODx.config.manager_time_format); } - if (format.length === 0) return defaultValue; + if (format.length === 0) { return defaultValue; } format = format.join(' '); @@ -551,6 +603,13 @@ MODx.util.Format = { .replace(new RegExp(`[${separator}]{2,}`, 'g'), separator) ; return padListItems ? formattedList.replaceAll(separator, `${separator} `) : formattedList ; + }, + + firstToUpperCase: function(string) { + return typeof string === 'string' && string.length > 0 + ? string.charAt(0).toUpperCase() + string.substring(1) + : string + ; } }; @@ -561,21 +620,23 @@ MODx.util.getHeaderBreadCrumbs = function(header, trail) { xtype: 'modx-header' }; } - - if (trail === undefined) trail = []; - if (!Array.isArray(trail)) trail = [trail]; - + if (trail === undefined) { + trail = []; + } + if (!Array.isArray(trail)) { + trail = [trail]; + } return { xtype: 'modx-breadcrumbs-panel', id: 'modx-header-breadcrumbs', cls: 'modx-header-breadcrumbs', desc: '', - bdMarkup: '
    • ' + - '{text}' + - '{text}' + - '
    ', + bdMarkup: '
    • ' + + '{text}' + + '{text}' + + '
    ', init: function() { - this.tpl = new Ext.XTemplate(this.bdMarkup, {compiled: true}); + this.tpl = new Ext.XTemplate(this.bdMarkup, { compiled: true }); }, trail: trail, listeners: { @@ -583,27 +644,26 @@ MODx.util.getHeaderBreadCrumbs = function(header, trail) { this.renderTrail(); } }, - renderTrail: function () { - this.tpl.overwrite(this.body.dom.lastElementChild, {trail: this.trail}); + renderTrail: function() { + this.tpl.overwrite(this.body.dom.lastElementChild, { trail: this.trail }); }, + // eslint-disable-next-line no-shadow updateTrail: function(trail, replace) { - if (replace === undefined) replace = false; - + if (replace === undefined) { + replace = false; + } if (replace === true) { this.trail = (Array.isArray(trail)) ? trail : [trail]; this.renderTrail(); return true; } - if (Array.isArray(trail)) { - for (var i = 0; i < trail.length; i++) { + for (let i = 0; i < trail.length; i++) { this.trail.push(trail[i]); } - this.renderTrail(); return true; } - this.trail.push(trail); this.renderTrail(); return true; @@ -737,71 +797,237 @@ MODx.util.tree = { }; Ext.util.Format.trimCommas = function(s) { - s = s.replace(',,',','); - var len = s.length; - if (s.substr(len-1,1) == ",") { - s = s.substring(0,len-1); + s = s.replace(',,', ','); + const len = s.length; + if (s.substr(len - 1, 1) === ',') { + s = s.substring(0, len - 1); } - if (s.substr(0,1) == ",") { + if (s.substr(0, 1) === ',') { s = s.substring(1); } - if (s == ',') { s = ''; } + if (s === ',') { + s = ''; + } return s; }; /* rowactions plugin */ -Ext.ns('Ext.ux.grid');if('function'!==typeof RegExp.escape){RegExp.escape=function(s){if('string'!==typeof s){return s}return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g,'\\$1')}}Ext.ux.grid.RowActions=function(a){Ext.apply(this,a);this.addEvents('beforeaction','action','beforegroupaction','groupaction');Ext.ux.grid.RowActions.superclass.constructor.call(this)};Ext.extend(Ext.ux.grid.RowActions,Ext.util.Observable,{actionEvent:'click',autoWidth:true,dataIndex:'',editable:false,header:'',isColumn:true,keepSelection:false,menuDisabled:true,sortable:false,tplGroup:''+'
    ux-action-right '+'{cls}" style="{style}" qtip="{qtip}">{text}
    '+'
    ',tplRow:'
    '+''+'
    '+'ux-row-action-text" style="{hide}{style}" qtip="{qtip}">'+'{text}
    '+'
    '+'
    ',hideMode:'visibility',widthIntercept:4,widthSlope:21,init:function(g){this.grid=g;this.id=this.id||Ext.id();var h=g.getColumnModel().lookup;delete(h[undefined]);h[this.id]=this;if(!this.tpl){this.tpl=this.processActions(this.actions)}if(this.autoWidth){this.width=this.widthSlope*this.actions.length+this.widthIntercept;this.fixed=true}var i=g.getView();var j={scope:this};j[this.actionEvent]=this.onClick;g.afterRender=g.afterRender.createSequence(function(){i.mainBody.on(j);g.on('destroy',this.purgeListeners,this)},this);if(!this.renderer){this.renderer=function(a,b,c,d,e,f){b.css+=(b.css?' ':'')+'ux-row-action-cell';return this.tpl.apply(this.getData(a,b,c,d,e,f))}.createDelegate(this)}if(i.groupTextTpl&&this.groupActions){i.interceptMouse=i.interceptMouse.createInterceptor(function(e){if(e.getTarget('.ux-grow-action-item')){return false}});i.groupTextTpl='
    '+i.groupTextTpl+'
    '+this.processActions(this.groupActions,this.tplGroup).apply()}if(true===this.keepSelection){g.processEvent=g.processEvent.createInterceptor(function(a,e){if('mousedown'===a){return!this.getAction(e)}},this)}},getData:function(a,b,c,d,e,f){return c.data||{}},processActions:function(b,c){var d=[];Ext.each(b,function(a,i){if(a.iconCls&&'function'===typeof(a.callback||a.cb)){this.callbacks=this.callbacks||{};this.callbacks[a.iconCls]=a.callback||a.cb}var o={cls:a.iconIndex?'{'+a.iconIndex+'}':(a.iconCls?a.iconCls:''),qtip:a.qtipIndex?'{'+a.qtipIndex+'}':(a.tooltip||a.qtip?a.tooltip||a.qtip:''),text:a.textIndex?'{'+a.textIndex+'}':(a.text?a.text:''),hide:a.hideIndex?''+('display'===this.hideMode?'display:none':'visibility:hidden')+';':(a.hide?('display'===this.hideMode?'display:none':'visibility:hidden;'):''),align:a.align||'right',style:a.style?a.style:''};d.push(o)},this);var e=new Ext.XTemplate(c||this.tplRow);return new Ext.XTemplate(e.apply({actions:d}))},getAction:function(e){var a=false;var t=e.getTarget('.ux-row-action-item');if(t){a=t.className.replace(/ux-row-action-item /,'');if(a){a=a.replace(/ ux-row-action-text/,'');a=a.trim()}}return a},onClick:function(e,a){var b=this.grid.getView();var c=e.getTarget('.x-grid3-row');var d=b.findCellIndex(a.parentNode.parentNode);var f=this.getAction(e);if(false!==c&&false!==d&&false!==f){var g=this.grid.store.getAt(c.rowIndex);if(this.callbacks&&'function'===typeof this.callbacks[f]){this.callbacks[f](this.grid,g,f,c.rowIndex,d)}if(true!==this.eventsSuspended&&false===this.fireEvent('beforeaction',this.grid,g,f,c.rowIndex,d)){return}else if(true!==this.eventsSuspended){this.fireEvent('action',this.grid,g,f,c.rowIndex,d)}}t=e.getTarget('.ux-grow-action-item');if(t){var h=b.findGroup(a);var i=h?h.id.replace(/ext-gen[0-9]+-gp-/,''):null;var j;if(i){var k=new RegExp(RegExp.escape(i));j=this.grid.store.queryBy(function(r){return r._groupId.match(k)});j=j?j.items:[]}f=t.className.replace(/ux-grow-action-item (ux-action-right )*/,'');if('function'===typeof this.callbacks[f]){this.callbacks[f](this.grid,j,f,i)}if(true!==this.eventsSuspended&&false===this.fireEvent('beforegroupaction',this.grid,j,f,i)){return false}this.fireEvent('groupaction',this.grid,j,f,i)}}});Ext.reg('rowactions',Ext.ux.grid.RowActions); +Ext.ns('Ext.ux.grid'); +if (typeof RegExp.escape !== 'function') { + RegExp.escape = function(s) { + if (typeof s !== 'string') { + return s; + } + return s.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, '\\$1'); + }; +} +Ext.ux.grid.RowActions = function(a) { + Ext.apply(this, a); + this.addEvents('beforeaction', 'action', 'beforegroupaction', 'groupaction'); + Ext.ux.grid.RowActions.superclass.constructor.call(this); +}; +Ext.extend(Ext.ux.grid.RowActions, Ext.util.Observable, { + actionEvent: 'click', + autoWidth: true, + dataIndex: '', + editable: false, + header: '', + isColumn: true, + keepSelection: false, + menuDisabled: true, + sortable: false, + tplGroup: '
    ux-action-right {cls}" style="{style}" qtip="{qtip}">{text}
    ', + tplRow: '
    ux-row-action-text" style="{hide}{style}" qtip="{qtip}">{text}
    ', + hideMode: 'visibility', + widthIntercept: 4, + widthSlope: 21, + init: function(g) { + this.grid = g; + this.id = this.id || Ext.id(); + const h = g.getColumnModel().lookup; + delete h[undefined]; + h[this.id] = this; + if (!this.tpl) { + this.tpl = this.processActions(this.actions); + } + if (this.autoWidth) { + this.width = this.widthSlope * this.actions.length + this.widthIntercept; + this.fixed = true; + } + const + i = g.getView(), + j = { scope: this } + ; + j[this.actionEvent] = this.onClick; + g.afterRender = g.afterRender.createSequence(function() { + i.mainBody.on(j); + g.on('destroy', this.purgeListeners, this); + }, this); + if (!this.renderer) { + this.renderer = function(a, b, c, d, e, f) { + b.css += `${b.css ? ' ' : ''}ux-row-action-cell`; + return this.tpl.apply(this.getData(a, b, c, d, e, f)); + }.createDelegate(this); + } + if (i.groupTextTpl && this.groupActions) { + i.interceptMouse = i.interceptMouse.createInterceptor(function(e) { + if (e.getTarget('.ux-grow-action-item')) { + return false; + } + }); + i.groupTextTpl = `
    ${i.groupTextTpl}
    ${this.processActions(this.groupActions, this.tplGroup).apply()}`; + } + if (this.keepSelection === true) { + g.processEvent = g.processEvent.createInterceptor(function(a, e) { + if (a === 'mousedown') { + return !this.getAction(e); + } + }, this); + } + }, + getData: function(a, b, c, d, e, f) { + return c.data || {}; + }, + processActions: function(b, c) { + const d = []; + Ext.each( + b, + function(a, i) { + if (a.iconCls && typeof (a.callback || a.cb) === 'function') { + this.callbacks = this.callbacks || {}; + this.callbacks[a.iconCls] = a.callback || a.cb; + } + const o = { + /* eslint-disable no-nested-ternary */ + cls: a.iconIndex ? `{${a.iconIndex}}` : a.iconCls ? a.iconCls : '', + qtip: a.qtipIndex ? `{${a.qtipIndex}}` : a.tooltip || a.qtip ? a.tooltip || a.qtip : '', + text: a.textIndex ? `{${a.textIndex}}` : a.text ? a.text : '', + hide: a.hideIndex + ? `${this.hideMode === 'display' ? 'display:none' : 'visibility:hidden'};` + : a.hide ? (this.hideMode === 'display' ? 'display:none' : 'visibility:hidden;') : '', + align: a.align || 'right', + style: a.style ? a.style : '' + }; + /* eslint-enable no-nested-ternary */ + d.push(o); + }, + this + ); + const e = new Ext.XTemplate(c || this.tplRow); + return new Ext.XTemplate(e.apply({ actions: d })); + }, + getAction: function(e) { + let a = false; + const t = e.getTarget('.ux-row-action-item'); + if (t) { + a = t.className.replace(/ux-row-action-item /, ''); + if (a) { + a = a.replace(/ ux-row-action-text/, ''); + a = a.trim(); + } + } + return a; + }, + onClick: function(e, a) { + const + b = this.grid.getView(), + c = e.getTarget('.x-grid3-row'), + d = b.findCellIndex(a.parentNode.parentNode) + ; + let f = this.getAction(e); + if (c !== false && d !== false && f !== false) { + const g = this.grid.store.getAt(c.rowIndex); + if (this.callbacks && typeof this.callbacks[f] === 'function') { + this.callbacks[f](this.grid, g, f, c.rowIndex, d); + } + if (this.eventsSuspended !== true && this.fireEvent('beforeaction', this.grid, g, f, c.rowIndex, d) === false) { + return; + } + if (this.eventsSuspended !== true) { + this.fireEvent('action', this.grid, g, f, c.rowIndex, d); + } + } + const t = e.getTarget('.ux-grow-action-item'); + if (t) { + const + h = b.findGroup(a), + i = h ? h.id.replace(/ext-gen[0-9]+-gp-/, '') : null + ; + let j; + if (i) { + const k = new RegExp(RegExp.escape(i)); + j = this.grid.store.queryBy(function(r) { + return r._groupId.match(k); + }); + j = j ? j.items : []; + } + f = t.className.replace(/ux-grow-action-item (ux-action-right )*/, ''); + if (typeof this.callbacks[f] === 'function') { + this.callbacks[f](this.grid, j, f, i); + } + if (this.eventsSuspended !== true && this.fireEvent('beforegroupaction', this.grid, j, f, i) === false) { + return false; + } + this.fireEvent('groupaction', this.grid, j, f, i); + } + } +}); +Ext.reg('rowactions', Ext.ux.grid.RowActions); -/* +/** * Ext JS Library 0.30 * Copyright(c) 2006-2009, Ext JS, LLC. * licensing@extjs.com * - * http://extjs.com/license + * @deprecated No use found in the core as of 3.x; remove? */ Ext.SwitchButton = Ext.extend(Ext.Component, { - initComponent : function(){ + initComponent: function() { Ext.SwitchButton.superclass.initComponent.call(this); - var mc = new Ext.util.MixedCollection(); + const mc = new Ext.util.MixedCollection(); mc.addAll(this.items); this.items = mc; this.addEvents('change'); - if(this.handler){ + if (this.handler) { this.on('change', this.handler, this.scope || this); } }, - onRender : function(ct, position){ - var el = document.createElement('table'); + onRender: function(ct, position) { + const el = document.createElement('table'); el.cellSpacing = 0; el.className = 'x-rbtn'; el.id = this.id; - var row = document.createElement('tr'); + const row = document.createElement('tr'); el.appendChild(document.createElement('tbody')).appendChild(row); - var count = this.items.length; - var last = count - 1; + const + count = this.items.length, + last = count - 1 + ; this.activeItem = this.items.get(this.activeItem); - for(var i = 0; i < count; i++){ - var item = this.items.itemAt(i); - - var cell = row.appendChild(document.createElement('td')); - cell.id = this.id + '-rbi-' + i; + for (let i = 0; i < count; i++) { + const + item = this.items.itemAt(i), + cell = row.appendChild(document.createElement('td')), + nextCls = i === last ? 'x-rbtn-last' : 'x-rbtn-item' + ; + cell.id = `${this.id}-rbi-${i}`; - var cls = i == 0 ? 'x-rbtn-first' : (i == last ? 'x-rbtn-last' : 'x-rbtn-item'); + let cls = i === 0 ? 'x-rbtn-first' : nextCls; item.baseCls = cls; - if(this.activeItem == item){ + if (this.activeItem === item) { cls += '-active'; } cell.className = cls; - var button = document.createElement('button'); + const button = document.createElement('button'); button.innerHTML = ' '; button.className = item.iconCls; button.qtip = item.tooltip; @@ -816,21 +1042,21 @@ Ext.SwitchButton = Ext.extend(Ext.Component, { this.el.on('click', this.onClick, this); }, - getActiveItem : function(){ + getActiveItem: function() { return this.activeItem; }, - setActiveItem : function(item){ - if(typeof item != 'object' && item !== null){ + setActiveItem: function(item) { + if (typeof item != 'object' && item !== null) { item = this.items.get(item); } - var current = this.getActiveItem(); - if(item != current){ - if(current){ - Ext.fly(current.cell).removeClass(current.baseCls + '-active'); + const current = this.getActiveItem(); + if (item !== current) { + if (current) { + Ext.fly(current.cell).removeClass(`${current.baseCls}-active`); } - if(item) { - Ext.fly(item.cell).addClass(item.baseCls + '-active'); + if (item) { + Ext.fly(item.cell).addClass(`${item.baseCls}-active`); } this.activeItem = item; this.fireEvent('change', this, item); @@ -838,14 +1064,13 @@ Ext.SwitchButton = Ext.extend(Ext.Component, { return item; }, - onClick : function(e){ - var target = e.getTarget('td', 2); - if(!this.disabled && target){ + onClick: function(e) { + const target = e.getTarget('td', 2); + if (!this.disabled && target) { this.setActiveItem(parseInt(target.id.split('-rbi-')[1], 10)); } } }); - Ext.reg('switch', Ext.SwitchButton); Ext.onReady(function() { @@ -853,12 +1078,217 @@ Ext.onReady(function() { MODx.form.Handler = MODx.load({ xtype: 'modx-form-handler' }); MODx.msg = MODx.load({ xtype: 'modx-msg' }); }); + /* always-submit checkboxes */ -Ext.form.XCheckbox=Ext.extend(Ext.form.Checkbox,{submitOffValue:0,submitOnValue:1,onRender:function(){this.inputValue=this.submitOnValue;Ext.form.XCheckbox.superclass.onRender.apply(this,arguments);this.hiddenField=this.wrap.insertFirst({tag:'input',type:'hidden'});if(this.tooltip){this.imageEl.set({qtip:this.tooltip})}this.updateHidden()},setValue:function(v){v=this.convertValue(v);this.updateHidden(v);Ext.form.XCheckbox.superclass.setValue.apply(this,arguments)},updateHidden:function(v){v=undefined!==v?v:this.checked;v=this.convertValue(v);if(this.hiddenField){this.hiddenField.dom.value=v?this.submitOnValue:this.submitOffValue;this.hiddenField.dom.name=v?'':this.el.dom.name}},convertValue:function(v){return(v===true||v==='true'||v===this.submitOnValue||String(v).toLowerCase()==='on')}});Ext.reg('xcheckbox',Ext.form.XCheckbox); +Ext.form.XCheckbox = Ext.extend(Ext.form.Checkbox, { + submitOffValue: 0, + submitOnValue: 1, + onRender: function() { + this.inputValue = this.submitOnValue; + Ext.form.XCheckbox.superclass.onRender.apply(this, arguments); + this.hiddenField = this.wrap.insertFirst({ + tag: 'input', + type: 'hidden' + }); + if (this.tooltip) { + this.imageEl.set({ qtip: this.tooltip }); + } + this.updateHidden(); + }, + setValue: function(v) { + v = this.convertValue(v); + this.updateHidden(v); + Ext.form.XCheckbox.superclass.setValue.apply(this, arguments); + }, + updateHidden: function(v) { + v = undefined !== v ? v : this.checked; + v = this.convertValue(v); + if (this.hiddenField) { + this.hiddenField.dom.value = v ? this.submitOnValue : this.submitOffValue; + this.hiddenField.dom.name = v ? '' : this.el.dom.name; + } + }, + convertValue: function(v) { + return v === true || v === 'true' || v === this.submitOnValue || String(v).toLowerCase() === 'on'; + } +}); +Ext.reg('xcheckbox', Ext.form.XCheckbox); /* drag/drop grids */ -Ext.namespace('Ext.ux.dd');Ext.ux.dd.GridDragDropRowOrder=Ext.extend(Ext.util.Observable,{copy:false,scrollable:false,constructor:function(config){if(config)Ext.apply(this,config);this.addEvents({beforerowmove:true,afterrowmove:true,beforerowcopy:true,afterrowcopy:true});Ext.ux.dd.GridDragDropRowOrder.superclass.constructor.call(this)},init:function(grid){this.grid=grid;grid.enableDragDrop=true;grid.on({render:{fn:this.onGridRender,scope:this,single:true}})},onGridRender:function(grid){var self=this;this.target=new Ext.dd.DropTarget(grid.getEl(),{ddGroup:grid.ddGroup||'GridDD',grid:grid,gridDropTarget:this,notifyDrop:function(dd,e,data){if(this.currentRowEl){this.currentRowEl.removeClass('grid-row-insert-below');this.currentRowEl.removeClass('grid-row-insert-above')}var t=Ext.lib.Event.getTarget(e);var rindex=this.grid.getView().findRowIndex(t);if(rindex===false||rindex==data.rowIndex){return false}if(this.gridDropTarget.fireEvent(self.copy?'beforerowcopy':'beforerowmove',this.gridDropTarget,data.rowIndex,rindex,data.selections,123)===false){return false}var ds=this.grid.getStore();var selections=new Array();var keys=ds.data.keys;for(var key in keys){for(var i=0;idata.rowIndex&&this.rowPosition<0){rindex--}if(rindex0){rindex++}if(rindex>data.rowIndex&&data.selections.length>1){rindex=rindex-(data.selections.length-1)}if(rindex==data.rowIndex){return false}if(!self.copy){for(var i=0;i=0;i--){var insertIndex=rindex;ds.insert(insertIndex,selections[i])}var sm=this.grid.getSelectionModel();if(sm){sm.selectRecords(data.selections)}this.gridDropTarget.fireEvent(self.copy?'afterrowcopy':'afterrowmove',this.gridDropTarget,data.rowIndex,rindex,data.selections);return true},notifyOver:function(dd,e,data){var t=Ext.lib.Event.getTarget(e);var rindex=this.grid.getView().findRowIndex(t);var ds=this.grid.getStore();var keys=ds.data.keys;for(var key in keys){for(var i=0;i0){this.currentRowEl=new Ext.Element(currentRow);this.currentRowEl.addClass('grid-row-insert-below')}else{if(rindex-1>=0){var previousRow=this.grid.getView().getRow(rindex-1);this.currentRowEl=new Ext.Element(previousRow);this.currentRowEl.addClass('grid-row-insert-below')}else{this.currentRowEl.addClass('grid-row-insert-above')}}}catch(err){console.warn(err);rindex=false}return(rindex===false)?this.dropNotAllowed:this.dropAllowed},notifyOut:function(dd,e,data){if(this.currentRowEl){this.currentRowEl.removeClass('grid-row-insert-above');this.currentRowEl.removeClass('grid-row-insert-below')}}});if(this.targetCfg){Ext.apply(this.target,this.targetCfg)}if(this.scrollable){Ext.dd.ScrollManager.register(grid.getView().getEditorParent());grid.on({beforedestroy:this.onBeforeDestroy,scope:this,single:true})}},getTarget:function(){return this.target},getGrid:function(){return this.grid},getCopy:function(){return this.copy?true:false},setCopy:function(b){this.copy=b?true:false},onBeforeDestroy:function(grid){Ext.dd.ScrollManager.unregister(grid.getView().getEditorParent())}}); +Ext.namespace('Ext.ux.dd'); +Ext.ux.dd.GridDragDropRowOrder = Ext.extend(Ext.util.Observable, { + copy: false, + scrollable: false, + constructor: function(config) { + if (config) { + Ext.apply(this, config); + } + this.addEvents({ + beforerowmove: true, + afterrowmove: true, + beforerowcopy: true, + afterrowcopy: true + }); + Ext.ux.dd.GridDragDropRowOrder.superclass.constructor.call(this); + }, + init: function(grid) { + this.grid = grid; + grid.enableDragDrop = true; + grid.on({ + render: { + fn: this.onGridRender, + scope: this, + single: true + } + }); + }, + onGridRender: function(grid) { + const self = this; + this.target = new Ext.dd.DropTarget(grid.getEl(), { + ddGroup: grid.ddGroup || 'GridDD', + grid: grid, + gridDropTarget: this, + notifyDrop: function(dd, e, data) { + if (this.currentRowEl) { + this.currentRowEl.removeClass('grid-row-insert-below'); + this.currentRowEl.removeClass('grid-row-insert-above'); + } + const target = Ext.lib.Event.getTarget(e); + let rindex = this.grid.getView().findRowIndex(target); + if (rindex === false || rindex === data.rowIndex) { + return false; + } + if (this.gridDropTarget.fireEvent(self.copy ? 'beforerowcopy' : 'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections, 123) === false) { + return false; + } + const + ds = this.grid.getStore(), + selections = [], + { keys } = ds.data + ; + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const key in keys) { + for (let i = 0; i < data.selections.length; i++) { + if (keys[key] === data.selections[i].id) { + if (rindex === key) { + return false; + } + selections.push(data.selections[i]); + } + } + } + if (rindex > data.rowIndex && this.rowPosition < 0) { + rindex--; + } + if (rindex < data.rowIndex && this.rowPosition > 0) { + rindex++; + } + if (rindex > data.rowIndex && data.selections.length > 1) { + rindex -= (data.selections.length - 1); + } + if (rindex === data.rowIndex) { + return false; + } + if (!self.copy) { + for (let i = 0; i < data.selections.length; i++) { + ds.remove(ds.getById(data.selections[i].id)); + } + } + for (let i = selections.length - 1; i >= 0; i--) { + const insertIndex = rindex; + ds.insert(insertIndex, selections[i]); + } + const sm = this.grid.getSelectionModel(); + if (sm) { + sm.selectRecords(data.selections); + } + this.gridDropTarget.fireEvent(self.copy ? 'afterrowcopy' : 'afterrowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections); + return true; + }, + notifyOver: function(dd, e, data) { + const + target = Ext.lib.Event.getTarget(e), + ds = this.grid.getStore(), + { keys } = ds.data + ; + let rindex = this.grid.getView().findRowIndex(target); + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const key in keys) { + for (let i = 0; i < data.selections.length; i++) { + if (keys[key] === data.selections[i].id) { + if (rindex === key) { + if (this.currentRowEl) { + this.currentRowEl.removeClass('grid-row-insert-below'); + this.currentRowEl.removeClass('grid-row-insert-above'); + } + return this.dropNotAllowed; + } + } + } + } + if (rindex < 0 || rindex === false) { + this.currentRowEl.removeClass('grid-row-insert-above'); + return this.dropNotAllowed; + } + try { + const + currentRow = this.grid.getView().getRow(rindex), + resolvedRow = new Ext.Element(currentRow).getY() - this.grid.getView().scroller.dom.scrollTop, + rowHeight = currentRow.offsetHeight + ; + this.rowPosition = e.getPageY() - resolvedRow - rowHeight / 2; + if (this.currentRowEl) { + this.currentRowEl.removeClass('grid-row-insert-below'); + this.currentRowEl.removeClass('grid-row-insert-above'); + } + if (this.rowPosition > 0) { + this.currentRowEl = new Ext.Element(currentRow); + this.currentRowEl.addClass('grid-row-insert-below'); + } else if (rindex - 1 >= 0) { + const previousRow = this.grid.getView().getRow(rindex - 1); + this.currentRowEl = new Ext.Element(previousRow); + this.currentRowEl.addClass('grid-row-insert-below'); + } else { + this.currentRowEl.addClass('grid-row-insert-above'); + } + } catch (err) { + console.warn(err); + rindex = false; + } + return rindex === false ? this.dropNotAllowed : this.dropAllowed; + }, + notifyOut: function(dd, e, data) { + if (this.currentRowEl) { + this.currentRowEl.removeClass('grid-row-insert-above'); + this.currentRowEl.removeClass('grid-row-insert-below'); + } + } + }); + if (this.targetCfg) { + Ext.apply(this.target, this.targetCfg); + } + if (this.scrollable) { + Ext.dd.ScrollManager.register(grid.getView().getEditorParent()); + grid.on({ beforedestroy: this.onBeforeDestroy, scope: this, single: true }); + } + }, + getTarget: function() { + return this.target; + }, + getGrid: function() { + return this.grid; + }, + getCopy: function() { + return this.copy; + }, + setCopy: function(copy) { + this.copy = copy; + }, + onBeforeDestroy: function(grid) { + Ext.dd.ScrollManager.unregister(grid.getView().getEditorParent()); + } +}); /** selectability in Ext grids */ if (!Ext.grid.GridView.prototype.templates) { @@ -872,20 +1302,22 @@ Ext.grid.GridView.prototype.templates.cell = new Ext.Template( /* combocolumn */ if (!MODx.grid) { MODx.grid = {}; } -MODx.grid.ComboColumn = Ext.extend(Ext.grid.Column,{ - gridId: undefined - ,constructor: function(cfg){ +MODx.grid.ComboColumn = Ext.extend(Ext.grid.Column, { + gridId: undefined, + constructor: function(cfg) { MODx.grid.ComboColumn.superclass.constructor.call(this, cfg); - this.renderer = (this.editor && this.editor.triggerAction) ? MODx.grid.ComboBoxRenderer(this.editor,this.gridId, cfg.renderer) : function(value) {return value;}; + this.renderer = (this.editor && this.editor.triggerAction) ? MODx.grid.ComboBoxRenderer(this.editor, this.gridId, cfg.renderer) : function(value) { return value; }; } }); -Ext.grid.Column.types['combocolumn'] = MODx.grid.ComboColumn; +Ext.grid.Column.types.combocolumn = MODx.grid.ComboColumn; MODx.grid.ComboBoxRenderer = function(combo, gridId, currentRenderer) { - var getValue = function(value) { - var idx = combo.store.find(combo.valueField, value); - var rec = combo.store.getAt(idx); - if (rec) { - return rec.get(combo.displayField); + const getValue = value => { + const + idx = combo.store.find(combo.valueField, value), + record = combo.store.getAt(idx) + ; + if (record) { + return record.get(combo.displayField); } return value; }; @@ -893,7 +1325,7 @@ MODx.grid.ComboBoxRenderer = function(combo, gridId, currentRenderer) { return function(value, metaData, record, rowIndex, colIndex, store) { if (currentRenderer) { if (typeof currentRenderer.fn === 'function') { - var scope = (currentRenderer.scope) ? currentRenderer.scope : false; + const scope = (currentRenderer.scope) ? currentRenderer.scope : false; currentRenderer = currentRenderer.fn.bind(scope); } @@ -902,23 +1334,19 @@ MODx.grid.ComboBoxRenderer = function(combo, gridId, currentRenderer) { } } - if (combo.store.getCount() == 0 && gridId) { - combo.store.on( - 'load', - function() { - var grid = Ext.getCmp(gridId); - if (grid) { - grid.getView().refresh(); - } - }, this, {single: true} - ); + if (combo.store.getCount() === 0 && gridId) { + combo.store.on('load', function() { + const grid = Ext.getCmp(gridId); + if (grid) { + grid.getView().refresh(); + } + }, this, { single: true }); return value; } return getValue(value); }; }; - Ext.Button.buttonTemplate = new Ext.Template( '' ); @@ -931,7 +1359,6 @@ Ext.TabPanel.prototype.itemTpl = new Ext.Template( Ext.TabPanel.prototype.itemTpl.disableFormats = true; Ext.TabPanel.prototype.itemTpl.compile(); - Ext.namespace('Ext.ux.form'); /** @@ -943,8 +1370,9 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { aggregateSubmitField: {}, initComponent: function() { - const me = this, - ct = this.ownerCt + const + me = this, + ct = this.ownerCt ; if (typeof this.name === 'string' && this.name.length > 0) { this.aggregateSubmitField = new Ext.form.Hidden({ @@ -953,12 +1381,13 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { Ext.ux.form.CheckboxGroup.superclass.initComponent.call(this); - this.cls = typeof this.cls === 'string' && this.cls.length > 0 ? 'aggregated-group ' + this.cls : 'aggregated-group' ; + this.cls = typeof this.cls === 'string' && this.cls.length > 0 ? `aggregated-group ${this.cls}` : 'aggregated-group' ; + // eslint-disable-next-line func-names, prefer-arrow-callback Ext.each(this.items, function(item) { if (typeof me.value === 'string' && me.value.length > 0) { const savedVals = me.value.split(','); - if (savedVals.find(function(v){ return v == item.inputValue; }) == item.inputValue) { + if (savedVals.find(function(v) { return v === item.inputValue; }) === item.inputValue) { item.checked = true; } me.aggregateSubmitField.setValue(me.value); @@ -968,7 +1397,7 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { fn: me.setHiddenSubmit, scope: me } - } + }; item.submitValue = false; }); ct.add(this.aggregateSubmitField); @@ -977,8 +1406,10 @@ Ext.ux.form.CheckboxGroup = Ext.extend(Ext.form.CheckboxGroup, { } }, setHiddenSubmit: function() { - const groupOpts = this.items.items; - let vals = []; + const + groupOpts = this.items.items, + vals = [] + ; Ext.each(groupOpts, function(item) { if (item.checked) { vals.push(item.inputValue); @@ -998,7 +1429,7 @@ Ext.reg('xcheckboxgroup', Ext.ux.form.CheckboxGroup); */ Ext.define('AddFieldUtilities.plugin.Class', { alias: 'plugin.fieldutilities', - init: function(cmp){ + init: function(cmp) { cmp.on('afterrender', this.afterRender, cmp); }, afterRender: function() { @@ -1009,7 +1440,7 @@ Ext.define('AddFieldUtilities.plugin.Class', { tag: 'a', title: _('field_reset'), cls: 'modx-field-utils modx-field-reset' - }).on('click', function(){ + }).on('click', function() { me.reset(); }, me); @@ -1018,8 +1449,8 @@ Ext.define('AddFieldUtilities.plugin.Class', { tag: 'a', title: _('field_clear'), cls: 'modx-field-utils modx-field-clear' - }).on('click', function(){ - switch(this.xtype) { + }).on('click', function() { + switch (this.xtype) { case 'xcheckboxgroup': case 'checkboxgroup': if (Ext.isArray(this.items.items)) { @@ -1060,43 +1491,71 @@ Ext.define('AddFieldUtilities.plugin.Class', { // setcookie($this->getProperty('cookieName'), 'true', time() + 10, '/'); // } -MODx.util.FileDownload = function (fields) { +MODx.util.FileDownload = function(fields) { if (!Ext.isObject(fields)) { return; } + let polling = fields.timeout * 10 || 300; + const + me = this, + cookieName = `fileDownload${me.randomHex(16)}`, + ident = fields.ident || `filedownload-${Ext.id()}`, + url = fields.url || MODx.config.connector_url, + params = fields.params || {}, + debug = fields.debug || false, + successCallback = fields.success || null, + failureCallback = fields.failure || null, + body = Ext.getBody(), + frame = body.createChild({ + tag: 'iframe', + cls: 'x-hidden', + id: `${ident}-iframe`, + name: `${ident}-iframe` + }), + form = body.createChild({ + tag: 'form', + cls: 'x-hidden', + id: `${ident}-form`, + action: url, + target: `${ident}-iframe`, + method: 'post' + }) + ; - var me = this; - me.clearCookie = function () { - Ext.util.Cookies.set(cookieName, null, new Date("January 1, 1970"), '/'); + me.clearCookie = function() { + Ext.util.Cookies.set(cookieName, null, new Date('January 1, 1970'), '/'); Ext.util.Cookies.clear(cookieName, '/'); - } - me.randomHex = function (len) { + }; + me.randomHex = function(len) { const hex = '0123456789ABCDEF'; let output = ''; for (let i = 0; i < len; ++i) { output += hex.charAt(Math.floor(Math.random() * hex.length)); } return output; - } - me.isFinished = function (successCallback, failureCallback) { + }; + me.isFinished = function(successCallback, failureCallback) { // Check if file is started downloading if (Ext.util.Cookies.get(cookieName) && Ext.util.Cookies.get(cookieName) === 'true') { me.clearCookie(); if (successCallback) { - successCallback({success: true, message: _('$file_msg_download_success')}); + successCallback({ + success: true, + message: _('$file_msg_download_success') + }); } return; } // Check for error / IF any error happens the frame will have content try { if (frame.dom.contentDocument.body.innerHTML.length > 0) { - var result = Ext.decode(frame.dom.contentDocument.body.innerHTML); - result = (result) ? result : {success: false, message: _('file_msg_download_error')}; + let result = Ext.decode(frame.dom.contentDocument.body.innerHTML); + result = result || { success: false, message: _('file_msg_download_error') }; me.clearCookie(); if (failureCallback) { failureCallback(result); } - frame.dom.contentDocument.body.innerHTML = ""; + frame.dom.contentDocument.body.innerHTML = ''; return; } } catch (e) { @@ -1105,10 +1564,10 @@ MODx.util.FileDownload = function (fields) { if (polling) { if (debug) { - console.log('polling ' + polling); + console.log(`polling ${polling}`); } // Download is not finished. Check again in 100 milliseconds. - window.setTimeout(function () { + window.setTimeout(function() { polling--; me.isFinished(successCallback, failureCallback); }, 100); @@ -1116,45 +1575,22 @@ MODx.util.FileDownload = function (fields) { // Polling timeout with no fileDownload cookie set me.clearCookie(); if (failureCallback) { - failureCallback({success: false, message: _('file_err_download_timeout')}); + failureCallback({ success: false, message: _('file_err_download_timeout') }); } } }; - var cookieName = 'fileDownload' + me.randomHex(16); - var polling = fields.timeout * 10 || 300; - var ident = fields.ident || 'filedownload-' + Ext.id(); - var url = fields.url || MODx.config.connector_url; - var params = fields.params || {}; - var debug = fields.debug || false; - var successCallback = fields.success || null; - var failureCallback = fields.failure || null; - - var body = Ext.getBody(); - var frame = body.createChild({ - tag: 'iframe', - cls: 'x-hidden', - id: ident + '-iframe', - name: ident + '-iframe', - }); - var form = body.createChild({ - tag: 'form', - cls: 'x-hidden', - id: ident + '-form', - action: url, - target: ident + '-iframe', - method: 'post', - }); params.HTTP_MODAUTH = MODx.siteId; if (typeof successCallback === 'function') { params.cookieName = cookieName; } - Ext.iterate(params, function (name, value) { + // eslint-disable-next-line func-names, prefer-arrow-callback + Ext.iterate(params, function(name, value) { form.createChild({ tag: 'input', type: 'text', cls: 'x-hidden', - id: ident + '-' + name, + id: `${ident}-${name}`, name: name, value: value }); diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js index fb88431782b..70fc2f1740a 100644 --- a/manager/assets/modext/widgets/core/modx.grid.js +++ b/manager/assets/modext/widgets/core/modx.grid.js @@ -1,107 +1,115 @@ Ext.namespace('MODx.grid'); -MODx.grid.Grid = function(config = {}) { +/** + * @class MODx.grid.GridBase + * @extends Ext.grid.EditorGridPanel + * @param {Object} config An object of configuration properties + */ +MODx.grid.GridBase = function GridBase(config = {}) { this.config = config; this._loadStore(); this._loadColumnModel(); + this._loadMenu(); - Ext.applyIf(config,{ - store: this.store - ,cm: this.cm - ,sm: new Ext.grid.RowSelectionModel({singleSelect:true}) - ,paging: (config.bbar ? true : false) - ,loadMask: true - ,autoHeight: true - ,collapsible: true - ,stripeRows: true - ,header: false - ,cls: 'modx-grid' - ,preventRender: true - ,preventSaveRefresh: true - ,showPerPage: true - ,stateful: false - ,showActionsColumn: true - ,disableContextMenuAction: false - ,menuConfig: { - defaultAlign: 'tl-b?' - ,enableScrolling: false - } - ,viewConfig: { - forceFit: true - ,enableRowBody: true - ,autoFill: true - ,showPreview: true - ,scrollOffset: 0 - ,emptyText: config.emptyText || _('ext_emptymsg') - } - ,groupingConfig: { - enableGroupingMenu: true - } - }); if (config.paging) { - var pgItms = config.showPerPage ? [_('per_page')+':',{ - xtype: 'textfield' - ,cls: 'x-tbar-page-size' - ,value: config.pageSize || (parseInt(MODx.config.default_per_page) || 20) - ,listeners: { - 'change': {fn:this.onChangePerPage,scope:this} - ,'render': {fn: function(cmp) { - new Ext.KeyMap(cmp.getEl(), { - key: Ext.EventObject.ENTER - ,fn: this.blur - ,scope: cmp - }); - },scope:this} + const pgItms = config.showPerPage ? [`${_('per_page')}:`, { + xtype: 'textfield', + cls: 'x-tbar-page-size', + value: config.pageSize || (parseInt(MODx.config.default_per_page, 10) || 20), + listeners: { + change: { + fn: this.onChangePerPage, + scope: this + }, + render: { + fn: function(cmp) { + new Ext.KeyMap(cmp.getEl(), { + key: Ext.EventObject.ENTER, + fn: this.blur, + scope: cmp + }); + }, + scope: this + } } }] : []; if (config.pagingItems) { - for (var i=0;i 1 ? "' - + (config.pluralText || _('records')) + '" : "' - + (config.singleText || _('record')) + '"]})' + const groupingConfig = { + forceFit: true, + scrollOffset: 0, + groupTextTpl: `{text} ({[values.rs.length]} {[values.rs.length > 1 ? '${config.pluralText || _('records')}' : '${config.singleText || _('record')}']})` }; Ext.applyIf(config.groupingConfig, groupingConfig); - Ext.applyIf(config,{ + if (Object.hasOwn(config, 'viewConfig') && Object.hasOwn(config.viewConfig, 'getRowClass')) { + Ext.applyIf(config.groupingConfig, { + getRowClass: config.viewConfig.getRowClass + }); + } + Ext.applyIf(config, { view: new Ext.grid.GroupingView(config.groupingConfig) }); } if (config.tbar) { - for (var ix = 0;ix 1)) { return false; } } - return true; }; @@ -113,10 +121,10 @@ MODx.grid.Grid = function(config = {}) { } config.columns.push({ - id: 'modx-actions' - ,width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + id: 'modx-actions', + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + renderer: this.actionsColumnRenderer.bind(this) }); } @@ -128,29 +136,21 @@ MODx.grid.Grid = function(config = {}) { } config.cm.columns.push({ - id: 'modx-actions' - ,width: config.actionsColumnWidth || defaultActionsColumnWidth - ,menuDisabled: true - ,renderer: this.actionsColumnRenderer.bind(this) + id: 'modx-actions', + width: config.actionsColumnWidth || defaultActionsColumnWidth, + menuDisabled: true, + renderer: this.actionsColumnRenderer.bind(this) }); } } - MODx.grid.Grid.superclass.constructor.call(this,config); - this._loadMenu(config); - this.addEvents('beforeRemoveRow','afterRemoveRow','afterAutoSave'); - if (this.autosave) { - this.on('afterAutoSave', this.onAfterAutoSave, this); - } - if (!config.preventRender) { this.render(); } + MODx.grid.GridBase.superclass.constructor.call(this, config); + + this.addEvents('beforeRemoveRow', 'afterRemoveRow'); + this.on({ - render: { - fn: function() { - const topToolbar = this.getTopToolbar(); - if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') { - this.hasNestedFilters = true; - } - }, + click: { + fn: this.onClickHandler, scope: this }, rowcontextmenu: { @@ -158,323 +158,67 @@ MODx.grid.Grid = function(config = {}) { scope: this } }); - if (config.autosave) { - this.on('afteredit',this.saveRecord,this); - } - - if (config.paging && config.grouping) { - this.getBottomToolbar().bind(this.store); - } - - if (!config.paging && !config.hasOwnProperty('pageSize')) { - config.pageSize = 0; - } - - this.getStore().load({ - params: { - start: config.pageStart || 0 - ,limit: config.hasOwnProperty('pageSize') ? config.pageSize : (parseInt(MODx.config.default_per_page) || 20) - } - }); - this.getStore().on('exception',this.onStoreException,this); - this.config = config; - - this.on('click', this.onClickHandler, this); }; -Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ - windows: {} +Ext.extend(MODx.grid.GridBase, Ext.grid.EditorGridPanel, { + currentLanguage: MODx.config.cultureKey || 'en', - ,onStoreException: function(dataProxy, type, action, options, response) { - const responseStatusCode = response.status || 'Unknown', - responseStatusText = !Ext.isEmpty(response.statusText) ? `(${response.statusText})` : '' - ; - let output = '', - msg = '' - ; - if (Ext.isEmpty(response.responseText)) { - // When php display_error is off, responseText will likely be empty and only general status info will be available - output = responseStatusCode !== 200 ? `
    ${responseStatusCode} ${responseStatusText}
    ` : ''; - } else { - // When php display_error is on OR the error is caught and explicity sent from the MODx class triggering the error, responseText should contain error text or possibly an object containing message text - try { - const responseText = Ext.decode(response.responseText); - // In what scenario will responseText be an object with a message property? - if (responseText && responseText.message) { - output = responseText.message; - } - } catch (e) { - output = response.responseText; - } - } - if (output) { - if (MODx.config.debug > 0) { - output = MODx.util.safeHtml(output, '
    ', 'class,colspan,rowspan'); - msg = _('error_grid_get_content_toscreen', { - message: `
    ${output}
    ` - }); - } else { - msg = _('error_grid_get_content_tolog'); - output = Ext.util.Format.stripTags(output).replaceAll('>', '>').replaceAll('<', '<'); - console.error(output); - } - } else { - // With some scenarios, such as when php display_errors = 1 and MODx system setting debug = 0 (reporting off), the reponseText will be empty and the status will be 200 - msg = _('error_grid_get_content_no_msg'); - } - this.getView().emptyText = `
    ${msg}
    `; - this.getView().refresh(false); - } + windows: {}, - ,saveRecord: function(e) { - e.record.data.menu = null; - var p = this.config.saveParams || {}; - Ext.apply(e.record.data,p); - var d = Ext.util.JSON.encode(e.record.data); - var url = this.config.saveUrl || (this.config.url || this.config.connector); - MODx.Ajax.request({ - url: url - ,params: { - action: this.config.save_action || 'updateFromGrid' - ,data: d - } - ,listeners: { - success: { - fn: function(r) { - if (this.config.save_callback) { - Ext.callback(this.config.save_callback,this.config.scope || this,[r]); - } - e.record.commit(); - if (!this.config.preventSaveRefresh) { - const gridRefresh = new Ext.util.DelayedTask(() => this.refresh()); - gridRefresh.delay(200); - } - this.fireEvent('afterAutoSave',r); - } - ,scope: this - } - ,failure: { - fn: function(r) { - e.record.reject(); - this.fireEvent('afterAutoSave', r); - } - ,scope: this - } - } - }); - } + protectedIdentifiers: null, /** - * Method executed after a record has been edited/saved inline from within the grid - * - * @param {Object} response - The processor save response object. See modConnectorResponse::outputContent (PHP) + * @property {String} permissionsProviderProp Specifies which property within a record contains + * the permissions object ('data' or 'json'). Local grids use Array stores where only the + * data *values* are stored in a simple array (record.json); the permissions and other object + * data must be stored in record.data. Remote stores, however, store their non-form (derived) + * data such as permissions in record.json. */ - ,onAfterAutoSave: function(response) { - if (!response.success && response.message === '') { - var msg = ''; - if (response.data.length) { - // We get some data for specific field(s) error but not regular error message - Ext.each(response.data, function(data, index, list) { - msg += (msg != '' ? '
    ' : '') + data.msg; - }, this); - } - if (Ext.isEmpty(msg)) { - // Still no valid message so far, let's use some fallback - msg = this.autosaveErrorMsg || _('error'); - } - MODx.msg.alert(_('error'), msg); - } - } - - ,onChangePerPage: function(tf,nv) { - if (Ext.isEmpty(nv)) return false; - nv = parseInt(nv); - this.getBottomToolbar().pageSize = nv; - this.store.load({params:{ - start:0 - ,limit: nv - }}); - } + permissionsProviderProp: 'data', - ,loadWindow: function(btn,e,win,or) { - var r = this.menu.record; - if (!this.windows[win.xtype] || win.force) { - Ext.applyIf(win,{ - record: win.blankValues ? {} : r - ,grid: this - ,listeners: { - 'success': {fn:win.success || this.refresh,scope:win.scope || this} - } - }); - if (or) { - Ext.apply(win,or); - } - this.windows[win.xtype] = Ext.ComponentMgr.create(win); - } - if (this.windows[win.xtype].setValues && win.blankValues !== true && r != undefined) { - this.windows[win.xtype].setValues(r); - } - this.windows[win.xtype].show(e.target); - } + userCanEdit: false, - ,confirm: function(type,text) { - var p = { action: type }; - var k = this.config.primaryKey || 'id'; - p[k] = this.menu.record[k]; + userCanCreate: false, - MODx.msg.confirm({ - title: _(type) - ,text: _(text) || _('confirm_remove') - ,url: this.config.url - ,params: p - ,listeners: { - 'success': {fn:this.refresh,scope:this} - } - }); - } + userCanDelete: false, - ,remove: function(text, action) { - if (this.destroying) { - return MODx.grid.Grid.superclass.remove.apply(this, arguments); - } - var r = this.menu.record; - text = text || 'confirm_remove'; - var p = this.config.saveParams || {}; - Ext.apply(p,{ action: action || 'remove' }); - var k = this.config.primaryKey || 'id'; - p[k] = r[k]; + gridMenuActions: [], - if (this.fireEvent('beforeRemoveRow',r)) { - MODx.msg.confirm({ - title: _('warning') - ,text: _(text, r) - ,url: this.config.url - ,params: p - ,listeners: { - 'success': {fn:function() { - this.removeActiveRow(r); - },scope:this} - } - }); - } - } + /** + * @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, - ,removeActiveRow: function(r) { - if (this.fireEvent('afterRemoveRow',r)) { - var rx = this.getSelectionModel().getSelected(); - this.getStore().remove(rx); - } - } + /** @property {Boolean} userHasPermissions Whether user has permissions of any kind to manipulate the current grid's data */ + userHasPermissions: false, - ,_loadMenu: function() { - this.menu = new Ext.menu.Menu(this.config.menuConfig); - } + /** @property {Boolean} userHasSavePermissions Whether user has the general ability to save (to either create or edit) */ + userHasSavePermissions: false, - ,_showMenu: function(g,ri,e) { - e.stopEvent(); - e.preventDefault(); - this.menu.record = this.getStore().getAt(ri).data; - if (!this.getSelectionModel().isSelected(ri)) { - this.getSelectionModel().selectRow(ri); - } - this.menu.removeAll(); - if (this.getMenu) { - var m = this.getMenu(g,ri,e); - if (m && m.length && m.length > 0) { - this.addContextMenuItem(m); - } - } - if ((!m || m.length <= 0) && this.menu.record.menu) { - this.addContextMenuItem(this.menu.record.menu); - } - if (this.menu.items.length > 0) { - this.menu.showAt(e.xy); - } - } + showActionsMenu: null, - ,_loadStore: function() { - if (this.config.grouping) { - this.store = new Ext.data.GroupingStore({ - url: this.config.url - ,baseParams: this.config.baseParams || { action: this.config.action || 'getList'} - ,reader: new Ext.data.JsonReader({ - totalProperty: 'total' - ,root: 'results' - ,fields: this.config.fields - }) - ,sortInfo:{ - field: this.config.sortBy || 'id' - ,direction: this.config.sortDir || 'ASC' - } - ,remoteSort: this.config.remoteSort || false - ,remoteGroup: this.config.remoteGroup || false - ,groupField: this.config.groupBy || 'name' - ,groupDir: this.config.groupDir || 'ASC' - ,storeId: this.config.storeId || Ext.id() - ,autoDestroy: true - ,listeners: { - beforeload: function(store, options) { - const changedGroupDir = store.groupField === store.sortInfo.field && store.groupDir !== store.sortInfo.direction; - if (changedGroupDir) { - store.groupDir = store.sortInfo.direction; - store.baseParams.groupDir = store.sortInfo.direction; - } - }, - load: function(store, records, options) { - const cmp = Ext.getCmp('modx-content'); - if (cmp) { - cmp.doLayout(); - } - }, - groupchange: { - fn: function(store, groupField) { - store.groupDir = this.config.groupDir || 'ASC'; - store.baseParams.groupDir = store.groupDir; - store.sortInfo.direction = this.config.sortDir || 'ASC'; - store.load(); - }, - scope: this - } - } - }); - } else { - this.store = new Ext.data.JsonStore({ - url: this.config.url - ,baseParams: this.config.baseParams || { action: this.config.action || 'getList' } - ,fields: this.config.fields - ,root: 'results' - ,totalProperty: 'total' - ,remoteSort: this.config.remoteSort || false - ,storeId: this.config.storeId || Ext.id() - ,autoDestroy: true - ,listeners:{ - load: function() { - const cmp = Ext.getCmp('modx-content'); - if (cmp) { - cmp.doLayout(); - } - } - } - }); - } - } + // -*-*-* LOADERS *-*-*- - ,_loadColumnModel: function() { + _loadColumnModel: function() { if (this.config.columns) { - var c = this.config.columns; - for (var i=0;i' + '' + '
      ' @@ -734,10 +367,51 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ + '', { compiled: true }); - } + }, + + actionsColumnRenderer: function(value, metaData, record, rowIndex, colIndex, store) { + /* + Note: To maintain backward compatibility for core grids that have not yet been updated + to the new permissions checks and for extras that may extend this class in their grids, + we check showActionsMenu for strict boolean values (which will only be set by grids using + the new checks); otherwise showActionsMenu will be null (its default value set above), + indicating the legacy checks are to be used. + */ + if (this.showActionsMenu === false) { + return; + } + /* + showActionsMenu will be true if at least one user group-level permission is granted, + excluding create/new permissions (since that is not executed by our context/actions menus). + */ + if (this.showActionsMenu) { + // Export is always available; only continue filtering if grid does not offer export + if (!this.gridMenuActions.includes('export')) { + const isProtected = record[this.permissionsProviderProp]?.isProtected || false; + if (!this.userHasSavePermissions && isProtected) { + return; + } + const hasPermissionsProp = Object.hasOwn(record[this.permissionsProviderProp], 'permissions'); + // Checking record-level permissions + /** @todo This block checking for 'cls' can be removed once all grids are updated */ + if (!hasPermissionsProp && Object.hasOwn(record.data, 'cls')) { + if (Ext.isEmpty(record.data.cls)) { + return; + } + } + if (hasPermissionsProp) { + if ( + Ext.isEmpty(record[this.permissionsProviderProp].permissions) + || Object.values(record[this.permissionsProviderProp].permissions).every(permission => !permission) + ) { + return; + } + } + } + } + // eslint-disable-next-line prefer-spread + const actions = this.getActions.apply(this, arguments); - ,actionsColumnRenderer: function(value, metaData, record, rowIndex, colIndex, store) { - var actions = this.getActions.apply(this, [record, rowIndex, colIndex, store]); if (this.config.disableContextMenuAction !== true) { actions.push({ text: _('context_menu'), @@ -745,36 +419,156 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ icon: 'gear' }); } - return this._getActionsColumnTpl().apply({ actions: actions }); - } + }, + + getActions: function(value, metaData, record, rowIndex, colIndex, store) { + return []; + }, + + /** + * Builds the menu activated by clicking an action column icon (typically gear menu) + * @param {*} record The selected row's record + * @param {*} rowIndex The selected row's zero-based index + * @param {Ext.EventObjectImpl} e The Ext extended event object + */ + actionContextMenu: function(record, rowIndex, e) { + this._showMenu(this, rowIndex, e); + }, + + addContextMenuItem: function(items) { + items.forEach(menuItem => { + if (menuItem === '-') { + this.menu.add('-'); + } else { + let handler = Ext.emptyFn; + if (menuItem.handler) { + // eslint-disable-next-line no-eval + handler = eval(menuItem.handler); + if (handler && typeof (handler) == 'object' && handler.xtype) { + handler = this.loadWindow.createDelegate(this, [handler], true); + } + } else { + handler = function(item) { + const + { options } = item, + { id } = this.menu.record, + // eslint-disable-next-line no-shadow + doAction = (id, options) => { + const + action = Ext.urlEncode(options.params || { action: options.action }), + query = `?${action}&id=${id}`, + content = Ext.get('modx_content') + ; + if (content === null) { + window.location.href = query; + } else { + content.dom.src = query; + } + } + ; + if (options.confirm) { + // eslint-disable-next-line prefer-arrow-callback, func-names + Ext.Msg.confirm('', options.confirm, function(e) { + if (e === 'yes') { + doAction(id, options); + } + }, this); + } else { + doAction(id, options); + } + }; + } + this.menu.add({ + id: menuItem.id || Ext.id(), + text: menuItem.text, + scope: menuItem.scope || this, + options: menuItem, + handler: handler + }); + } + }); + }, + + /** + * Based on properties set in the calling child class and the current user's + * permissions for actions taken within that class (create, edit, delete, etc), + * evaluates whether the actions menu trigger should appear and sets boolean value + * on the showActionsMenu property + * @param {String} action The permissions key to evaluate; typically keys matching + * crud actions (create, update, delete), and/or sometimes custom ones (e.g., updateTv, etc) + * @param {Array} permissions Optional custom list of permissions required to show actions + * + * @return void + */ + setShowActionsMenu: function(action = null, permissions = []) { + if (this.config.disableContextMenuAction === true) { + this.showActionsMenu = false; + return; + } + if (permissions.length > 0) { + this.showActionsMenu = this.setUserHasPermissions(action, permissions); + } else { + const permissionsValues = []; + this.gridMenuActions.forEach(mode => { + mode = mode === 'duplicate' ? 'userCanCreate' : `userCan${MODx.util.Format.firstToUpperCase(mode)}`; + const modePermission = mode === 'userCanExport' ? true : this[mode]; + if (['userCanCreate', 'userCanEdit'].includes(mode) && modePermission === true) { + this.userHasSavePermissions = true; + } + permissionsValues.push(modePermission); + }); + this.showActionsMenu = !(permissionsValues.length === 0 || permissionsValues.every(value => value === false) === true); + } + }, - ,renderLink: function(v,attr) { - var el = new Ext.Element(document.createElement('a')); - el.addClass('x-grid-link'); - el.dom.title = _('edit'); - for (var i in attr) { - el.dom[i] = attr[i]; + /** + * Displays a row's context menu + * @param {Object} grid The selected row's grid + * @param {Number} rowIndex The selected row's zero-based index + * @param {Ext.EventObjectImpl} e The Ext extended event object + */ + _showMenu: function(grid, rowIndex, e) { + e.stopEvent(); + e.preventDefault(); + this.menu.record = this.getStore().getAt(rowIndex).data; + this.menu.recordIndex = rowIndex; + if (!this.getSelectionModel().isSelected(rowIndex)) { + this.getSelectionModel().selectRow(rowIndex); } - el.dom.innerHTML = Ext.util.Format.htmlEncode(v); - return el.dom.outerHTML; - } + this.menu.removeAll(); + let menu; + if (this.getMenu) { + menu = this.getMenu(grid, rowIndex, e); + if (menu && menu.length && menu.length > 0) { + this.addContextMenuItem(menu); + } + } + if ((!menu || menu.length <= 0) && this.menu.record.menu) { + this.addContextMenuItem(this.menu.record.menu); + } + if (this.menu.items.length > 0) { + this.menu.showAt(e.xy); + } + }, + + // -*-*-* PERMISSIONS HANDLING *-*-*- /** * Deprecated; renamed checkCellIsEditable. Remove in 3.1 */ - ,checkEditable: function(e) { + checkEditable: function(e) { this.checkCellIsEditable(e); - } + }, /** * Disables cell editor under specified conditions * @param {Object} e - Ext event object containing references to grid, record, field, value, row (index), column (index), and cancel (set true to cancel edit). * @return {Boolean} Return false to cancel or true to commit the edit */ - ,checkCellIsEditable: function(e) { + checkCellIsEditable: function(e) { const permissions = e.record.data.perm || ''; if (permissions.indexOf('edit') === -1) { return false; @@ -784,7 +578,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ case 'modx-grid-role': { const isAuthorityField = e.field === 'authority', - roleIsAssigned = e.record.json.isAssigned + roleIsAssigned = e.record[this.permissionsProviderProp].isAssigned ; if (roleIsAssigned && isAuthorityField) { return false; @@ -794,20 +588,20 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ default: } return true; - } + }, /** * Add one or more classes to a specific Editor Grid cell, typically to indicate a level of restriction - * + * * @param {Object} record - The row's data record * @param {Array} lockConditions - A set of one or more Boolean values (or ones that cast correctly to the expected Boolean value) derived from the row record or other values that indicate whether or not the subject cell should be marked as locked * @param {String} lockedClasses - One or more css class names * @param {Boolean} conditionsRequireAll - Whether all passed lockConditions need to evaluate to true to apply the locked class(es) */ - ,setEditableCellClasses: function(record, lockConditions = [], lockedClasses = 'locked', conditionsRequireAll = true) { + setEditableCellClasses: function(record, lockConditions = [], lockedClasses = '', conditionsRequireAll = true) { const - permissions = record.data.perm.trim(), - hasEditPermission = permissions.split(' ').includes('edit') + userCanEditRecord = this.userCanEditRecord(record), + lockedCSS = lockedClasses || 'locked' ; let classes = '', @@ -819,110 +613,148 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ : lockConditions.some(condition => Boolean(condition) === true) ; } - if (Ext.isEmpty(permissions)) { + if (!this.userCanEdit || !this.userHasRecordPermissions(record) || !userCanEditRecord) { classes = 'editor-disabled'; - } else if (hasEditPermission && shouldLock) { - classes = lockedClasses; + } else if (userCanEditRecord && shouldLock) { + classes = lockedCSS; } return classes; - } + }, + + // -> User- /User Group-Level Permissions Checks for the calling "class" object /** - * @property {Function} getLinkTemplate - Adds a link on a grid column's value based on the passed params. - * Usage of this method is necessary for grouping grids, where usage of renderers on its column model - * interfere with the grouping functionality. + * Assesses whether user can take the given action on an object or + * has been granted one of a custom list of permissions * - * @param {String} controllerPath - The initial part of the URL query indicating the controller action - * @param {String} displayValueIndex - The data index used as the link's text - * @param {Object} options - Additional URL query parameters (linkParams) and attributes for the link's anchor tag - * @return {Ext.Template} + * @param {String} action Identifies the action (create, edit, delete, or custom action) + * being evaluated. This applies to only a single object type and not to grids + * that have mixed object types displayed (in which case a custom list of permissions + * should be supplied to setShowActionsMenu, which in turn calls this method). + * @param {Array} permissions The list of permissions keys to be evaluated + * @returns {Boolean} Whether the user has permissions for this action or a set of custom set of permissions. */ - ,getLinkTemplate: function(controllerPath, displayValueIndex, options = {}) { - /* - linkParams, if given, should be an array of objects in the following format: - [{ key: 'paramKey', valueIndex: 'paramValue' }, ...{}] - */ - Ext.applyIf(options, { - linkParams: [], - linkClass: 'x-grid-link', - linkTitle: _('edit'), - linkTarget: '_blank' - }); - let params = ''; - controllerPath = controllerPath.indexOf('?a=') === 0 ? controllerPath : `?a=${controllerPath}` ; - if (options.linkParams.length > 0) { - params = []; - options.linkParams.forEach(param => { - params.push(`${param.key}={${param.valueIndex}}`); - }); - params = `&${params.join('&')}`; + setUserHasPermissions: function(action, permissions) { + const + permissionsList = permissions.map(item => item.trim()), + hasPermissions = action + ? permissionsList.every(permission => MODx.perm[permission]) + : permissionsList.some(permission => MODx.perm[permission]) + ; + if (action) { + this[`userCan${MODx.util.Format.firstToUpperCase(action)}`] = hasPermissions; } - return new Ext.Template( - `{${displayValueIndex}:htmlEncode}`, - { compiled: true } - ); - } + // Conditional needed, as we only want to change userHasPermissions if true + if (hasPermissions) { + this.userHasPermissions = true; + } + return hasPermissions; + }, - ,getActions: function(record, rowIndex, colIndex, store) { - return []; - } + /** + * Assigns a value to userCanEdit property based on the user's permissions; + * used to adjust which menu items are available, whether to render links + * to and item's editing page, and css cues across many grid classes + * + * @param {Array} permissions - A set of permissions keys to evaluate; note that many areas currently + * rely on a pair of permissions (save_x and edit_x), both of which must be enabled to edit a grid item + * + * @return void + */ + setUserCanEdit: function(permissions) { + this.setUserHasPermissions('edit', permissions); + }, - ,onClickHandler: function(e) { - var target = e.getTarget(); - if (!target.classList.contains('x-grid-action')) return; - if (!target.dataset.action) return; + /** + * Assigns a value to userCanCreate property based on the user's permissions; + * used to adjust which menu items are available (namely the Duplicate item) + * and whether to render the Create button in the grid's toolbar + * + * @param {Array} permissions - A set of permissions keys to evaluate; note that many areas currently + * rely on a pair of permissions (save_x and new_x), both of which must be enabled to create/duplicate a grid item + * + * @return void + */ + setUserCanCreate: function(permissions) { + this.setUserHasPermissions('create', permissions); + }, - var actionHandler = 'action' + target.dataset.action.charAt(0).toUpperCase() + target.dataset.action.slice(1); - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - actionHandler = target.dataset.action; - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - return; - } + /** + * Assigns a value to userCanDelete property based on the user's permissions; + * used to adjust which menu items are available in the context menus + * and whether to render the Delete menu item within a grid toolbar's Batch button + * + * @param {Array} permissions - A set of permissions keys to evaluate + * + * @return void + */ + setUserCanDelete: function(permissions) { + this.setUserHasPermissions('delete', permissions); + }, + + // -> Record-Level Permissions Checks, for objects with specific policies + + userHasRecordPermissions: function(record) { + const objPermissions = record[this.permissionsProviderProp].permissions; + if (Ext.isEmpty(objPermissions)) { + return false; } + return Object.values(objPermissions).some(permission => Boolean(permission) === true); + }, - var record = this.getSelectionModel().getSelected(); - var recordIndex = this.store.indexOf(record); - this.menu.record = record.data; + userCanEditRecord: function(record, action = 'update') { + const objPermissions = record[this.permissionsProviderProp].permissions; + return !Ext.isEmpty(objPermissions) && objPermissions[action] === true; + }, - this[actionHandler](record, recordIndex, e); - } + userCanDeleteRecords: function(records) { + return records.some(record => this.userCanDeleteRecord(record)); + }, - ,actionContextMenu: function(record, recordIndex, e) { - this._showMenu(this, recordIndex, e); - } + userCanDeleteRecord: function(record, action = 'delete') { + const objPermissions = record[this.permissionsProviderProp].permissions; + return !Ext.isEmpty(objPermissions) && !record[this.permissionsProviderProp].isProtected && objPermissions[action] === true; + }, - ,makeUrl: function () { - if (Array.isArray(this.config.urlFilters) && this.config.urlFilters.length > 0) { - var s = this.getStore(); - var p = { - a: MODx.request.a - } - if (MODx.request.id) { - p['id'] = MODx.request.id; - } - if (MODx.request.key) { - p['key'] = MODx.request.key; - } - for (var i = 0; i < this.config.urlFilters.length; ++i) { - if (s.baseParams.hasOwnProperty(this.config.urlFilters[i]) && s.baseParams[this.config.urlFilters[i]]) { - if (this.config.urlFilters[i] === 'namespace') { - p['ns'] = s.baseParams[this.config.urlFilters[i]]; - } else { - p[this.config.urlFilters[i]] = s.baseParams[this.config.urlFilters[i]]; - } - } - } - return Ext.urlAppend(MODx.config.manager_url, Ext.urlEncode(p).replace(/%2F/g, '/')); + userCanDuplicateRecord: function(record) { + const objPermissions = record[this.permissionsProviderProp].permissions; + return !Ext.isEmpty(objPermissions) && objPermissions.duplicate === true; + }, + + /** + * @property {Function} recordIsProtected - Used to remove the ability to delete + * specific record rows, regardless of permissions levels, based on a given record identifier + * + * @param {Number} subject - The value of the current record's identifier + * @param {Number} protectedIdentifiers - The record identifiers to be protected (making them non-editable/deletable) + * + * @return {Boolean} + */ + recordIsProtected: function(subject, protectedIdentifiers) { + if (Ext.isEmpty(protectedIdentifiers)) { + return false; } - } + protectedIdentifiers = protectedIdentifiers.map(identifier => (typeof identifier === 'string' ? identifier.trim() : identifier)); + return protectedIdentifiers.includes(subject); + }, - ,replaceState: function () { - if (typeof window.history.replaceState !== 'undefined' && - Array.isArray(this.config.urlFilters) && this.config.urlFilters.length > 0 - ) { - window.history.replaceState(this.getStore().baseParams, document.title, this.makeUrl()); + /** + * @property {Function} valueIsReserved - Wraps a grid value with a real or simulated link — a trigger that appears + * like an anchor link, usually to access a dropdown chooser or other control + * + * @param {Array|String} reservedValues - A set of values that can not be used for a particular object's field + * @param {Object} value - The submitted value being tested + * + * @return {Boolean} + */ + valueIsReserved: function(reservedValues, value) { + if (!Array.isArray(reservedValues)) { + reservedValues = reservedValues.split(','); } - } + return reservedValues.some(reserved => reserved.toLowerCase() === value.toLowerCase()); + }, + + // -*-*-* GRID FILTERING *-*-*- /** * @property {Function} findTabPanel - Recursively search ownerCts for this component's enclosing TabPanel @@ -930,28 +762,19 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @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.'); + findTabPanel: function(referenceCmp) { + if (!Object.hasOwn(referenceCmp, 'ownerCt')) { + console.error('MODx.grid.GridBase::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') + isTabPanel = Object.hasOwn(container, '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 + }, /** * Applies a value persisted via URL (MODx.request) for use in grid and filter params. Used when multiple @@ -965,15 +788,15 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * 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 ; + 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 + ? MODx.util.url.getParamValue(requestKey) + : emptyVal ; - } + }, /** * Filters the grid data by the passed filter component (field) @@ -982,7 +805,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @param {String} param - The record index to apply the filter on; * may also be the general query/search field name. */ - ,applyGridFilter: function(cmp, param = 'query') { + applyGridFilter: function(cmp, param = 'query') { const filterValue = cmp.getValue(), store = this.getStore(), urlParams = {}, @@ -1005,8 +828,8 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } else { MODx.util.url.clearParam(cmp); } - if (param == 'ns') { - store.baseParams['namespace'] = filterValue; + if (param === 'ns') { + store.baseParams.namespace = filterValue; } else { store.baseParams[param] = filterValue; } @@ -1015,7 +838,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ 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') { + if (tabPanel.xtype === 'modx-vtabs') { const parentTabPanel = this.findTabPanel(tabPanel); if (parentTabPanel) { const activeParentTab = parentTabPanel.getActiveTab(); @@ -1036,18 +859,16 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ if (parentTabItems.length > 1) { urlParams.tab = activeParentTabIdx; } - } else { - if (tabItems.length > 1) { - urlParams.tab = activeTabIdx; - } + } else if (tabItems.length > 1) { + urlParams.tab = activeTabIdx; } } store.load(); - MODx.util.url.setParams(urlParams) + MODx.util.url.setParams(urlParams); if (bottomToolbar) { bottomToolbar.changePage(1); } - } + }, /** * @property {Function} clearGridFilters - Clears all grid filters and sets them to their default value @@ -1058,7 +879,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * 'filter-category:3', where '3' is the filter's default value to be applied (instead of setting to an empty value) * */ - ,clearGridFilters: function(items) { + clearGridFilters: function(items) { const store = this.getStore(), bottomToolbar = this.getBottomToolbar(), data = Array.isArray(items) ? items : items.split(',') @@ -1066,9 +887,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ data.forEach(item => { const itemData = item.replace(/\s+/g, '').split(':'), itemId = itemData[0], - itemDefaultVal = itemData.length == 2 ? itemData[1] : null , + itemDefaultVal = itemData.length === 2 ? itemData[1] : null, cmp = this.getFilterComponent(itemId), - param = MODx.util.url.getParamNameFromCmp(cmp), + cmpParam = MODx.util.url.getParamNameFromCmp(cmp), isCombo = cmp?.xtype?.includes('combo') ; if (isCombo) { @@ -1083,13 +904,14 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ if (!Ext.isEmpty(itemDefaultVal)) { const paramsList = Object.keys(cmp.baseParams); paramsList.forEach(param => { - switch(param) { + switch (param) { case 'namespace': cmp.baseParams[param] = 'core'; break; case 'topic': cmp.baseParams[param] = 'default'; break; + // no default } }); } @@ -1098,21 +920,14 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ cmp.getStore().load(); } } - store.baseParams[param] = itemDefaultVal; + store.baseParams[cmpParam] = itemDefaultVal; }); store.load(); MODx.util.url.clearAllParams(); if (bottomToolbar) { bottomToolbar.changePage(1); } - } - - /** - * @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 + }, /** * @property {Function} getFilterComponent - Gets a filter component from the top toolbar by its itemId @@ -1120,17 +935,17 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @param {String} filterId - The Ext itemId of the filter component to fetch * @return {Ext.Component} */ - ,getFilterComponent: function(filterId) { + getFilterComponent: function(filterId) { const topToolbar = this.getTopToolbar(), cmp = this.hasNestedFilters && filterId !== 'filter-query' - ? topToolbar.find('itemId', `${filterId}-container`)[0].getComponent(filterId) - : topToolbar.getComponent(filterId) + ? topToolbar.find('itemId', `${filterId}-container`)[0].getComponent(filterId) + : topToolbar.getComponent(filterId) ; if (typeof cmp !== 'undefined') { return cmp; } console.error(`getFilterComponent: The filter component with itemId '${filterId}' could not be retrieved.`); - } + }, /** * @property {Function} refreshFilterOptions - Used to syncronize a filter's store options to those available in its target grid @@ -1138,7 +953,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @param {Array} filterData - An array of objects containing info needed to refresh each filter * @param {Boolean} clearDependentParams - If true, will clear values of dependentParams specified in the filterData */ - ,refreshFilterOptions: function(filterData = [], clearDependentParams = true) { + refreshFilterOptions: function(filterData = [], clearDependentParams = true) { if (filterData.length > 0) { filterData.forEach(data => { const filter = this.getFilterComponent(data.filterId); @@ -1146,10 +961,13 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const store = filter.getStore(); filter.setValue(''); if (store) { - if (data.hasOwnProperty('dependentParams')) { - const dependentParams = Array.isArray(data.dependentParams) ? data.dependentParams : data.dependentParams.split(','); + if (Object.hasOwn(data, 'dependentParams')) { + const dependentParams = Array.isArray(data.dependentParams) + ? data.dependentParams + : data.dependentParams.split(',') + ; dependentParams.forEach(param => { - if (clearDependentParams && store.baseParams.hasOwnProperty(param)) { + if (clearDependentParams && Object.hasOwn(store.baseParams, param)) { store.baseParams[param] = ''; } }); @@ -1158,9 +976,9 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } }); - this.refresh(); + this.refresh(); } - } + }, /** * @property {Function} updateDependentFilter - Reloads a related filter's store based on the current filter's selected item @@ -1170,7 +988,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @param {Mixed} paramValue - Filter baseParams value for the paramKey * @param {Boolean} clearValue - Set true to clear filter's selected value */ - ,updateDependentFilter: function(filterId, paramKey, paramValue, clearValue = false) { + updateDependentFilter: function(filterId, paramKey, paramValue, clearValue = false) { const filter = this.getFilterComponent(filterId), filterStore = filter ? filter.getStore() : null ; @@ -1181,7 +999,320 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ filterStore.baseParams[paramKey] = paramValue; filterStore.load(); } - } + }, + + // -*-*-* RENDERERS & ENCODING *-*-*- + + renderEditableColumn: function(renderer) { + return function(value, metaData, record, rowIndex, colIndex, store) { + if (renderer) { + if (typeof renderer.fn === 'function') { + const scope = (renderer.scope) ? renderer.scope : false; + renderer = renderer.fn.bind(scope); + } + if (typeof renderer === 'function') { + value = renderer(value, metaData, record, rowIndex, colIndex, store); + } + } + metaData.css = ['x-editable-column', metaData.css || ''].join(' '); + + return value; + }; + }, + + /** + * @property {Function} renderLink - Wraps a grid value with a real or simulated link — a trigger that appears + * like an anchor link, usually to access a dropdown chooser or other control + * + * @param {String} content - The value being wrapped + * @param {Object} attributes - Html attributes to add to the link's tag + * @param {Boolean} isSimulated - Indicates whether the link is real (anchor tag) or not (simulated) + * @param {String} isSimulatedTag - The html tag name to wrap the content with + * + * @return {String} + */ + renderLink: function(content, attributes = {}, isSimulated = false, isSimulatedTag = 'span') { + const + tag = isSimulated ? isSimulatedTag : 'a', + classes = isSimulated ? 'x-grid-link simulated-link' : 'x-grid-link', + el = new Ext.Element(document.createElement(tag)) + ; + el.addClass(classes); + // Add default title if none given in attributes + if (!Object.hasOwn(attributes, 'title')) { + attributes.title = _('edit'); + } + Object.entries(attributes).forEach(([attr, value]) => { + el.dom[attr] = value; + }); + el.dom.innerHTML = Ext.util.Format.htmlEncode(content); + return el.dom.outerHTML; + }, + + rendYesNo: function(v, metaData) { + if (v === 1 || v === '1') { v = true; } + if (v === 0 || v === '0') { v = false; } + switch (v) { + case true: + case 'true': + case 1: + metaData.css += ' green'; + return _('yes'); + case false: + case 'false': + case '': + case 0: + metaData.css += ' red'; + return _('no'); + // no default + } + }, + + rendPassword: function(v) { + let z = ''; + for (let i = 0; i < v.length; i++) { + z = `${z}*`; + } + return z; + }, + + /** + * @todo Implement encodeURIComponent to fix issues with passing particluar + * chars via URL (e.g., # [pound/hash] used in color values, etc.) + */ + encode: function() { + const p = this.getStore().getRange(), + rs = {}; + for (let i = 0; i < p.length; i++) { + rs[p[i].data[this.config.primaryKey || 'id']] = p[i].data; + } + return Ext.encode(rs); + }, + + /** + * @property {Function} getLinkTemplate - Adds a link on a grid column's value based on the passed params. + * Usage of this method is necessary for grouping grids, where usage of renderers on its column model + * interfere with the grouping functionality. + * + * @param {String} controllerPath - The initial part of the URL query indicating the controller action + * @param {String} displayValueIndex - The data index used as the link's text + * @param {Object} options - Additional URL query parameters (linkParams) and attributes for the link's anchor tag + * @return {Ext.Template} + */ + getLinkTemplate: function(controllerPath, displayValueIndex, options = {}) { + /* + linkParams, if given, should be an array of objects in the following format: + [{ key: 'paramKey', valueIndex: 'paramValue' }, ...{}] + */ + Ext.applyIf(options, { + linkParams: [], + linkClass: 'x-grid-link', + linkTitle: _('edit'), + linkTarget: '_blank' + }); + let params = ''; + controllerPath = controllerPath.indexOf('?a=') === 0 ? controllerPath : `?a=${controllerPath}`; + if (options.linkParams.length > 0) { + params = []; + options.linkParams.forEach(param => { + params.push(`${param.key}={${param.valueIndex}}`); + }); + params = `&${params.join('&')}`; + } + return new Ext.Template( + `{${displayValueIndex}:htmlEncode}`, + { compiled: true } + ); + }, + + // -*-*-* CONFIG & COMPONENT BUILDERS *-*-*- + + /** + * Gets the view configuration for grids having row-specific editing permissions + * @param {Boolean} hasBulkActions Whether the grid has a bulk actions option + * (uses the checkbox selection model to select multiple rows) + * @param {Boolean} hasObjectLevelPermissions Whether individual rows might have + * differing permissions, based on the specific object they represent + * @param {Boolean} markActiveRows Whether classes should be added for objects + * whose records that can be activated or deactivated (e.g., Form Customization, Users, etc.) + * @returns {Object} The complete view config + */ + getViewConfig: function(hasBulkActions = true, hasObjectLevelPermissions = true, markActiveRows = false) { + return { + forceFit: true, + scrollOffset: 0, + enableRowBody: true, + showPreview: true, + getRowClass: function(record, index, rowParams, store) { + const + canDeleteRecord = this.grid.userCanDeleteRecord(record), + rowClasses = [] + ; + // Initial class required for grids utilizing the row expander + if (this.cm && Object.hasOwn(this.cm.config[0], 'expandRow')) { + rowClasses.push('x-grid3-row-collapsed'); + } + /* + Objects whose records can be activated/deactivated do not depend upon + permission to delete; 'visible' prop is used in Form Customization grids, + 'active' in others + */ + if (markActiveRows && (Object.hasOwn(record.data, 'active') || Object.hasOwn(record.data, 'visible'))) { + const activeClass = record.data.active || record.data.visible ? 'grid-row-active' : 'grid-row-inactive'; + rowClasses.push(activeClass); + } + // Early return if no deletion restrictions are in effect + if (hasObjectLevelPermissions && canDeleteRecord) { + return rowClasses.length ? rowClasses.join(' ') : '' ; + } + // Add various classes marking a row as protected + if (hasBulkActions && !canDeleteRecord && !markActiveRows) { + rowClasses.push('disable-selection'); + } + if (record[this.grid.permissionsProviderProp].isProtected) { + rowClasses.push('modx-protected-row'); + } + return rowClasses.length ? rowClasses.join(' ') : '' ; + } + }; + }, + + /** + * Builds the top toolbar's create/add button + * @param {String} objectType Identifier for object being worked with + * @param {String|Object} createHandler The name of the handler method or an object containing + * a custom configuration (typically a form window config used to create a new record) + * @param {String|Boolean} createPermission Name of the grid property that specifies whether the + * current user has necessary permissions to create new records. Set to true|false to override. + * @returns {Object} An Ext button config object + */ + getCreateButton: function(objectType, createHandler = 'create', createPermission = 'userCanCreate') { + const + handler = typeof createHandler === 'string' + ? this[createHandler] + : createHandler, + text = _(`${objectType.toLowerCase()}_create`) || _('create') + ; + return { + text: text, + cls: 'primary-button', + handler: handler, + scope: this, + listeners: { + /* + Note that because this method is typically called from a grid's + tbar config object, the permissions properties are not yet available; + thus it is important that the Create permission be assessed within the + button's render (or later) event, as is done below. + */ + render: { + fn: function(btn) { + const hasPermission = typeof createPermission === 'boolean' + ? createPermission + : this[createPermission] + ; + if (!hasPermission) { + btn.hide(); + } + }, + scope: this + } + } + }; + }, + + /** + * Builds the top toolbar's bulk actions button, containing a menu of various actions + * (typically only contains a delete action) + * @param {String} objectType Identifier for object being worked with + * @param {String} deleteAction Processor path for the removal action + * @param {String} pkType Specifies the object's primary key type (int or string) + * @param {...any} moreActions Additional button identifiers or config objects + * to add to the bulk actions menu + * @returns {Object} The complete bulk actions config + */ + getBulkActionsButton: function(objectType, deleteAction, pkType = 'int', ...moreActions) { + const + menuItems = [], + additionalMenuItems = [], + hasMoreActions = moreActions.length > 0 + ; + if (hasMoreActions) { + /** @var standardButtons Button configs for actions that are used in select grids, such as the Users and Form Customization (Sets) grids */ + const standardButtons = { + activate: { + text: _('selected_activate'), + itemId: 'modx-bulk-menu-opt-activate', + handler: this.activateSelected, + scope: this + }, + deactivate: { + text: _('selected_deactivate'), + itemId: 'modx-bulk-menu-opt-deactivate', + handler: this.deactivateSelected, + scope: this + } + }; + moreActions.forEach(action => { + if (typeof action === 'string') { + const key = action.toLowerCase(); + if (Object.hasOwn(standardButtons, key)) { + additionalMenuItems.push(standardButtons[key]); + } + } + }); + menuItems.push(...additionalMenuItems); + menuItems.push('-'); + } + menuItems.push({ + text: _('selected_remove'), + itemId: 'modx-bulk-menu-opt-remove', + handler: this.removeSelected.createDelegate(this, [objectType, deleteAction, pkType]), + scope: this + }); + return { + text: _('bulk_actions'), + menu: menuItems, + listeners: { + render: { + fn: function(btn) { + if ( + (!this.userCanDelete && !hasMoreActions) + || (!this.userCanDelete && !this.userCanEdit && hasMoreActions) + ) { + btn.hide(); + } + }, + scope: this + }, + click: { + fn: function(btn) { + const + removableItems = this.getRemovableItemsFromSelection(pkType), + menuOptRemove = btn.menu.getComponent('modx-bulk-menu-opt-remove') + ; + if (removableItems.length === 0) { + menuOptRemove.disable(); + } else { + menuOptRemove.enable(); + } + if (hasMoreActions) { + const selections = this.getSelectionModel().getSelections(); + additionalMenuItems.forEach(item => { + const itemCmp = btn.menu.getComponent(item.itemId); + if (selections.length === 0) { + itemCmp.disable(); + } else { + itemCmp.enable(); + } + }); + } + }, + scope: this + } + } + }; + }, /** * @property {Function} getQueryFilterField - Creates the query field component configuration @@ -1192,16 +1323,17 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * @param {String} implementation - Optional, an identifier used to assign grid-specific behavior * @return {Object} */ - ,getQueryFilterField: function(filterSpec = '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) { + // eslint-disable-next-line prefer-destructuring queryValue = filterSpecs[1]; } else { - queryValue = MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : '' ; + queryValue = MODx.request.query ? MODx.util.url.decodeParamValue(MODx.request.query) : ''; } return { xtype: 'textfield', @@ -1213,7 +1345,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ change: { fn: function(cmp, newValue, oldValue) { this.applyGridFilter(cmp); - const usergroupTree = Ext.getCmp('modx-tree-usergroup') + 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, @@ -1223,7 +1355,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ const selectedNode = usergroupTree.getSelectionModel().getSelectedNode(), groupId = MODx.util.tree.getGroupIdFromNode(selectedNode) ; - MODx.util.url.setParams({group: groupId}); + MODx.util.url.setParams({ group: groupId }); } }, scope: this @@ -1243,12 +1375,12 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ fn: this.blur, scope: cmp }); - } - ,scope: this + }, + scope: this } } - } - } + }; + }, /** * @property {Function} getClearFiltersButton - Creates the clear filter button component configuration @@ -1258,7 +1390,7 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ * in the following format: 'filterItemId:relatedBaseParam, [filterItemId:relatedBaseParam,] ...' * @return {Object} */ - ,getClearFiltersButton: function(filters = 'filter-query', dependentFilterResets = null) { + 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 {}; @@ -1286,462 +1418,627 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{ } } } - } + }; if (dependentFilterResets) { config.dependentResets = dependentFilterResets; } return config; - } -}); - -/* local grid */ -MODx.grid.LocalGrid = function(config) { - config = config || {}; + }, - if (config.grouping) { - Ext.applyIf(config,{ - view: new Ext.grid.GroupingView({ - forceFit: true - ,scrollOffset: 0 - ,hideGroupedColumn: config.hideGroupedColumn ? true : false - ,groupTextTpl: config.groupTextTpl || ('{text} ({[values.rs.length]} {[values.rs.length > 1 ? "' - +(config.pluralText || _('records')) + '" : "' - +(config.singleText || _('record'))+'"]})' ) - }) + editorYesNo: function(record = {}) { + Ext.applyIf(record, { + store: new Ext.data.SimpleStore({ + fields: ['d', 'v'], + data: [[_('yes'), true], [_('no'), false]] + }), + displayField: 'd', + valueField: 'v', + mode: 'local', + triggerAction: 'all', + editable: false, + selectOnFocus: false }); - } - if (config.tbar) { - for (var i = 0;i { + const deletableRecord = record[this.permissionsProviderProp].permissions.delete; + if (!record[this.permissionsProviderProp].isProtected && deletableRecord) { + const item = itemIdType === 'string' ? record.data[pk] : parseInt(record.data[pk], 10); + removableItems.push(item); } }); - } + return removableItems; + }, - MODx.grid.LocalGrid.superclass.constructor.call(this,config); - this.addEvents({ - beforeRemoveRow: true, - afterRemoveRow: true - }); - this.on('rowcontextmenu',this._showMenu,this); -}; + /** + * Performs the removal of one or more items selected + * + * @param {String} gridName The object identifier (e.g., 'source', 'context', etc) + * @param {String} removeAction The remove processor to call + * @param {String} pkType Indicates the primary key data type (string or int) + */ + removeSelected: function(gridName, removeAction, pkType = 'int') { + const removableSelections = this.getRemovableItemsFromSelection(pkType); + let + modalText, + actionKey + ; + if (removableSelections.length === 0) { + return false; + } + if (removableSelections.length === 1) { + modalText = _(`${gridName}_remove_confirm`, { name: removableSelections[0] }) || _('confirm_remove'); + } else { + modalText = _(`${gridName}_remove_multiple_confirm`) || _('confirm_remove_multiple'); + } + switch (gridName) { + case 'policy_template': + actionKey = 'templates'; + break; + default: + actionKey = gridName.endsWith('y') + ? `${gridName.substring(0, gridName.length - 1)}ies` + : `${gridName}s` + ; + } + MODx.msg.confirm({ + title: _('selected_remove'), + text: modalText, + url: this.config.url, + params: { + action: removeAction, + [actionKey]: removableSelections.join(',') + }, + listeners: { + success: { + fn: function(response) { + this.getSelectionModel().clearSelections(true); + this.refresh(); + this.fireEvent('afterRemoveRow', { ...response, itemsRemoved: removableSelections }); + }, + scope: this + } + } + }); + return true; + }, -Ext.extend(MODx.grid.LocalGrid,Ext.grid.EditorGridPanel,{ - windows: {} + confirm: function(type, text) { + const + p = { action: type }, + k = this.config.primaryKey || 'id' + ; + p[k] = this.menu.record[k]; - ,_loadStore: function(config) { - if (config.grouping) { - this.store = new Ext.data.GroupingStore({ - data: config.data || [] - ,reader: new Ext.data.ArrayReader({},config.fields || []) - ,sortInfo: config.sortInfo || { - field: config.sortBy || 'name' - ,direction: config.sortDir || 'ASC' + MODx.msg.confirm({ + title: _(type), + text: _(text) || _('confirm_remove'), + url: this.config.url, + params: p, + listeners: { + success: { fn: this.refresh, scope: this } + } + }); + }, + + remove: function(text, action) { + if (this.destroying) { + return MODx.grid.Grid.superclass.remove.apply(this, arguments); + } + const + { record } = this.menu, + saveParams = this.config.saveParams || {}, + primaryKey = this.config.primaryKey || 'id' + ; + text = text || 'confirm_remove'; + Ext.apply(saveParams, { action: action || 'remove' }); + saveParams[primaryKey] = record[primaryKey]; + + if (this.fireEvent('beforeRemoveRow', record)) { + MODx.msg.confirm({ + title: _('warning'), + text: _(text, record), + url: this.config.url, + params: saveParams, + listeners: { + success: { + fn: function() { + this.removeActiveRow(record); + }, + scope: this + } } - ,groupField: config.groupBy || 'name' }); - } else { - this.store = new Ext.data.SimpleStore({ - fields: config.fields - ,data: config.data || [] - }) } - return this.store; - } + }, - ,loadWindow: function(btn,e,win,or) { - var r = this.menu.record; - if (!this.windows[win.xtype]) { - Ext.applyIf(win,{ - scope: this - ,success: this.refresh - ,record: win.blankValues ? {} : r - }); - if (or) { - Ext.apply(win,or); + removeActiveRow: function(record) { + if (this.fireEvent('afterRemoveRow', record)) { + const selection = this.getSelectionModel().getSelected(); + this.getStore().remove(selection); + } + }, + + refresh: function() { + this.getStore().reload(); + }, + + // -*-*-* EVENT HANDLERS *-*-*- + + onClickHandler: function(e) { + const target = e.getTarget(); + if (!target.classList.contains('x-grid-action')) { + return; + } + if (!target.dataset.action) { + return; + } + let actionHandler = `action${target.dataset.action.charAt(0).toUpperCase()}${target.dataset.action.slice(1)}`; + + if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { + actionHandler = target.dataset.action; + if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { + return; } - this.windows[win.xtype] = Ext.ComponentMgr.create(win); } - if (this.windows[win.xtype].setValues && win.blankValues !== true && r != undefined) { - this.windows[win.xtype].setValues(r); + const + record = this.getSelectionModel().getSelected(), + recordIndex = this.store.indexOf(record) + ; + this.menu.record = record.data; + this[actionHandler](record, recordIndex, e); + }, + + onChangePerPage: function(field, newValue) { + if (Ext.isEmpty(newValue)) { + return false; } - this.windows[win.xtype].show(e.target); + newValue = parseInt(newValue, 10); + this.getBottomToolbar().pageSize = newValue; + this.store.load({ + params: { + start: 0, + limit: newValue + } + }); } - ,_loadColumnModel: function() { - if (this.config.columns) { - var c = this.config.columns; - for (var i=0;i${responseStatusCode} ${responseStatusText}
    ` : ''; + } else { + // When php display_error is on OR the error is caught and explicity sent from the MODx class triggering the error, responseText should contain error text or possibly an object containing message text + try { + const responseText = Ext.decode(response.responseText); + // In what scenario will responseText be an object with a message property? + if (responseText && responseText.message) { + output = responseText.message; } + } catch (e) { + output = response.responseText; + } + } + if (output) { + if (MODx.config.debug > 0) { + output = MODx.util.safeHtml(output, '
    ', 'class,colspan,rowspan'); + msg = _('error_grid_get_content_toscreen', { + message: `
    ${output}
    ` + }); + } else { + msg = _('error_grid_get_content_tolog'); + output = Ext.util.Format.stripTags(output).replaceAll('>', '>').replaceAll('<', '<'); + console.error(output); + } + } else { + // With some scenarios, such as when php display_errors = 1 and MODx system setting debug = 0 (reporting off), the reponseText will be empty and the status will be 200 + msg = _('error_grid_get_content_no_msg'); + } + this.getView().emptyText = `
    ${msg}
    `; + this.getView().refresh(false); + }, - /** - * When the field has an editor defined, wrap the (optional) renderer with - * a special renderer that applies a class and tooltip to indicate the - * column is editable. - */ - if (c[i].editor) { - c[i].renderer = this.renderEditableColumn(c[i].renderer); + /** + * Executes auto save of the row after edits are complete and optional success callback + * @param {Ext.Event} e Extended event data including: + * * column + * * row + * * field (name) + * * grid (full grid object) + * * record (full Ext record object including store, data, json, etc.) + * * originalValue + * * value (current) + */ + saveRecord: function(e) { + e.record.data.menu = null; + const p = this.config.saveParams || {}; + Ext.apply(e.record.data, p); + const + data = Ext.util.JSON.encode(e.record.data), + url = this.config.saveUrl || (this.config.url || this.config.connector) + ; + MODx.Ajax.request({ + url: url, + params: { + action: this.config.save_action || 'updateFromGrid', + data: data + }, + listeners: { + success: { + fn: function(response) { + if (this.config.save_callback) { + Ext.callback(this.config.save_callback, this.config.scope || this, [response]); + } + e.record.commit(); + if (!this.config.preventSaveRefresh) { + const gridRefresh = new Ext.util.DelayedTask(() => this.refresh()); + gridRefresh.delay(200); + } + const + /** @var {Object} eventData Plucking only the needed event props to forward in the post-save event */ + eventData = { field: e.field, originalValue: e.originalValue, value: e.value }, + responseData = { ...response, eventData } + ; + this.fireEvent('afterAutoSave', responseData); + }, + scope: this + }, + failure: { + fn: function(response) { + e.record.reject(); + this.fireEvent('afterAutoSave', response); + }, + scope: this } } - this.cm = new Ext.grid.ColumnModel(c); + }); + }, + + /** + * Method executed after a record has been edited/saved inline from within the grid + * + * @param {Object} response - The processor save response object. See modConnectorResponse::outputContent (PHP) + */ + onAfterAutoSave: function(response) { + if (!response.success && response.message === '') { + let msg = ''; + if (response.data.length) { + // We get some data for specific field(s) error but not regular error message + Ext.each(response.data, function(data, index, list) { + msg += (msg !== '' ? '
    ' : '') + data.msg; + }, this); + } + if (Ext.isEmpty(msg)) { + // Still no valid message so far, let's use some fallback + msg = this.autosaveErrorMsg || _('error'); + } + MODx.msg.alert(_('error'), msg); } - } + }, - ,renderEditableColumn: function(renderer) { - return function(value, metaData, record, rowIndex, colIndex, store) { - if (renderer) { - if (typeof renderer.fn === 'function') { - var scope = (renderer.scope) ? renderer.scope : false; - renderer = renderer.fn.bind(scope); - } + encodeModified: function() { + const p = this.getStore().getModifiedRecords(), + rs = {} + ; + for (let i = 0; i < p.length; i++) { + rs[p[i].data[this.config.primaryKey || 'id']] = p[i].data; + } + return Ext.encode(rs); + }, - if (typeof renderer === 'function') { - value = renderer(value, metaData, record, rowIndex, colIndex, store); + makeUrl: function() { + if (Array.isArray(this.config.urlFilters) && this.config.urlFilters.length > 0) { + const s = this.getStore(), + p = { + a: MODx.request.a + }; + if (MODx.request.id) { + p.id = MODx.request.id; + } + if (MODx.request.key) { + p.key = MODx.request.key; + } + for (let i = 0; i < this.config.urlFilters.length; ++i) { + if (Object.hasOwn(s.baseParams, this.config.urlFilters[i]) && s.baseParams[this.config.urlFilters[i]]) { + if (this.config.urlFilters[i] === 'namespace') { + p.ns = s.baseParams[this.config.urlFilters[i]]; + } else { + p[this.config.urlFilters[i]] = s.baseParams[this.config.urlFilters[i]]; + } } } - metaData.css = ['x-editable-column', metaData.css || ''].join(' '); - - return value; + return Ext.urlAppend(MODx.config.manager_url, Ext.urlEncode(p).replace(/%2F/g, '/')); } - } + }, - ,_showMenu: function(g,ri,e) { - e.stopEvent(); - e.preventDefault(); - this.menu.recordIndex = ri; - this.menu.record = this.getStore().getAt(ri).data; - if (!this.getSelectionModel().isSelected(ri)) { - this.getSelectionModel().selectRow(ri); - } - this.menu.removeAll(); - var m = this.getMenu(g,ri); - if (m) { - this.addContextMenuItem(m); - this.menu.showAt(e.xy); + replaceState: function() { + if (typeof window.history.replaceState !== 'undefined' + && Array.isArray(this.config.urlFilters) + && this.config.urlFilters.length > 0 + ) { + window.history.replaceState(this.getStore().baseParams, document.title, this.makeUrl()); } - } + }, - ,getMenu: function() { - return this.menu.record.menu; + /** + * Builds the standard "Creator" column model object. This column displays for + * objects that have built-in system values as well as values installed/entered + * by Extras and/or Users + * @param {String} objectType Identifier for object being worked with + * @returns {Object} The configuration for the "Creator" column + */ + getCreatorColumnConfig: function(objectType) { + return { + header: _('grid_column_creator_header'), + dataIndex: 'creator', + id: `modx-${objectType}--creator`, + width: 70, + align: 'center', + tooltip: _('grid_column_creator_description'), + menuDisabled: true + }; } - ,addContextMenuItem: function(items) { - var l = items.length; - for(var i = 0; i < l; i++) { - var options = items[i]; +}); - if (options == '-') { - this.menu.add('-'); - continue; - } - var h = Ext.emptyFn; - if (options.handler) { - h = eval(options.handler); - if (h && typeof(h) == 'object' && h.xtype) { - h = this.loadWindow.createDelegate(this,[h],true); - } - } else { - h = function(itm) { - var o = itm.options; - var id = this.menu.record.id; - var w = Ext.get('modx_content'); - if (o.confirm) { - Ext.Msg.confirm('',o.confirm,function(e) { - if (e == 'yes') { - var a = Ext.urlEncode(o.params || {action: o.action}); - var s = '?id='+id+'&'+a; - if (w === null) { - location.href = s; - } else { w.dom.src = s; } - } - },this); - } else { - var a = Ext.urlEncode(o.params || {action: o.action}); - var s = '?id='+id+'&'+a; - if (w === null) { - location.href = s; - } else { w.dom.src = s; } - } - }; - } - this.menu.add({ - id: options.id || Ext.id() - ,text: options.text - ,scope: this - ,options: options - ,handler: h - }); +/* + Local Grid, used by: + - FC Profile Set Fields, Regions, and TVs grids (3) + - Element Properties grid + - Element Sources grid + - Source Properties + - Source Access Permissions + - Resource, Resource Groups (security) grid + - User, Access Permissions (user-groups) + - Dashboard Widget, Dashboards grid (modx-grid-dashboard-widget-dashboards) + - Dashboards (modx-grid-dashboard-widget-placements) +*/ +MODx.grid.LocalGrid = function(config = {}) { + Ext.applyIf(config, { + title: '', + enableColumnMove: true, + groupingConfig: { + hideGroupedColumn: config.hideGroupedColumn } - } + }); + MODx.grid.LocalGrid.superclass.constructor.call(this, config); +}; + +Ext.extend(MODx.grid.LocalGrid, MODx.grid.GridBase, { + _loadStore: function() { + if (this.config.grouping) { + this.store = new Ext.data.GroupingStore({ + data: this.config.data || [], + reader: new Ext.data.ArrayReader({}, this.config.fields || []), + sortInfo: this.config.sortInfo || { + field: this.config.sortBy || 'name', + direction: this.config.sortDir || 'ASC' + }, + groupField: this.config.groupBy || 'name' + }); + } else { + this.store = new Ext.data.SimpleStore({ + fields: this.config.fields, + data: this.config.data || [] + }); + } + return this.store; + }, - ,remove: function(config) { + /** + * @override + */ + remove: function(config) { if (this.destroying) { return MODx.grid.LocalGrid.superclass.remove.apply(this, arguments); } - var r = this.getSelectionModel().getSelected(); - if (this.fireEvent('beforeRemoveRow',r)) { - Ext.Msg.confirm(config.title || '',config.text || '',function(e) { - if (e == 'yes') { + const r = this.getSelectionModel().getSelected(); + if (this.fireEvent('beforeRemoveRow', r)) { + Ext.Msg.confirm(config.title || '', config.text || '', function(e) { + if (e === 'yes') { this.getStore().remove(r); - this.fireEvent('afterRemoveRow',r); + this.fireEvent('afterRemoveRow', r); } - },this); + }, this); } - } + }, - ,encode: function() { - var s = this.getStore(); - var ct = s.getCount(); - var rs = this.config.encodeByPk ? {} : []; - var r; - for (var j=0;j' - + '' - + '
      ' - + '' - + '
    • ' - + '
      ' - + '
    ' - + '
    ' - + '', { - compiled: true - }); - } - - ,actionsColumnRenderer: function(value, metaData, record, rowIndex, colIndex, store) { - var actions = this.getActions.apply(this, arguments); - - if (this.config.disableContextMenuAction !== true) { - actions.push({ - text: _('context_menu'), - action: 'contextMenu', - icon: 'gear' - }); - } - - return this._getActionsColumnTpl().apply({ - actions: actions - }); - } - - ,renderLink: function(v,attr) { - var el = new Ext.Element(document.createElement('a')); - el.addClass('x-grid-link'); - el.dom.title = _('edit'); - for (var i in attr) { - el.dom[i] = attr[i]; - } - el.dom.innerHTML = Ext.util.Format.htmlEncode(v); - return el.dom.outerHTML; - } - - ,getActions: function(value, metaData, record, rowIndex, colIndex, store) { - return []; - } - - ,onClick: function(e) { - var target = e.getTarget(); - if (!target.classList.contains('x-grid-action')) return; - if (!target.dataset.action) return; - - var actionHandler = 'action' + target.dataset.action.charAt(0).toUpperCase() + target.dataset.action.slice(1); - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - actionHandler = target.dataset.action; - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - return; - } - } - - var record = this.getSelectionModel().getSelected(); - var recordIndex = this.store.indexOf(record); - this.menu.record = record.data; - - this[actionHandler](record, recordIndex, e); - }, - - actionContextMenu: function(record, recordIndex, e) { - this._showMenu(this, recordIndex, e); - } }); -Ext.reg('grid-local',MODx.grid.LocalGrid); -Ext.reg('modx-grid-local',MODx.grid.LocalGrid); +Ext.reg('grid-local', MODx.grid.LocalGrid); +Ext.reg('modx-grid-local', MODx.grid.LocalGrid); /* grid extensions */ /*! @@ -1767,26 +2064,26 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { * true to toggle selected row(s) between expanded/collapsed when the enter * key is pressed (defaults to true). */ - expandOnEnter : true, + expandOnEnter: true, /** * @cfg {Boolean} expandOnDblClick * true to toggle a row between expanded/collapsed when double clicked * (defaults to true). */ - expandOnDblClick : true, + expandOnDblClick: true, - header : '', - width : 20, - sortable : false, - fixed : true, + header: '', + width: 25, + sortable: false, + fixed: true, hideable: false, - menuDisabled : true, - dataIndex : '', - id : 'expander', - lazyRender : true, - enableCaching : true, + menuDisabled: true, + dataIndex: '', + id: 'expander', + lazyRender: true, + enableCaching: true, - constructor: function(config){ + constructor: function(config) { Ext.apply(this, config); this.addEvents({ @@ -1830,8 +2127,8 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { Ext.ux.grid.RowExpander.superclass.constructor.call(this); - if(this.tpl){ - if(typeof this.tpl == 'string'){ + if (this.tpl) { + if (typeof this.tpl == 'string') { this.tpl = new Ext.Template(this.tpl); } this.tpl.compile(); @@ -1841,39 +2138,40 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { this.bodyContent = {}; }, - getRowClass : function(record, rowIndex, p, ds){ - p.cols = p.cols-1; - var content = this.bodyContent[record.id]; - if(!content && !this.lazyRender){ + getRowClass: function(record, rowIndex, p, ds) { + p.cols -= 1; + let content = this.bodyContent[record.id]; + if (!content && !this.lazyRender) { content = this.getBodyContent(record, rowIndex); } - if(content){ + if (content) { p.body = content; } return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed'; }, - init : function(grid){ + init: function(grid) { this.grid = grid; - var view = grid.getView(); + const view = grid.getView(); view.getRowClass = this.getRowClass.createDelegate(this); view.enableRowBody = true; - grid.on('render', this.onRender, this); grid.on('destroy', this.onDestroy, this); }, // @private onRender: function() { - var grid = this.grid; - var mainBody = grid.getView().mainBody; - mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'}); + const + { grid } = this, + { mainBody } = grid.getView() + ; + mainBody.on('mousedown', this.onMouseDown, this, { delegate: '.x-grid3-row-expander' }); if (this.expandOnEnter) { this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), { - 'enter' : this.onEnter, + enter: this.onEnter, scope: this }); } @@ -1884,7 +2182,7 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { // @private onDestroy: function() { - if(this.keyNav){ + if (this.keyNav) { this.keyNav.disable(); delete this.keyNav; } @@ -1893,8 +2191,8 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { * which means the mainBody won't be available. On the off chance that the plugin * isn't destroyed with the grid, take care of removing the listener. */ - var mainBody = this.grid.getView().mainBody; - if(mainBody){ + const { mainBody } = this.grid.getView(); + if (mainBody) { mainBody.un('mousedown', this.onMouseDown, this); } }, @@ -1904,76 +2202,75 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { }, onEnter: function(e) { - var g = this.grid; - var sm = g.getSelectionModel(); - var sels = sm.getSelections(); - for (var i = 0, len = sels.length; i < len; i++) { - var rowIdx = g.getStore().indexOf(sels[i]); + const g = this.grid, + sm = g.getSelectionModel(), + sels = sm.getSelections(); + for (let i = 0, len = sels.length; i < len; i++) { + const rowIdx = g.getStore().indexOf(sels[i]); this.toggleRow(rowIdx); } }, - getBodyContent : function(record, index){ - if(!this.enableCaching){ + getBodyContent: function(record, index) { + if (!this.enableCaching) { return this.tpl.apply(record.data); } - var content = this.bodyContent[record.id]; - if(!content){ + let content = this.bodyContent[record.id]; + if (!content) { content = this.tpl.apply(record.data); this.bodyContent[record.id] = content; } return content; }, - onMouseDown : function(e, t){ + onMouseDown: function(e, t) { e.stopEvent(); - var row = e.getTarget('.x-grid3-row'); + const row = e.getTarget('.x-grid3-row'); this.toggleRow(row); }, - renderer : function(v, p, record){ + renderer: function(v, p, record) { p.cellAttr = 'rowspan="2"'; return '
     
    '; }, - beforeExpand : function(record, body, rowIndex){ - if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){ - if(this.tpl && this.lazyRender){ + beforeExpand: function(record, body, rowIndex) { + if (this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false) { + if (this.tpl && this.lazyRender) { body.innerHTML = this.getBodyContent(record, rowIndex); } return true; - }else{ - return false; } + return false; }, - toggleRow : function(row){ - if(typeof row == 'number'){ + toggleRow: function(row) { + if (typeof row == 'number') { row = this.grid.view.getRow(row); } this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row); }, - expandRow : function(row){ - if(typeof row == 'number'){ + expandRow: function(row) { + if (typeof row == 'number') { row = this.grid.view.getRow(row); } - var record = this.grid.store.getAt(row.rowIndex); - var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); - if(this.beforeExpand(record, body, row.rowIndex)){ + const record = this.grid.store.getAt(row.rowIndex), + body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row); + if (this.beforeExpand(record, body, row.rowIndex)) { this.state[record.id] = true; Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded'); this.fireEvent('expand', this, record, body, row.rowIndex); } }, - collapseRow : function(row){ - if(typeof row == 'number'){ + collapseRow: function(row) { + if (typeof row == 'number') { row = this.grid.view.getRow(row); } - var record = this.grid.store.getAt(row.rowIndex); - var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); - if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){ + const record = this.grid.store.getAt(row.rowIndex), + body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true); + if (this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false) { this.state[record.id] = false; Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed'); this.fireEvent('collapse', this, record, body, row.rowIndex); @@ -1983,33 +2280,34 @@ Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, { Ext.preg('rowexpander', Ext.ux.grid.RowExpander); -//backwards compat +// backwards compat Ext.grid.RowExpander = Ext.ux.grid.RowExpander; Ext.ns('Ext.ux.grid'); -Ext.ux.grid.CheckColumn = function (a) { +Ext.ux.grid.CheckColumn = function(a) { Ext.apply(this, a); if (!this.id) { - this.id = Ext.id() + this.id = Ext.id(); } - this.renderer = this.renderer.createDelegate(this) + this.renderer = this.renderer.createDelegate(this); }; Ext.ux.grid.CheckColumn.prototype = { - init: function (b) { + init: function(b) { this.grid = b; - this.grid.on('render', function () { - var a = this.grid.getView(); - a.mainBody.on('mousedown', this.onMouseDown, this) + this.grid.on('render', function() { + const a = this.grid.getView(); + a.mainBody.on('mousedown', this.onMouseDown, this); }, this); - this.grid.on('destroy', this.onDestroy, this) - }, onMouseDown: function (e, t) { + this.grid.on('destroy', this.onDestroy, this); + }, + onMouseDown: function(e, t) { this.grid.fireEvent('rowclick'); - if (t.className && t.className.indexOf('x-grid3-cc-' + this.id) != -1) { + if (t.className && t.className.indexOf(`x-grid3-cc-${this.id}`) !== -1) { e.stopEvent(); - var a = this.grid.getView().findRowIndex(t); - var b = this.grid.store.getAt(a); - var sv = b.data[this.dataIndex]; + const a = this.grid.getView().findRowIndex(t), + b = this.grid.store.getAt(a), + sv = b.data[this.dataIndex]; b.set(this.dataIndex, !sv); this.grid.fireEvent('afteredit', { grid: this.grid, @@ -2020,11 +2318,13 @@ Ext.ux.grid.CheckColumn.prototype = { cancel: false }); } - }, renderer: function (v, p, a) { + }, + renderer: function(v, p, a) { p.css += ' x-grid3-check-col-td'; - return '
     
    ' - }, onDestroy: function () { - var mainBody = this.grid.getView().mainBody; + return `
     
    `; + }, + onDestroy: function() { + const { mainBody } = this.grid.getView(); if (mainBody) { mainBody.un('mousedown', this.onMouseDown, this); } @@ -2033,7 +2333,115 @@ Ext.ux.grid.CheckColumn.prototype = { Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn); Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn; -Ext.grid.PropertyColumnModel=function(a,b){var g=Ext.grid,f=Ext.form;this.grid=a;g.PropertyColumnModel.superclass.constructor.call(this,[{header:this.nameText,width:50,sortable:true,dataIndex:'name',id:'name',menuDisabled:true},{header:this.valueText,width:50,resizable:false,dataIndex:'value',id:'value',menuDisabled:true}]);this.store=b;var c=new f.Field({autoCreate:{tag:'select',children:[{tag:'option',value:'true',html:'true'},{tag:'option',value:'false',html:'false'}]},getValue:function(){return this.el.dom.value=='true'}});this.editors={'date':new g.GridEditor(new f.DateField({selectOnFocus:true})),'string':new g.GridEditor(new f.TextField({selectOnFocus:true})),'number':new g.GridEditor(new f.NumberField({selectOnFocus:true,style:'text-align:left;'})),'boolean':new g.GridEditor(c)};this.renderCellDelegate=this.renderCell.createDelegate(this);this.renderPropDelegate=this.renderProp.createDelegate(this)};Ext.extend(Ext.grid.PropertyColumnModel,Ext.grid.ColumnModel,{nameText:'Name',valueText:'Value',dateFormat:'m/j/Y',renderDate:function(a){return a.dateFormat(this.dateFormat)},renderBool:function(a){return a?'true':'false'},isCellEditable:function(a,b){return a==1},getRenderer:function(a){return a==1?this.renderCellDelegate:this.renderPropDelegate},renderProp:function(v){return this.getPropertyName(v)},renderCell:function(a){var b=a;if(Ext.isDate(a)){b=this.renderDate(a)}else if(typeof a=='boolean'){b=this.renderBool(a)}return Ext.util.Format.htmlEncode(b)},getPropertyName:function(a){var b=this.grid.propertyNames;return b&&b[a]?b[a]:a},getCellEditor:function(a,b){var p=this.store.getProperty(b),n=p.data.name,val=p.data.value;if(this.grid.customEditors[n]){return this.grid.customEditors[n]}if(Ext.isDate(val)){return this.editors.date}else if(typeof val=='number'){return this.editors.number}else if(typeof val=='boolean'){return this.editors['boolean']}else{return this.editors.string}},destroy:function(){Ext.grid.PropertyColumnModel.superclass.destroy.call(this);for(var a in this.editors){Ext.destroy(a)}}}); +Ext.grid.PropertyColumnModel = function(a, b) { + const + g = Ext.grid, + f = Ext.form + ; + this.grid = a; + g.PropertyColumnModel.superclass.constructor.call(this, [ + { + header: this.nameText, + width: 50, + sortable: true, + dataIndex: 'name', + id: 'name', + menuDisabled: true + }, + { + header: this.valueText, + width: 50, + resizable: false, + dataIndex: 'value', + id: 'value', + menuDisabled: true + } + ]); + this.store = b; + const c = new f.Field({ + autoCreate: { + tag: 'select', + children: [ + { tag: 'option', value: 'true', html: 'true' }, + { tag: 'option', value: 'false', html: 'false' } + ] + }, + getValue: function() { + // eslint-disable-next-line eqeqeq + return this.el.dom.value == 'true'; + } + }); + this.editors = { + date: new g.GridEditor(new f.DateField({ selectOnFocus: true })), + string: new g.GridEditor(new f.TextField({ selectOnFocus: true })), + number: new g.GridEditor(new f.NumberField({ selectOnFocus: true, style: 'text-align:left;' })), + boolean: new g.GridEditor(c) + }; + this.renderCellDelegate = this.renderCell.createDelegate(this); + this.renderPropDelegate = this.renderProp.createDelegate(this); +}; +Ext.extend(Ext.grid.PropertyColumnModel, Ext.grid.ColumnModel, { + nameText: 'Name', + valueText: 'Value', + dateFormat: 'm/j/Y', + renderDate: function(a) { + return a.dateFormat(this.dateFormat); + }, + renderBool: function(a) { + return a ? 'true' : 'false'; + }, + isCellEditable: function(a, b) { + // eslint-disable-next-line eqeqeq + return a == 1; + }, + getRenderer: function(a) { + // eslint-disable-next-line eqeqeq + return a == 1 ? this.renderCellDelegate : this.renderPropDelegate; + }, + renderProp: function(v) { + return this.getPropertyName(v); + }, + renderCell: function(a) { + let b = a; + if (Ext.isDate(a)) { + b = this.renderDate(a); + } else if (typeof a == 'boolean') { + b = this.renderBool(a); + } + return Ext.util.Format.htmlEncode(b); + }, + getPropertyName: function(a) { + const b = this.grid.propertyNames; + return b && b[a] ? b[a] : a; + }, + getCellEditor: function(a, b) { + const + p = this.store.getProperty(b), + n = p.data.name, + val = p.data.value + ; + if (this.grid.customEditors[n]) { + return this.grid.customEditors[n]; + } + if (Ext.isDate(val)) { + return this.editors.date; + } + if (typeof val == 'number') { + return this.editors.number; + } + if (typeof val == 'boolean') { + return this.editors.boolean; + } + return this.editors.string; + }, + destroy: function() { + Ext.grid.PropertyColumnModel.superclass.destroy.call(this); + // eslint-disable-next-line guard-for-in, no-restricted-syntax + for (const a in this.editors) { + Ext.destroy(a); + } + } +}); /** * MODx JSON Grid @@ -2054,18 +2462,17 @@ Ext.grid.PropertyColumnModel=function(a,b){var g=Ext.grid,f=Ext.form;this.grid=a * * [{name: 'key'}, {name: 'value'}] */ -MODx.grid.JsonGrid = function (config) { - config = config || {}; - this.ident = config.ident || 'jsongrid-mecitem' + Ext.id(); +MODx.grid.JsonGrid = function(config = {}) { + this.ident = config.ident || `jsongrid-mecitem${Ext.id()}`; this.hiddenField = new Ext.form.TextArea({ name: config.hiddenName || config.name, hidden: true }); - this.fieldConfig = config.fieldConfig || [{name: 'key'}, {name: 'value'}]; - this.fieldConfig.push({name: 'id', hidden: true}); + this.fieldConfig = config.fieldConfig || [{ name: 'key' }, { name: 'value' }]; + this.fieldConfig.push({ name: 'id', hidden: true }); this.fieldColumns = []; this.fieldNames = []; - Ext.each(this.fieldConfig, function (el) { + Ext.each(this.fieldConfig, function(el) { this.fieldNames.push(el.name); this.fieldColumns.push({ header: el.header || _(el.name), @@ -2084,8 +2491,8 @@ MODx.grid.JsonGrid = function (config) { scope: this }, keyup: { - fn: function (sb) { - var record = this.getSelectionModel().getSelected(); + fn: function(sb) { + const record = this.getSelectionModel().getSelected(); if (record) { record.set(sb.fieldname, sb.el.dom.value); this.saveValue(); @@ -2103,7 +2510,7 @@ MODx.grid.JsonGrid = function (config) { }); }, this); Ext.applyIf(config, { - id: this.ident + '-json-grid', + id: `${this.ident}-json-grid`, fields: this.fieldNames, autoHeight: true, store: new Ext.data.JsonStore({ @@ -2111,12 +2518,12 @@ MODx.grid.JsonGrid = function (config) { data: this.loadValue(config.value) }), enableDragDrop: true, - ddGroup: this.ident + '-json-grid-dd', + ddGroup: `${this.ident}-json-grid-dd`, labelStyle: 'position: absolute', columns: this.fieldColumns, disableContextMenuAction: true, tbar: ['->', { - text: ' ' + _('add'), + text: ` ${_('add')}`, cls: 'primary-button', handler: this.addElement, scope: this @@ -2128,50 +2535,59 @@ MODx.grid.JsonGrid = function (config) { } } }); - MODx.grid.JsonGrid.superclass.constructor.call(this, config) + MODx.grid.JsonGrid.superclass.constructor.call(this, config); }; Ext.extend(MODx.grid.JsonGrid, MODx.grid.LocalGrid, { - getMenu: function () { - var m = []; - m.push({ + getMenu: function() { + const menu = []; + menu.push({ text: _('remove'), handler: this.removeElement }); - return m; + return menu; }, - getActions: function () { + + /** + * @override + */ + getActions: function() { return [{ action: 'removeElement', icon: 'trash-o', text: _('remove') - }] + }]; }, - addElement: function () { - var ds = this.getStore(); - var row = {}; - Ext.each(this.fieldNames, function (fieldname) { + + addElement: function() { + const ds = this.getStore(), + row = {}; + Ext.each(this.fieldNames, function(fieldname) { row[fieldname] = ''; }); - row['id'] = this.getStore().getCount(); + row.id = this.getStore().getCount(); + // eslint-disable-next-line new-cap this.getStore().insert(this.getStore().getCount(), new ds.recordType(row)); this.getView().refresh(); this.getSelectionModel().selectRow(0); }, - removeElement: function () { - Ext.Msg.confirm(_('remove') || '', _('confirm_remove') || '', function (e) { + + removeElement: function() { + Ext.Msg.confirm(_('remove') || '', _('confirm_remove') || '', function(e) { if (e === 'yes') { - var ds = this.getStore(); - var rows = this.getSelectionModel().getSelections(); + const ds = this.getStore(), + rows = this.getSelectionModel().getSelections(); if (!rows.length) { return false; } - for (var i = 0; i < rows.length; i++) { - var id = rows[i].id; - var index = ds.findBy(function (record) { - if (record.id === id) { - return true; - } - }); + for (let i = 0; i < rows.length; i++) { + const + { id } = rows[i], + index = ds.findBy(function(record) { + if (record.id === id) { + return true; + } + }) + ; ds.removeAt(index); } this.getView().refresh(); @@ -2179,20 +2595,22 @@ Ext.extend(MODx.grid.JsonGrid, MODx.grid.LocalGrid, { } }, this); }, - renderListener: function (grid) { + + renderListener: function(grid) { + // eslint-disable-next-line no-new new Ext.dd.DropTarget(grid.container, { copy: false, - ddGroup: this.ident + '-json-grid-dd', - notifyDrop: function (dd, e, data) { - var ds = grid.store; - var sm = grid.getSelectionModel(); - var rows = sm.getSelections(); + ddGroup: `${this.ident}-json-grid-dd`, + notifyDrop: function(dd, e, data) { + const ds = grid.store, + sm = grid.getSelectionModel(), + rows = sm.getSelections(), - var dragData = dd.getDragData(e); + dragData = dd.getDragData(e); if (dragData) { - var cindex = dragData.rowIndex; - if (typeof (cindex) !== "undefined") { - for (var i = 0; i < rows.length; i++) { + const cindex = dragData.rowIndex; + if (typeof (cindex) !== 'undefined') { + for (let i = 0; i < rows.length; i++) { ds.remove(ds.getById(rows[i].id)); } ds.insert(cindex, data.selections); @@ -2206,22 +2624,24 @@ Ext.extend(MODx.grid.JsonGrid, MODx.grid.LocalGrid, { this.add(this.hiddenField); this.saveValue(); }, - loadValue: function (value) { + + loadValue: function(value) { value = Ext.util.JSON.decode(value); if (value && Array.isArray(value)) { - Ext.each(value, function (record, idx) { - value[idx]['id'] = idx; + Ext.each(value, function(record, idx) { + value[idx].id = idx; }); } else { value = []; } return value; }, - saveValue: function () { - var value = []; - Ext.each(this.getStore().getRange(), function (record) { - var row = {}; - Ext.each(this.fieldNames, function (fieldname) { + + saveValue: function() { + const value = []; + Ext.each(this.getStore().getRange(), function(record) { + const row = {}; + Ext.each(this.fieldNames, function(fieldname) { if (fieldname !== 'id') { row[fieldname] = record.data[fieldname]; } @@ -2229,43 +2649,6 @@ Ext.extend(MODx.grid.JsonGrid, MODx.grid.LocalGrid, { value.push(row); }, this); this.hiddenField.setValue(Ext.util.JSON.encode(value)); - }, - _getActionsColumnTpl: function () { - return new Ext.XTemplate('' - + '' - + '
      ' - + '' - + '
    • ' - + '
      ' - + '
    ' - + '
    ' - + '
    ', { - compiled: true - }); - }, - actionsColumnRenderer: function (value, metaData, record, rowIndex, colIndex, store) { - return this._getActionsColumnTpl().apply({ - actions: this.getActions() - }); - }, - onClick: function (e) { - var target = e.getTarget(); - if (!target.classList.contains('x-grid-action')) return; - if (!target.dataset.action) return; - - var actionHandler = 'action' + target.dataset.action.charAt(0).toUpperCase() + target.dataset.action.slice(1); - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - actionHandler = target.dataset.action; - if (!this[actionHandler] || (typeof this[actionHandler] !== 'function')) { - return; - } - } - - var record = this.getSelectionModel().getSelected(); - var recordIndex = this.store.indexOf(record); - this.menu.record = record.data; - - this[actionHandler](record, recordIndex, e); } }); Ext.reg('grid-json', MODx.grid.JsonGrid); diff --git a/manager/assets/modext/widgets/core/modx.grid.local.property.js b/manager/assets/modext/widgets/core/modx.grid.local.property.js index 9b5b7a3235f..c06a7f61632 100644 --- a/manager/assets/modext/widgets/core/modx.grid.local.property.js +++ b/manager/assets/modext/widgets/core/modx.grid.local.property.js @@ -1,119 +1,142 @@ -MODx.grid.LocalProperty = function(config) { - config = config || {}; - Ext.applyIf(config,{ - dynProperty: 'xtype' - ,dynField: 'value' - ,propertyRecord: [{name: 'name'},{name: 'value'}] - ,data: [] +MODx.grid.LocalProperty = function(config = {}) { + Ext.applyIf(config, { + dynProperty: 'xtype', + dynField: 'value', + propertyRecord: [ + { name: 'name' }, + { name: 'value' } + ], + data: [] }); - MODx.grid.LocalProperty.superclass.constructor.call(this,config); + MODx.grid.LocalProperty.superclass.constructor.call(this, config); this.propRecord = Ext.data.Record.create(config.propertyRecord); }; -Ext.extend(MODx.grid.LocalProperty,MODx.grid.LocalGrid,{ - onCellDblClick: function(g,ri,ci,e) { - var cm = this.getColumnModel(); - if (cm.getColumnId(ci) == this.config.dynField) { +Ext.extend(MODx.grid.LocalProperty, MODx.grid.LocalGrid, { + onCellDblClick: function(grid, rowIndex, colIndex, e) { + const colModel = this.getColumnModel(); + if (colModel.getColumnId(colIndex) === this.config.dynField) { e.preventDefault(); - var r = this.getStore().getAt(ri).data; - this.initEditor(cm,ci,ri,r); - this.startEditing(ri,ci); + const record = this.getStore().getAt(rowIndex).data; + this.initEditor(colModel, colIndex, rowIndex, record); + this.startEditing(rowIndex, colIndex); } - } + }, - ,initEditor: function(cm,ci,ri,r) { - cm.setEditable(ci,true); - var xtype = this.config.dynProperty; - var o; - if (r[xtype] == 'list') { - o = this.createCombo(r); + initEditor: function(colModel, colIndex, rowIndex, record) { + colModel.setEditable(colIndex, true); + const fieldType = record[this.config.dynProperty]; + let fieldCmp; + if (fieldType === 'list') { + fieldCmp = this.createCombo(record); } else { - var z = {}; - z[xtype] = r[xtype] || 'textfield'; + const config = {}; + config[this.config.dynProperty] = fieldType || 'textfield'; try { - o = Ext.ComponentMgr.create(z); + fieldCmp = Ext.ComponentMgr.create(config); } catch (e) { - z[xtype] = 'textfield'; - o = MODx.load(z); + config[this.config.dynProperty] = 'textfield'; + fieldCmp = MODx.load(config); } } - var ed = new Ext.grid.GridEditor(o); - cm.setEditor(ci,ed); - return ed; - } + const editor = new Ext.grid.GridEditor(fieldCmp); + colModel.setEditor(colIndex, editor); + return editor; + }, + + renderDynField: function(value, metaData, record, rowIndex, colIndex, store, grid) { + const + { data } = record, + fieldType = data[this.config.dynProperty], + encodedValue = Ext.util.Format.htmlEncode(value), + rendererArgs = [encodedValue, metaData, record, rowIndex, colIndex, store, grid] + ; + let renderFn; + + metaData.css = this.setEditableCellClasses(record); - ,renderDynField: function(v,md,rec,ri,ci,s,g) { - var r = s.getAt(ri).data; - var f,idx; - var oz = v; - var xtype = this.config.dynProperty; - if (!r[xtype] || r[xtype] == 'combo-boolean') { - f = MODx.grid.Grid.prototype.rendYesNo; - return this.renderEditableColumn(f)(Ext.util.Format.htmlEncode(v),md,rec,ri,ci,s,g); - } else if (r[xtype] === 'datefield') { - f = Ext.util.Format.dateRenderer('Y-m-d'); - return this.renderEditableColumn(f)(Ext.util.Format.htmlEncode(v),md,rec,ri,ci,s,g); - } else if (r[xtype] === 'password') { - f = this.rendPassword; - return this.renderEditableColumn(f)(Ext.util.Format.htmlEncode(v),md,rec,ri,ci,s,g); - } else if (r[xtype].substr(0,5) == 'combo' || r[xtype] == 'list' || r[xtype].substr(0,9) == 'modx-combo') { - var cm = g.getColumnModel(); - var ed = cm.getCellEditor(ci,ri); - var cb; - if (!ed) { - r.xtype = r.xtype || 'combo-boolean'; - cb = this.createCombo(r); - ed = new Ext.grid.GridEditor(cb); - cm.setEditor(ci,ed); - } else if (ed && ed.field && ed.field.xtype == 'modx-combo') { - cb = ed.field; + if (!fieldType || fieldType === 'combo-boolean') { + renderFn = MODx.grid.Grid.prototype.rendYesNo; + return this.renderEditableColumn(renderFn)(...rendererArgs); + } + if (fieldType === 'datefield') { + renderFn = Ext.util.Format.dateRenderer('Y-m-d'); + return this.renderEditableColumn(renderFn)(...rendererArgs); + } + if (fieldType === 'password') { + renderFn = this.rendPassword; + return this.renderEditableColumn(renderFn)(...rendererArgs); + } + if (fieldType.includes('combo') || fieldType === 'list') { + const colModel = grid.getColumnModel(); + let + editor = colModel.getCellEditor(colIndex, rowIndex), + comboCmp + ; + if (!editor) { + data.xtype = data.xtype || 'combo-boolean'; + comboCmp = this.createCombo(data); + editor = new Ext.grid.GridEditor(comboCmp); + colModel.setEditor(colIndex, editor); + } else if (editor?.field?.xtype === 'modx-combo') { + comboCmp = editor.field; } - if (r[xtype] != 'list') { - f = Ext.util.Format.comboRenderer(ed.field); - return this.renderEditableColumn(f)(Ext.util.Format.htmlEncode(v),md,rec,ri,ci,s,g); - } else if (cb) { - idx = cb.getStore().find(cb.valueField,v); - rec = cb.getStore().getAt(idx); - if (rec) { - oz = rec.get(cb.displayField); - } else { - oz = v; + if (fieldType !== 'list') { + renderFn = Ext.util.Format.comboRenderer(editor.field); + return this.renderEditableColumn(renderFn)(...rendererArgs); + } + if (comboCmp) { + const + valueIndex = comboCmp.getStore().find(comboCmp.valueField, value), + comboRecord = comboCmp.getStore().getAt(valueIndex) + ; + if (comboRecord) { + const displayValue = comboRecord.get(comboCmp.displayField); + // override args in upper scope with this combo's value and record + rendererArgs[0] = Ext.util.Format.htmlEncode(displayValue); + rendererArgs[2] = comboRecord; } } } + return this.renderEditableColumn()(...rendererArgs); + }, - return this.renderEditableColumn()(Ext.util.Format.htmlEncode(oz),md,rec,ri,ci,s,g); - } - - ,createCombo: function(p) { - var obj; + createCombo: function(record) { + let combo; try { - obj = Ext.ComponentMgr.create({ xtype: r.xtype, id: Ext.id() }); - } catch(e) { + combo = Ext.ComponentMgr.create({ + xtype: record.xtype, + id: Ext.id() + }); + } catch (e) { try { - var flds = p.options; - var data = []; - for (var i=0;i data.push([option.name, option.value, option.text])); + + combo = MODx.load({ + xtype: 'modx-combo', + store: new Ext.data.SimpleStore({ + fields: ['d', 'v', 't'], + data: data + }), + displayField: 'd', + valueField: 'v', + mode: 'local', + triggerAction: 'all', + editable: false, + selectOnFocus: false, + preventRender: true }); } catch (e2) { - obj = Ext.ComponentMgr.create({ xtype: 'combo-boolean', id: Ext.id() }); + combo = Ext.ComponentMgr.create({ + xtype: 'combo-boolean', + id: Ext.id() + }); } } - return obj; + return combo; } }); -Ext.reg('grid-local-property',MODx.grid.LocalProperty); +Ext.reg('grid-local-property', MODx.grid.LocalProperty); diff --git a/manager/assets/modext/widgets/element/modx.grid.element.properties.js b/manager/assets/modext/widgets/element/modx.grid.element.properties.js index 92e04f45f6b..dca956a366d 100644 --- a/manager/assets/modext/widgets/element/modx.grid.element.properties.js +++ b/manager/assets/modext/widgets/element/modx.grid.element.properties.js @@ -1,238 +1,305 @@ -MODx.panel.ElementProperties = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-element-properties' - ,title: _('properties') - ,header: false - ,defaults: { collapsible: false ,autoHeight: true ,border: false } - ,layout: 'form' - ,items: [{ - html: '

    '+_('element_properties_desc')+'

    ' - ,itemId: 'desc-properties' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-element-properties' - ,cls:'main-wrapper' - ,id: 'modx-grid-element-properties' - ,itemId: 'grid-properties' - ,autoHeight: true - ,border: true - ,panel: config.elementPanel - ,elementId: config.elementId - ,elementType: config.elementType - },{ - layout: 'form' - ,labelAlign: 'top' - ,border: false - ,cls: 'main-wrapper' - ,items: [{ - xtype: 'xcheckbox' - ,boxLabel: _('property_preprocess') - ,description: MODx.expandHelp ? '' : _('property_preprocess_msg') - ,name: 'property_preprocess' - ,id: 'modx-element-property-preprocess' - ,inputValue: true - ,hideLabel: true - ,checked: config.record.property_preprocess || 0 - ,listeners: { - 'check':{fn:function() {Ext.getCmp(this.config.elementPanel).markDirty();},scope:this} +MODx.panel.ElementProperties = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-element-properties', + title: _('properties'), + header: false, + defaults: { + collapsible: false, + autoHeight: true, + border: false + }, + layout: 'form', + items: [{ + html: `

    ${_('element_properties_desc')}

    `, + itemId: 'desc-properties', + xtype: 'modx-description' + }, { + xtype: 'modx-grid-element-properties', + cls: 'main-wrapper', + id: 'modx-grid-element-properties', + itemId: 'grid-properties', + autoHeight: true, + border: true, + panel: config.elementPanel, + elementId: config.elementId, + elementType: config.elementType + }, { + layout: 'form', + labelAlign: 'top', + border: false, + cls: 'main-wrapper', + items: [{ + xtype: 'xcheckbox', + boxLabel: _('property_preprocess'), + description: MODx.expandHelp ? '' : _('property_preprocess_msg'), + name: 'property_preprocess', + inputValue: true, + hideLabel: true, + checked: config.record.property_preprocess || 0, + listeners: { + check: { + fn: function() { + Ext.getCmp(this.config.elementPanel).markDirty(); + }, + scope: this + } } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-element-property-preprocess' - ,html: _('property_preprocess_msg') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_preprocess_msg'), + cls: 'desc-under' }] }] }); - MODx.panel.ElementProperties.superclass.constructor.call(this,config); + MODx.panel.ElementProperties.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.ElementProperties,MODx.Panel); -Ext.reg('modx-panel-element-properties',MODx.panel.ElementProperties); - +Ext.extend(MODx.panel.ElementProperties, MODx.Panel); +Ext.reg('modx-panel-element-properties', MODx.panel.ElementProperties); -MODx.grid.ElementProperties = function(config) { - config = config || {}; +MODx.grid.ElementProperties = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {desc_trans}

    ' ) }); - Ext.applyIf(config,{ - title: _('properties') - ,id: 'modx-grid-element-properties' - ,maxHeight: 300 - ,fields: ['name','desc','xtype','options','value','lexicon','overridden','desc_trans','area','area_trans'] - ,autoExpandColumn: 'value' - ,sortBy: 'name' - ,anchor: '100%' - ,sm: new Ext.grid.RowSelectionModel({singleSelect:false}) - ,loadMask: true - ,lockProperties: true - ,plugins: [this.exp] - ,grouping: true - ,groupBy: 'area_trans' - ,singleText: _('property') - ,pluralText: _('properties') - ,columns: [this.exp,{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - ,renderer: this._renderName - },{ - header: _('type') - ,dataIndex: 'xtype' - ,width: 100 - ,renderer: this._renderType - ,sortable: true - },{ - header: _('value') - ,dataIndex: 'value' - ,id: 'value' - ,width: 250 - ,renderer: this.renderDynField.createDelegate(this,[this],true) - ,sortable: true - },{ - header: _('area') - ,dataIndex: 'area_trans' - ,id: 'area' - ,width: 150 - ,sortable: true - ,hidden: true - }] - ,tbar: [{ - text: _('property_create') - ,id: 'modx-btn-property-create' - ,handler: this.create - ,scope: this - ,disabled: true - },{ - text: _('properties_default_locked') - ,id: 'modx-btn-propset-lock' - ,handler: this.togglePropertiesLock - ,enableToggle: true - ,pressed: true - ,disabled: MODx.perm.unlock_element_properties ? false : true - ,scope: this - },'->',{ - xtype: 'modx-combo-property-set' - ,id: 'modx-combo-property-set' - ,baseParams: { - action: 'Element/PropertySet/GetList' - ,showAssociated: true - ,elementId: config.elementId - ,elementType: config.elementType - } - ,value: 0 - ,listeners: { - 'select': {fn:this.changePropertySet,scope:this} + Ext.applyIf(config, { + title: _('properties'), + id: 'modx-grid-element-properties', + maxHeight: 300, + fields: [ + 'name', + 'desc', + 'xtype', + 'options', + 'value', + 'lexicon', + 'overridden', + 'desc_trans', + 'area', + 'area_trans', + 'permissions' + ], + autoExpandColumn: 'value', + sortBy: 'name', + anchor: '100%', + sm: new Ext.grid.RowSelectionModel({ + singleSelect: false + }), + loadMask: true, + lockProperties: true, + plugins: [this.exp], + grouping: true, + groupBy: 'area_trans', + singleText: _('property'), + pluralText: _('properties'), + columns: [this.exp, { + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + renderer: this._renderName + }, { + header: _('type'), + dataIndex: 'xtype', + width: 100, + renderer: this._renderType, + sortable: true + }, { + header: _('value'), + dataIndex: 'value', + id: 'value', + width: 250, + renderer: this.renderDynField.createDelegate(this, [this], true), + sortable: true + }, { + header: _('area'), + dataIndex: 'area_trans', + id: 'area', + width: 150, + sortable: true, + hidden: true + }], + tbar: [{ + text: _('property_create'), + id: 'modx-btn-property-create', + handler: this.create, + scope: this, + disabled: true + }, { + text: _('properties_default_locked'), + id: 'modx-btn-propset-lock', + handler: this.togglePropertiesLock, + enableToggle: true, + pressed: true, + disabled: !MODx.perm.unlock_element_properties, + scope: this + }, '->', { + xtype: 'modx-combo-property-set', + id: 'modx-combo-property-set', + baseParams: { + action: 'Element/PropertySet/GetList', + showAssociated: true, + elementId: config.elementId, + elementType: config.elementType, + combo: true + }, + value: 0, + listeners: { + select: { + fn: this.changePropertySet, + scope: this + } } - },{ - text: _('propertyset_add') - ,handler: this.addPropertySet - ,scope: this - },{ - text: _('propertyset_save') - ,cls: 'primary-button' - ,handler: this.save - ,scope: this - ,hidden: MODx.request.id ? false : true - }] - ,bbar: [{ - text: _('property_revert_all') - ,id: 'modx-btn-property-revert-all' - ,handler: this.revertAll - ,scope:this - ,disabled: true - },{ - text: _('import') - ,handler: this.importProperties - ,scope: this - },{ - text: _('export') - ,handler: this.exportProperties - ,scope: this - }] - ,collapseFirst: false - ,tools: [{ - id: 'plus' - ,qtip: _('expand_all') - ,handler: this.expandAll - ,scope: this - },{ - id: 'minus' - ,hidden: true - ,qtip: _('collapse_all') - ,handler: this.collapseAll - ,scope: this + }, { + text: _('propertyset_add'), + id: 'modx-btn-property-set-add', + handler: this.addPropertySet, + scope: this + }, { + text: _('propertyset_save'), + id: 'modx-btn-property-set-save', + cls: 'primary-button', + handler: this.save, + scope: this, + hidden: !MODx.request.id + }], + bbar: [{ + text: _('property_revert_all'), + id: 'modx-btn-property-revert-all', + handler: this.revertAll, + scope: this, + disabled: true + }, { + text: _('import'), + id: 'modx-btn-property-import', + handler: this.importProperties, + scope: this + }, { + text: _('export'), + handler: this.exportProperties, + scope: this + }], + collapseFirst: false, + tools: [{ + id: 'plus', + qtip: _('expand_all'), + handler: this.expandAll, + scope: this + }, { + id: 'minus', + hidden: true, + qtip: _('collapse_all'), + handler: this.collapseAll, + scope: this }] }); - MODx.grid.ElementProperties.superclass.constructor.call(this,config); - this.on('afteredit', this.propertyChanged, this); - this.on('afterRemoveRow', this.propertyChanged, this); - this.on('render',function() { - this.mask = new Ext.LoadMask(this.getEl()); - },this); + MODx.grid.ElementProperties.superclass.constructor.call(this, config); - if (this.config.lockProperties) { - this.on('render',function() { - this.lockMask = MODx.load({ - xtype: 'modx-lockmask' - ,el: this.getGridEl() - ,msg: _('properties_default_locked') - }); - this.lockMask.toggle(); - },this); - } + // Omitting 'revert' action, as it is effectively the same as 'edit' + this.gridMenuActions = ['edit', 'delete']; + + // Note there are currently no action-specific permissions for Dashboards + this.setUserCanEdit(['edit_propertyset', 'save_propertyset']); + this.setUserCanCreate(['new_propertyset', 'save_propertyset']); + this.setUserCanDelete(['delete_propertyset']); + this.setShowActionsMenu(); + + this.on({ + render: grid => { + const buttonsToHide = []; + this.mask = new Ext.LoadMask(this.getEl()); + if (this.config.lockProperties) { + this.lockMask = MODx.load({ + xtype: 'modx-lockmask', + el: this.getGridEl(), + msg: _('properties_default_locked') + }); + this.lockMask.toggle(); + } + if (!this.userCanCreate) { + buttonsToHide.push('modx-btn-property-set-add', 'modx-btn-property-import'); + } + if (!this.userCanEdit) { + buttonsToHide.push('modx-btn-property-create', 'modx-btn-property-revert-all'); + if (!this.userCanCreate) { + buttonsToHide.push('modx-btn-property-set-save'); + } + } + if ( + !MODx.perm.unlock_element_properties + && !this.id === 'modx-grid-element-properties' + ) { + buttonsToHide.push('modx-btn-propset-lock'); + } + if (buttonsToHide.length > 0) { + buttonsToHide.forEach(btnId => Ext.getCmp(btnId)?.hide()); + } + }, + beforeedit: e => { + if (e.record[this.permissionsProviderProp].isProtected || !this.userCanEditRecord(e.record)) { + return false; + } + }, + afteredit: e => { + this.propertyChanged(); + }, + afterRemoveRow: record => { + this.propertyChanged(); + } + }); }; -Ext.extend(MODx.grid.ElementProperties,MODx.grid.LocalProperty,{ - defaultProperties: [] +Ext.extend(MODx.grid.ElementProperties, MODx.grid.LocalProperty, { + defaultProperties: [], - ,onDirty: function() { + onDirty: function() { if (this.config.panel) { Ext.getCmp(this.config.panel).fireEvent('fieldChange'); } - } + }, - ,_renderType: function(v,md,rec,ri) { - switch (v) { - case 'combo-boolean': return _('yesno'); break; - case 'datefield': return _('date'); break; - case 'numberfield': return _('integer'); break; - case 'file': return _('file'); break; - case 'color': return _('color'); break; + _renderType: function(value, metaData, record, rowIndex) { + switch (value) { + case 'combo-boolean': return _('yesno'); + case 'datefield': return _('date'); + case 'numberfield': return _('integer'); + case 'file': return _('file'); + case 'color': return _('color'); + // no default } - return _(v); - } - ,_renderName: function(v,md,rec,ri) { - switch (rec.data.overridden) { + return _(value); + }, + + _renderName: function(value, metaData, record, rowIndex) { + switch (record.data.overridden) { case 1: - return ''+v+''; break; + return `${value}`; case 2: - return ''+v+''; + return `${value}`; default: - return ''+v+''; + return `${value}`; } - } + }, - ,save: function() { - var d = this.encode(); - var cb = Ext.getCmp('modx-combo-property-set'); - if (!cb) { + save: function() { + const + data = this.encode(), + propSetCombo = Ext.getCmp('modx-combo-property-set') + ; + if (!propSetCombo) { this.getStore().commitChanges(); this.onDirty(); return true; } - var p = { - action: 'Element/PropertySet/UpdateFromElement' - ,id: cb.getValue() - ,data: d + const params = { + action: 'Element/PropertySet/UpdateFromElement', + id: propSetCombo.getValue(), + data: data }; if (this.config.elementId) { - Ext.apply(p,{ - elementId: this.config.elementId - ,elementType: this.config.elementType + Ext.apply(params, { + elementId: this.config.elementId, + elementType: this.config.elementType }); } try { @@ -240,361 +307,425 @@ Ext.extend(MODx.grid.ElementProperties,MODx.grid.LocalProperty,{ this.mask = new Ext.LoadMask(this.getEl()); } if (this.mask) { this.mask.show(); } - } catch (e) { } + // eslint-disable-next-line no-empty + } catch (e) {} MODx.Ajax.request({ - url: MODx.config.connector_url - ,params: p - ,listeners: { - 'success': {fn:function(r) { - this.getStore().commitChanges(); - this.changePropertySet(cb); - this.onDirty(); - if (this.mask) { this.mask.hide(); } - MODx.msg.status({ - title: _('success') - ,message: _('save_successful') - ,dontHide: r.message != '' ? true : false - }); - },scope:this} + url: MODx.config.connector_url, + params: params, + listeners: { + success: { + fn: function(response) { + this.getStore().commitChanges(); + this.changePropertySet(propSetCombo); + this.onDirty(); + if (this.mask) { this.mask.hide(); } + MODx.msg.status({ + title: _('success'), + message: _('save_successful'), + dontHide: !Ext.isEmpty(response.message) + }); + }, + scope: this + } } }); - } + }, - ,addPropertySet: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-element-property-set-add' - ,record: { - elementId: this.config.elementId != 0 ? this.config.elementId : '' - ,elementType: this.config.elementType - } - ,listeners: { - 'success': {fn:function(o) { - var cb = Ext.getCmp('modx-combo-property-set'); - cb.store.reload({ - callback: function() { - cb.setValue(o.a.result.object.id); - this.changePropertySet(cb); - } - ,scope: this - }); - this.onDirty(); - },scope:this} + addPropertySet: function(btn, e) { + this.loadWindow(btn, e, { + xtype: 'modx-window-element-property-set-add', + record: { + elementId: this.config.elementId !== 0 ? this.config.elementId : '', + elementType: this.config.elementType + }, + listeners: { + success: { + fn: function(response) { + const propSetCombo = Ext.getCmp('modx-combo-property-set'); + propSetCombo.store.reload({ + callback: function() { + propSetCombo.setValue(response.a.result.object.id); + this.changePropertySet(propSetCombo); + }, + scope: this + }); + this.onDirty(); + }, + scope: this + } } }); - } + }, - ,togglePropertiesLock: function() { - var ps = Ext.getCmp('modx-combo-property-set').getValue(); - if (ps == 0 || ps == _('default')) { - Ext.getCmp('modx-btn-propset-lock').setText(this.lockMask.locked ? _('properties_default_unlocked') : _('properties_default_locked')); + togglePropertiesLock: function() { + const propSetId = Ext.getCmp('modx-combo-property-set').getValue(); + if (propSetId === 0 || propSetId === _('default')) { + Ext.getCmp('modx-btn-propset-lock').setText(this.lockMask.locked + ? _('properties_default_unlocked') + : _('properties_default_locked')) + ; this.lockMask.toggle(); this.toggleButtons(this.lockMask.locked); } - } + }, - ,toggleButtons: function(v) { - var btn = Ext.getCmp('modx-btn-property-create'); + toggleButtons: function(value) { + const btn = Ext.getCmp('modx-btn-property-create'); if (btn) { - Ext.getCmp('modx-btn-property-create').setDisabled(v); - Ext.getCmp('modx-btn-property-revert-all').setDisabled(v); + Ext.getCmp('modx-btn-property-create').setDisabled(value); + Ext.getCmp('modx-btn-property-revert-all').setDisabled(value); } - } + }, - ,changePropertySet: function(cb) { - var ps = cb.getValue(); - var lockbtn = Ext.getCmp('modx-btn-propset-lock'); - if (ps == 0 || ps == _('default')) { + changePropertySet: function(propSetCombo) { + const + propSetId = propSetCombo.getValue(), + lockbtn = Ext.getCmp('modx-btn-propset-lock') + ; + if (propSetId === 0 || propSetId === _('default')) { if (MODx.perm.unlock_element_properties) { - if (lockbtn) { lockbtn.setDisabled(false); } + if (lockbtn) { + lockbtn.setDisabled(false); + } } if (this.lockMask && this.lockMask.locked) { this.lockMask.show(); this.toggleButtons(true); } } else { - if (lockbtn) { lockbtn.setDisabled(true); } - if (this.lockMask) this.lockMask.hide(); + if (lockbtn) { + lockbtn.setDisabled(true); + } + if (this.lockMask) { + this.lockMask.hide(); + } this.toggleButtons(false); } MODx.Ajax.request({ - url: MODx.config.connector_url - ,params: { - action: 'Element/PropertySet/Get' - ,id: ps - ,elementId: this.config.elementId - ,elementType: this.config.elementType - } - ,listeners: { - 'success': {fn:function(r) { - var s = this.getStore(); - var data = Ext.decode(r.object.data); - s.removeAll(); - s.loadData(data); - },scope:this} + url: MODx.config.connector_url, + params: { + action: 'Element/PropertySet/Get', + id: propSetId, + elementId: this.config.elementId, + elementType: this.config.elementType + }, + listeners: { + success: { + fn: function(response) { + const + store = this.getStore(), + data = Ext.decode(response.object.data) + ; + store.removeAll(); + store.loadData(data); + }, + scope: this + } } }); - } + }, - ,create: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-element-property-create' - ,blankValues: true - ,listeners: { - 'success': {fn:function(r) { - - var rec = new this.propRecord({ - name: r.name - ,desc: r.desc - ,desc_trans: r.desc - ,xtype: r.xtype - ,options: r.options - ,value: r.value - ,lexicon: r.lexicon - ,overridden: this.isDefaultPropSet() ? 0 : 2 - ,area: r.area - ,area_trans: r.area - }); - this.getStore().add(rec); - this.propertyChanged(); - this.onDirty(); - },scope:this} + create: function(btn, e) { + this.loadWindow(btn, e, { + xtype: 'modx-window-element-property-create', + blankValues: true, + listeners: { + success: { + fn: function(response) { + const record = new this.propRecord({ + name: response.name, + desc: response.desc, + desc_trans: response.desc, + xtype: response.xtype, + options: response.options, + value: response.value, + lexicon: response.lexicon, + overridden: this.isDefaultPropSet() ? 0 : 2, + area: response.area, + area_trans: response.area + }); + this.getStore().add(record); + this.propertyChanged(); + this.onDirty(); + }, + scope: this + } } }); - } + }, - ,update: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-element-property-update' - ,record: this.menu.record - ,listeners: { - 'success': {fn:function(r) { - var def = this.isDefaultPropSet(); - var s = this.getStore(); - var rec = s.getAt(this.menu.recordIndex); - rec.set('name',r.name); - rec.set('desc',r.desc); - rec.set('desc_trans', r.desc); - rec.set('xtype',r.xtype); - rec.set('options',r.options); - rec.set('value',r.value); - rec.set('lexicon',r.lexicon); - rec.set('overridden',r.overridden == 2 ? 2 : (!def ? 1 : 0)); - rec.set('area',r.area); - rec.set('area_trans',r.area); - this.getView().refresh(); - this.onDirty(); - },scope:this} + update: function(btn, e) { + this.loadWindow(btn, e, { + xtype: 'modx-window-element-property-update', + record: this.menu.record, + listeners: { + success: { + fn: function(response) { + const + isDefaultSet = this.isDefaultPropSet(), + store = this.getStore(), + record = store.getAt(this.menu.recordIndex) + ; + record.set('name', response.name); + record.set('desc', response.desc); + record.set('desc_trans', response.desc); + record.set('xtype', response.xtype); + record.set('options', response.options); + record.set('value', response.value); + record.set('lexicon', response.lexicon); + // eslint-disable-next-line no-nested-ternary + record.set('overridden', response.overridden === 2 ? 2 : (!isDefaultSet ? 1 : 0)); + record.set('area', response.area); + record.set('area_trans', response.area); + this.getView().refresh(); + this.onDirty(); + }, + scope: this + } } }); - } + }, - ,revert: function(btn,e) { - Ext.Msg.confirm(_('warning'),_('property_revert_confirm'),function(e) { - if (e == 'yes') { - var ri = this.menu.recordIndex; - var d = this.defaultProperties[ri]; - if (d) { - var rec = this.getStore().getAt(ri); - rec.set('name',d[0]); - rec.set('desc',d[1]); - rec.set('desc_trans',d[1]); - rec.set('xtype',d[2]); - rec.set('options',d[3]); - rec.set('value',d[4]); - rec.set('overridden',0); - rec.set('area',d[5]); - rec.set('area_trans',d[5]); - rec.commit(); + revert: function(btn, e) { + Ext.Msg.confirm(_('warning'), _('property_revert_confirm'), function(e) { + if (e === 'yes') { + const + { recordIndex } = this.menu, + propData = this.defaultProperties[recordIndex] + ; + if (propData) { + const record = this.getStore().getAt(recordIndex); + record.set('name', propData[0]); + record.set('desc', propData[1]); + record.set('desc_trans', propData[1]); + record.set('xtype', propData[2]); + record.set('options', propData[3]); + record.set('value', propData[4]); + record.set('overridden', 0); + record.set('area', propData[5]); + record.set('area_trans', propData[5]); + record.commit(); } } - },this); - } + }, this); + }, - ,revertAll: function(btn,e) { - Ext.Msg.confirm(_('warning'),_('property_revert_all_confirm'),function(e) { - if (e == 'yes') { + revertAll: function(btn, e) { + Ext.Msg.confirm(_('warning'), _('property_revert_all_confirm'), function(e) { + if (e === 'yes') { this.getStore().loadData(this.defaultProperties); } - },this); - } + }, this); + }, - ,removeMultiple: function(btn,e) { - var rows = this.getSelectionModel().getSelections(); - var rids = []; - for (var i=0;i in values, desc */ - for (var i in data) { - if (data[i][4]) { data[i][4] = data[i][4].replace(/>/g,'>').replace(/</g,'<'); } - if (data[i][5]) { data[i][5] = data[i][5].replace(/>/g,'>').replace(/</g,'<'); } - if (data[i][1]) { data[i][1] = data[i][1].replace(/>/g,'>').replace(/</g,'<'); } - } - s.loadData(data); - /* mark fields dirty */ - var recs = s.getRange(0,s.getTotalCount()); - for (var i=0;i { + [4, 5, 1].forEach(index => { + if (record[index]) { + record[index] = record[index].replace(/>/g, '>').replace(/</g, '<'); + } + }); + }); + store.loadData(data); + const newRecords = store.getRange(0, store.getTotalCount()); + newRecords.forEach(record => record.markDirty()); + this.getView().refresh(); + }, + scope: this + } } }); - } - - ,_showMenu: function(g,ri,e) { - var sm = this.getSelectionModel(); - if (sm.getSelections().length > 1) { - e.stopEvent(); - e.preventDefault(); - this.menu.removeAll(); - this.addContextMenuItem([{ - text: _('properties_remove') - ,handler: this.removeMultiple - ,scope: this - }]); - this.menu.show(e.target); - } else { - MODx.grid.ElementProperties.superclass._showMenu.call(this,g,ri,e); - } - } + }, - ,isDefaultPropSet: function() { - var ps = Ext.getCmp('modx-combo-property-set').getValue(); - return (ps == 0 || ps == _('default')); - } - - ,getMenu: function() { - var def = this.isDefaultPropSet(); - - var r = this.menu.record; - var m = []; - m.push({ - text: _('property_update') - ,scope: this - ,handler: this.update - }); + isDefaultPropSet: function() { + const propSetId = Ext.getCmp('modx-combo-property-set').getValue(); + return (propSetId === 0 || propSetId === _('default')); + }, - if (r.overridden) { - m.push({ - text: _('property_revert') - ,scope: this - ,handler: this.revert - }); - } - if ((r.overridden == 2 && !def) || (r.overridden != 1 && def) || (!r.overridden && !def)) { - m.push({ - text: _('property_remove') - ,scope: this - ,handler: this.remove.createDelegate(this,[{ - title: _('warning') - ,text: _('property_remove_confirm') - }]) + getMenu: function() { + const + isDefaultSet = this.isDefaultPropSet(), + model = this.getSelectionModel(), + record = model.getSelected(), + propIsCustom = record.data.overridden === 2, + propIsOverriden = record.data.overridden === 1, + propUnchanged = [0, false].includes(record.data.overridden), + menu = [] + ; + if (model.getCount() > 1 && this.userCanDelete) { + menu.push({ + text: _('properties_remove'), + handler: this.removeMultiple, + scope: this }); + } else { + if (this.userCanEdit) { + menu.push({ + text: _('property_update'), + scope: this, + handler: this.update + }); + if (propIsOverriden) { + menu.push({ + text: _('property_revert'), + scope: this, + handler: this.revert + }); + } + } + if ( + this.userCanDelete + && ((!isDefaultSet && (propUnchanged || propIsCustom)) + || (isDefaultSet && !propIsOverriden)) + ) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('property_remove'), + scope: this, + handler: this.remove.createDelegate(this, [{ + title: _('warning'), + text: _('property_remove_confirm') + }]) + }); + } } + return menu; + }, - return m; - } - - ,propertyChanged: function() { - var ep = Ext.getCmp(this.config.panel); - if (!ep) return false; - var hf = this.config.hiddenPropField || 'props'; - ep.getForm().findField(hf).setValue('1'); - ep.fireEvent('fieldChange',{ - field: hf - ,form: ep.getForm() + /** + * Updates hidden field with the current set of serialized properties to + * be persisted to the database. Only applies to an Element's editing panel + * (in its Properties tab), not to the standalone Property Sets editor. + */ + propertyChanged: function() { + const elementPanel = Ext.getCmp(this.config.panel); + if (!elementPanel) { + return false; + } + const propsValueField = this.config.hiddenPropField || 'props'; + elementPanel.getForm().findField(propsValueField).setValue('1'); + elementPanel.fireEvent('fieldChange', { + field: propsValueField, + form: elementPanel.getForm() }); return true; } }); -Ext.reg('modx-grid-element-properties',MODx.grid.ElementProperties); +Ext.reg('modx-grid-element-properties', MODx.grid.ElementProperties); - -MODx.grid.ElementPropertyOption = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('property_options') - ,id: 'modx-grid-element-property-options' - ,autoHeight: true - ,maxHeight: 300 - ,width: '100%' - ,fields: ['text','value','name'] - ,data: [] - ,columns: [{ - header: _('name') - ,dataIndex: 'text' - ,width: 150 - ,editor: { xtype: 'textfield' ,allowBlank: false } - },{ - header: _('value') - ,dataIndex: 'value' - ,id: 'value' - ,width: 250 - ,editor: { xtype: 'textfield' ,allowBlank: true } - }] - ,tbar: [{ - text: _('property_option_create') - ,cls: 'primary-button' - ,handler: this.create - ,scope: this +MODx.grid.ElementPropertyOption = function(config = {}) { + Ext.applyIf(config, { + title: _('property_options'), + id: 'modx-grid-element-property-options', + autoHeight: true, + maxHeight: 300, + width: '100%', + fields: [ + 'text', + 'value', + 'name' + ], + data: [], + columns: [{ + header: _('name'), + dataIndex: 'text', + width: 150, + editor: { + xtype: 'textfield', + allowBlank: false + } + }, { + header: _('value'), + dataIndex: 'value', + id: 'value', + width: 250, + editor: { + xtype: 'textfield' + } + }], + tbar: [{ + text: _('property_option_create'), + cls: 'primary-button', + handler: this.create, + scope: this }] }); - MODx.grid.ElementPropertyOption.superclass.constructor.call(this,config); - this.optRecord = Ext.data.Record.create([{name: 'text'},{name: 'value'}]); + MODx.grid.ElementPropertyOption.superclass.constructor.call(this, config); + this.optRecord = Ext.data.Record.create([ + { name: 'text' }, + { name: 'value' } + ]); }; -Ext.extend(MODx.grid.ElementPropertyOption,MODx.grid.LocalGrid,{ - create: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-element-property-option-create' - ,listeners: { - 'success': {fn:function(r) { - var rec = new this.optRecord({ - text: r.text - ,value: r.value - }); - this.getStore().add(rec); - },scope:this} +Ext.extend(MODx.grid.ElementPropertyOption, MODx.grid.LocalGrid, { + create: function(btn, e) { + this.loadWindow(btn, e, { + xtype: 'modx-window-element-property-option-create', + listeners: { + success: { + fn: function(response) { + const record = new this.optRecord({ + text: response.text, + value: response.value + }); + this.getStore().add(record); + }, + scope: this + } } }); - } + }, - ,getMenu: function() { + getMenu: function() { return [{ - text: _('property_option_remove') - ,scope: this - ,handler: this.remove.createDelegate(this,[{ - title: _('warning') - ,text: _('property_option_remove_confirm') + text: _('property_option_remove'), + scope: this, + handler: this.remove.createDelegate(this, [{ + title: _('warning'), + text: _('property_option_remove_confirm') }]) }]; } }); -Ext.reg('modx-grid-element-property-options',MODx.grid.ElementPropertyOption); +Ext.reg('modx-grid-element-property-options', MODx.grid.ElementPropertyOption); /** * @class MODx.window.CreateElementProperty @@ -602,322 +733,190 @@ Ext.reg('modx-grid-element-property-options',MODx.grid.ElementPropertyOption); * @param {Object} config An object of configuration properties * @xtype modx-window-element-property-create */ -MODx.window.CreateElementProperty = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('property_create') - ,id: 'modx-window-element-property-create' - ,width: 600 - ,saveBtnText: _('done') - ,fields: [{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .6 - ,items: [{ - fieldLabel: _('name') - ,description: MODx.expandHelp ? '' : _('property_name_desc') - ,name: 'name' - ,id: 'modx-cep-name' - ,xtype: 'textfield' - ,anchor: '100%' - ,allowBlank: false - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-cep-name' - ,html: _('property_name_desc') - ,cls: 'desc-under' - },{ - fieldLabel: _('description') - ,description: MODx.expandHelp ? '' : _('property_description_desc') - ,name: 'desc' - ,id: 'modx-cep-desc' - ,xtype: 'textarea' - ,anchor: '100%' - ,height: 120 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-cep-description' - ,html: _('property_description_desc') - ,cls: 'desc-under' +MODx.window.CreateElementProperty = function(config = {}) { + const + id = Ext.id(), + action = config.isUpdate ? 'update' : 'create' + ; + this.id = `modx-window-${action}-property-${id}`; + Ext.applyIf(config, { + title: _('property_create'), + width: 600, + saveBtnText: _('done'), + fields: [{ + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelAlign: 'top', + anchor: '100%', + border: false + }, + items: [{ + columnWidth: 0.6, + items: [{ + fieldLabel: _('name'), + description: MODx.expandHelp ? '' : _('property_name_desc'), + name: 'name', + xtype: 'textfield', + anchor: '100%', + allowBlank: false + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_name_desc'), + cls: 'desc-under' + }, { + fieldLabel: _('description'), + description: MODx.expandHelp ? '' : _('property_description_desc'), + name: 'desc', + xtype: 'textarea', + anchor: '100%', + height: 120 + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_description_desc'), + cls: 'desc-under' }] - },{ - columnWidth: .4 - ,items: [{ - fieldLabel: _('type') - ,description: MODx.expandHelp ? '' : _('property_xtype_desc') - ,name: 'xtype' - ,id: 'modx-cep-xtype' - ,xtype: 'modx-combo-xtype' - ,anchor: '100%' - ,listeners: { - 'select': {fn:function(cb) { - var g = Ext.getCmp('modx-cep-grid-element-property-options'); - if (!g) return; - if (cb.getValue() == 'list' || cb.getValue() == 'color') { - g.show(); - } else { - g.hide(); - } - this.syncSize(); - },scope:this} + }, { + columnWidth: 0.4, + items: [{ + fieldLabel: _('type'), + description: MODx.expandHelp ? '' : _('property_xtype_desc'), + name: 'xtype', + id: `modx-property-xtype--${this.id}`, + xtype: 'modx-combo-xtype', + anchor: '100%', + listeners: { + select: { + fn: function(combo) { + const optsGrid = Ext.getCmp(`modx-grid--property-options--${this.id}`); + if (!optsGrid) { + return; + } + if (['list', 'color'].includes(combo.getValue())) { + optsGrid.show(); + } else { + optsGrid.hide(); + } + this.syncSize(); + }, + scope: this + } } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-cep-xtype' - ,html: _('property_xtype_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('lexicon') - ,description: MODx.expandHelp ? '' : _('property_lexicon_desc') - ,name: 'lexicon' - ,id: 'modx-cep-lexicon' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-cep-lexicon' - ,html: _('property_lexicon_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('area') - ,description: MODx.expandHelp ? '' : _('property_area_desc') - ,name: 'area' - ,id: 'modx-cep-area' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-cep-area' - ,html: _('property_area_desc') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_xtype_desc'), + cls: 'desc-under' + }, { + xtype: 'textfield', + fieldLabel: _('lexicon'), + description: MODx.expandHelp ? '' : _('property_lexicon_desc'), + name: 'lexicon', + anchor: '100%', + allowBlank: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_lexicon_desc'), + cls: 'desc-under' + }, { + xtype: 'textfield', + fieldLabel: _('area'), + description: MODx.expandHelp ? '' : _('property_area_desc'), + name: 'area', + anchor: '100%', + allowBlank: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('property_area_desc'), + cls: 'desc-under' }] }] - },{ - xtype: 'modx-element-value-field' - ,xtypeField: 'modx-cep-xtype' - ,id: 'modx-cep-value' - ,anchor: '100%' - },{ - xtype: 'modx-grid-element-property-options' - ,id: 'modx-cep-grid-element-property-options' - ,anchor: '100%' - }] - ,keys: [] + }, { + xtype: 'modx-element-value-field', + xtypeField: `modx-property-xtype--${this.id}`, + anchor: '100%' + }, { + xtype: 'modx-grid-element-property-options', + id: `modx-grid--property-options--${this.id}`, + anchor: '100%' + }], + keys: [] }); - MODx.window.CreateElementProperty.superclass.constructor.call(this,config); - this.on('show',this.onShow,this); + MODx.window.CreateElementProperty.superclass.constructor.call(this, config); + this.on('show', this.onShow, this); }; -Ext.extend(MODx.window.CreateElementProperty,MODx.Window,{ +Ext.extend(MODx.window.CreateElementProperty, MODx.Window, { submit: function() { - var v = this.fp.getForm().getValues(); - - var g = Ext.getCmp('modx-cep-grid-element-property-options'); - var opt = eval(g.encode()); - Ext.apply(v,{ - options: opt + const + values = this.fp.getForm().getValues(), + optsGrid = Ext.getCmp(`modx-grid--property-options--${this.id}`), + // eslint-disable-next-line no-eval + options = eval(optsGrid.encode()) + ; + Ext.apply(values, { + options: options }); - if (this.fp.getForm().isValid()) { - if (this.fireEvent('success',v)) { + if (this.fireEvent('success', values)) { this.fp.getForm().reset(); this.hide(); return true; } } return false; - } - ,onShow: function() { - var g = Ext.getCmp('modx-cep-grid-element-property-options'); - g.getStore().removeAll(); - g.hide(); + }, + + onShow: function() { + const optsGrid = Ext.getCmp(`modx-grid--property-options--${this.id}`); + if (!optsGrid) { + return; + } + optsGrid.getStore().removeAll(); + optsGrid.hide(); + if ( + this.config.isUpdate + && ['list', 'color'].includes(this.fp.getForm().findField('xtype').getValue()) + ) { + const + propsGrid = Ext.getCmp('modx-grid-element-properties'), + selectedRecord = propsGrid.getSelectionModel().getSelected() + ; + if (selectedRecord) { + const + { options } = selectedRecord.data, + optionsData = [] + ; + options.forEach(option => optionsData.push([option.text, option.value])); + optsGrid.getStore().loadData(optionsData); + optsGrid.show(); + } + } this.syncSize(); this.center(); } }); -Ext.reg('modx-window-element-property-create',MODx.window.CreateElementProperty); - - +Ext.reg('modx-window-element-property-create', MODx.window.CreateElementProperty); /** * @class MODx.window.UpdateElementProperty - * @extends MODx.Window + * @extends MODx.window.CreateElementProperty * @param {Object} config An object of configuration properties * @xtype modx-window-element-property-update */ -MODx.window.UpdateElementProperty = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('property_update') - ,id: 'modx-window-element-property-update' - ,width: 600 - ,saveBtnText: _('done') - ,forceLayout: true - ,fields: [{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .6 - ,items: [{ - fieldLabel: _('name') - ,description: MODx.expandHelp ? '' : _('property_name_desc') - ,name: 'name' - ,id: 'modx-uep-name' - ,xtype: 'textfield' - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-uep-name' - ,html: _('property_name_desc') - ,cls: 'desc-under' - },{ - fieldLabel: _('description') - ,description: MODx.expandHelp ? '' : _('property_description_desc') - ,name: 'desc' - ,id: 'modx-uep-desc' - ,xtype: 'textarea' - ,anchor: '100%' - ,height: 120 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-uep-description' - ,html: _('property_description_desc') - ,cls: 'desc-under' - }] - },{ - columnWidth: .4 - ,items: [{ - fieldLabel: _('type') - ,description: MODx.expandHelp ? '' : _('property_xtype_desc') - ,name: 'xtype' - ,xtype: 'modx-combo-xtype' - ,id: 'modx-uep-xtype' - ,anchor: '100%' - ,listeners: { - 'select': {fn:function(cb) { - var g = Ext.getCmp('modx-uep-grid-element-property-options'); - if (!g) return; - var v = cb.getValue(); - if (v == 'list' || v == 'color') { - g.show(); - } else { - g.hide(); - } - this.syncSize(); - },scope:this} - } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-uep-xtype' - ,html: _('property_xtype_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('lexicon') - ,description: MODx.expandHelp ? '' : _('property_lexicon_desc') - ,name: 'lexicon' - ,id: 'modx-uep-lexicon' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-uep-lexicon' - ,html: _('property_lexicon_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('area') - ,description: MODx.expandHelp ? '' : _('property_area_desc') - ,name: 'area' - ,id: 'modx-uep-area' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-uep-area' - ,html: _('property_area_desc') - ,cls: 'desc-under' - }] - }] - },{ - xtype: 'hidden' - ,name: 'overridden' - ,id: 'modx-uep-overridden' - },{ - xtype: 'modx-element-value-field' - ,xtypeField: 'modx-uep-xtype' - ,name: 'value' - ,id: 'modx-uep-value' - ,anchor: '100%' - },{ - id: 'modx-uep-grid-element-property-options' - ,xtype: 'modx-grid-element-property-options' - ,autoHeight: true - }] - ,keys: [] +MODx.window.UpdateElementProperty = function(config = {}) { + Ext.applyIf(config, { + title: _('property_update'), + isUpdate: true }); - MODx.window.UpdateElementProperty.superclass.constructor.call(this,config); - this.on('show',this.onShow,this); + MODx.window.UpdateElementProperty.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.UpdateElementProperty,MODx.Window,{ - submit: function() { - var v = this.fp.getForm().getValues(); - - var g = Ext.getCmp('modx-uep-grid-element-property-options'); - var opt = eval(g.encode()); - Ext.apply(v,{ - options: opt - }); - - if (this.fp.getForm().isValid()) { - if (this.fireEvent('success',v)) { - this.fp.getForm().reset(); - this.hide(); - return true; - } - } - return false; - } - ,onShow: function() { - var g = Ext.getCmp('modx-uep-grid-element-property-options'); - if (!g) return; - if (this.fp.getForm().findField('xtype').getValue() == 'list' || this.fp.getForm().findField('xtype').getValue() == 'color') { - g.show(); - } else { - g.hide(); - } - g.getStore().removeAll(); - var gp = Ext.getCmp('modx-grid-element-properties'); - var rec = gp.getSelectionModel().getSelected(); - if (rec) { - var opt = rec.data.options; - var opts = []; - for (var x in opt) { - if (opt.hasOwnProperty(x)) { - opts.push([opt[x].text,opt[x].value]); - } - } - g.getStore().loadData(opts); - } - this.syncSize(); - this.center(); - } -}); -Ext.reg('modx-window-element-property-update',MODx.window.UpdateElementProperty); +Ext.extend(MODx.window.UpdateElementProperty, MODx.window.CreateElementProperty); +Ext.reg('modx-window-element-property-update', MODx.window.UpdateElementProperty); /** * @class MODx.window.CreateElementPropertyOption @@ -925,32 +924,31 @@ Ext.reg('modx-window-element-property-update',MODx.window.UpdateElementProperty) * @param {Object} config An object of configuration properties * @xtype modx-window-element-property-option-create */ -MODx.window.CreateElementPropertyOption = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('property_option_create') - ,id: 'modx-window-element-property-option-create' - ,saveBtnText: _('done') - ,fields: [{ - fieldLabel: _('name') - ,name: 'text' - ,id: 'modx-cepo-text' - ,xtype: 'textfield' - ,anchor: '100%' - },{ - fieldLabel: _('value') - ,name: 'value' - ,id: 'modx-cepo-value' - ,xtype: 'textfield' - ,anchor: '100%' +MODx.window.CreateElementPropertyOption = function(config = {}) { + Ext.applyIf(config, { + title: _('property_option_create'), + id: 'modx-window-element-property-option-create', + saveBtnText: _('done'), + fields: [{ + fieldLabel: _('name'), + name: 'text', + id: 'modx-cepo-text', + xtype: 'textfield', + anchor: '100%' + }, { + fieldLabel: _('value'), + name: 'value', + id: 'modx-cepo-value', + xtype: 'textfield', + anchor: '100%' }] }); - MODx.window.CreateElementPropertyOption.superclass.constructor.call(this,config); + MODx.window.CreateElementPropertyOption.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateElementPropertyOption,MODx.Window,{ +Ext.extend(MODx.window.CreateElementPropertyOption, MODx.Window, { submit: function() { if (this.fp.getForm().isValid()) { - if (this.fireEvent('success',this.fp.getForm().getValues())) { + if (this.fireEvent('success', this.fp.getForm().getValues())) { this.fp.getForm().reset(); this.hide(); return true; @@ -959,9 +957,7 @@ Ext.extend(MODx.window.CreateElementPropertyOption,MODx.Window,{ return false; } }); -Ext.reg('modx-window-element-property-option-create',MODx.window.CreateElementPropertyOption); - - +Ext.reg('modx-window-element-property-option-create', MODx.window.CreateElementPropertyOption); /** * Displays a xtype combobox @@ -971,84 +967,78 @@ Ext.reg('modx-window-element-property-option-create',MODx.window.CreateElementPr * @param {Object} config An object of configuration properties * @xtype modx-combo-xtype */ -MODx.combo.xType = function(config) { - config = config || {}; - Ext.applyIf(config,{ +MODx.combo.xType = function(config = {}) { + Ext.applyIf(config, { store: new Ext.data.SimpleStore({ - fields: ['d','v'] - ,data: [ - [_('textfield'),'textfield'] - ,[_('textarea'),'textarea'] - ,[_('yesno'),'combo-boolean'] - ,[_('date'),'datefield'] - ,[_('list'),'list'] - ,[_('integer'),'numberfield'] - ,[_('file'),'file'] - ,[_('color'),'color'] + fields: ['d', 'v'], + data: [ + [_('textfield'), 'textfield'], + [_('textarea'), 'textarea'], + [_('yesno'), 'combo-boolean'], + [_('date'), 'datefield'], + [_('list'), 'list'], + [_('integer'), 'numberfield'], + [_('file'), 'file'], + [_('color'), 'color'] ] - }) - ,displayField: 'd' - ,valueField: 'v' - ,mode: 'local' - ,name: 'xtype' - ,hiddenName: 'xtype' - ,triggerAction: 'all' - ,editable: false - ,selectOnFocus: false - ,value: 'textfield' + }), + displayField: 'd', + valueField: 'v', + mode: 'local', + name: 'xtype', + hiddenName: 'xtype', + triggerAction: 'all', + editable: false, + selectOnFocus: false, + value: 'textfield' }); - MODx.combo.xType.superclass.constructor.call(this,config); + MODx.combo.xType.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.xType,Ext.form.ComboBox); -Ext.reg('modx-combo-xtype',MODx.combo.xType); - - +Ext.extend(MODx.combo.xType, Ext.form.ComboBox); +Ext.reg('modx-combo-xtype', MODx.combo.xType); - -MODx.form.ElementValueField = function(config) { - config = config || {}; - Ext.applyIf(config,{ - fieldLabel: _('value') - ,name: 'value' - ,xtype: 'textfield' +MODx.form.ElementValueField = function(config = {}) { + Ext.applyIf(config, { + fieldLabel: _('value'), + name: 'value', + xtype: 'textfield' }); - MODx.form.ElementValueField.superclass.constructor.call(this,config); + MODx.form.ElementValueField.superclass.constructor.call(this, config); this.config = config; - this.on('change',this.checkValue,this); + this.on('change', this.checkValue, this); }; -Ext.extend(MODx.form.ElementValueField,Ext.form.TextField,{ - checkValue: function(fld,nv,ov) { - var t = Ext.getCmp(this.config.xtypeField).getValue(); - var v = fld.getValue(); - if (t == 'combo-boolean') { - v = (v == '1' || v == 'true' || v == 1 || v == true || v == _('yes') || v == 'yes') ? 1 : 0; - fld.setValue(v); +Ext.extend(MODx.form.ElementValueField, Ext.form.TextField, { + checkValue: function(field, newValue, oldValue) { + const xtype = Ext.getCmp(this.config.xtypeField).getValue(); + if (xtype === 'combo-boolean') { + let value = field.getValue(); + value = [1, '1', true, 'true', _('yes'), 'yes'].includes(value) ? 1 : 0; + field.setValue(value); } } }); -Ext.reg('modx-element-value-field',MODx.form.ElementValueField); - +Ext.reg('modx-element-value-field', MODx.form.ElementValueField); -MODx.combo.PropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - name: 'propertyset' - ,hiddenName: 'propertyset' - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Element/PropertySet/GetList' - } - ,displayField: 'name' - ,valueField: 'id' - ,fields: ['id','name','description','properties'] - ,editable: false - ,value: 0 - ,pageSize: 10 +MODx.combo.PropertySet = function(config = {}) { + Ext.applyIf(config, { + name: 'propertyset', + hiddenName: 'propertyset', + url: MODx.config.connector_url, + baseParams: { + action: 'Element/PropertySet/GetList', + combo: true + }, + displayField: 'name', + valueField: 'id', + fields: ['id', 'name', 'description', 'properties'], + editable: false, + value: 0, + pageSize: 10 }); - MODx.combo.PropertySet.superclass.constructor.call(this,config); + MODx.combo.PropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.PropertySet,MODx.combo.ComboBox); -Ext.reg('modx-combo-property-set',MODx.combo.PropertySet); +Ext.extend(MODx.combo.PropertySet, MODx.combo.ComboBox); +Ext.reg('modx-combo-property-set', MODx.combo.PropertySet); /** * @class MODx.window.AddPropertySet @@ -1056,115 +1046,116 @@ Ext.reg('modx-combo-property-set',MODx.combo.PropertySet); * @param {Object} config An object of configuration properties * @xtype modx-window-element-property-set-add */ -MODx.window.AddPropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertyset_add') - ,id: 'modx-window-element-property-set-add' - ,url: MODx.config.connector_url - ,action: 'Element/PropertySet/Associate' - ,autoHeight: true // makes window grow when the fieldset is toggled - ,fields: [{ - xtype: 'hidden' - ,name: 'elementId' - ,id: 'modx-aps-elementId' - },{ - xtype: 'hidden' - ,name: 'elementType' - ,id: 'modx-aps-elementType' - },{ - html: _('propertyset_panel_desc') - ,xtype: 'modx-description' +MODx.window.AddPropertySet = function(config = {}) { + Ext.applyIf(config, { + title: _('propertyset_add'), + id: 'modx-window-element-property-set-add', + url: MODx.config.connector_url, + action: 'Element/PropertySet/Associate', + autoHeight: true, // makes window grow when the fieldset is toggled + fields: [{ + xtype: 'hidden', + name: 'elementId' + }, { + xtype: 'hidden', + name: 'elementType' + }, { + html: _('propertyset_panel_desc'), + xtype: 'modx-description' - },MODx.PanelSpacer,{ - xtype: 'modx-combo-property-set' - ,fieldLabel: _('propertyset') - ,name: 'propertyset' - ,id: 'modx-aps-propertyset' - ,anchor: '100%' - ,baseParams: { - action: 'Element/PropertySet/GetList' - ,showNotAssociated: true - ,elementId: config.record.elementId - ,elementType: config.record.elementType - } - },{ - xtype: 'hidden' - ,name: 'propertyset_new' - ,id: 'modx-aps-propertyset-new' - ,value: false - },{ - xtype: 'fieldset' - ,title: _('propertyset_create_new') - ,autoHeight: true - ,checkboxToggle: true - ,collapsed: true - ,forceLayout: true - ,id: 'modx-aps-propertyset-new-fs' - ,listeners: { - 'expand': {fn:function(p) { - Ext.getCmp('modx-aps-propertyset-new').setValue(true); - this.center(); // re-centers window on screen after height changed - },scope:this} - ,'collapse': {fn:function(p) { - Ext.getCmp('modx-aps-propertyset-new').setValue(false); - this.center(); // re-centers window on screen after height changed - },scope:this} + }, MODx.PanelSpacer, { + xtype: 'modx-combo-property-set', + fieldLabel: _('propertyset'), + name: 'propertyset', + anchor: '100%', + baseParams: { + action: 'Element/PropertySet/GetList', + showNotAssociated: true, + elementId: config.record.elementId, + elementType: config.record.elementType, + combo: true } - ,items: [{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,id: 'modx-aps-name' - ,anchor: '100%' - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,id: 'modx-aps-description' - ,anchor: '100%' - ,grow: true + }, { + xtype: 'hidden', + name: 'propertyset_new', + id: 'modx-aps-propertyset-new', + value: false + }, { + xtype: 'fieldset', + title: _('propertyset_create_new'), + autoHeight: true, + checkboxToggle: true, + collapsed: true, + forceLayout: true, + listeners: { + expand: { + fn: function(p) { + Ext.getCmp('modx-aps-propertyset-new').setValue(true); + this.center(); + }, + scope: this + }, + collapse: { + fn: function(p) { + Ext.getCmp('modx-aps-propertyset-new').setValue(false); + this.center(); + }, + scope: this + } + }, + items: [{ + xtype: 'textfield', + fieldLabel: _('name'), + name: 'name', + anchor: '100%' + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + anchor: '100%', + grow: true }] }] }); - MODx.window.AddPropertySet.superclass.constructor.call(this,config); + MODx.window.AddPropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.AddPropertySet,MODx.Window); -Ext.reg('modx-window-element-property-set-add',MODx.window.AddPropertySet); +Ext.extend(MODx.window.AddPropertySet, MODx.Window); +Ext.reg('modx-window-element-property-set-add', MODx.window.AddPropertySet); -MODx.window.ImportProperties = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('import') - ,id: 'modx-window-properties-import' - ,url: MODx.config.connector_url - ,action: 'Element/ImportProperties' - ,fileUpload: true - ,saveBtnText: _('import') - ,fields: [{ - html: _('properties_import_msg') - ,id: 'modx-impp-desc' - ,style: 'margin-bottom: 10px;' - ,xtype: 'modx-description' - },{ - xtype: 'fileuploadfield' - ,fieldLabel: _('file') - ,buttonText: _('upload.buttons.upload') - ,name: 'file' - ,id: 'modx-impp-file' - ,anchor: '100%' +MODx.window.ImportProperties = function(config = {}) { + Ext.applyIf(config, { + title: _('import'), + id: 'modx-window-properties-import', + url: MODx.config.connector_url, + action: 'Element/ImportProperties', + fileUpload: true, + saveBtnText: _('import'), + fields: [{ + html: _('properties_import_msg'), + style: 'margin-bottom: 10px;', + xtype: 'modx-description' + }, { + xtype: 'fileuploadfield', + fieldLabel: _('file'), + buttonText: _('upload.buttons.upload'), + name: 'file', + id: 'modx-impp-file', + anchor: '100%' }] }); - MODx.window.ImportProperties.superclass.constructor.call(this,config); - - // Trigger "fileselected" event - var fp = Ext.getCmp('modx-impp-file'); - var onFileUploadFieldFileSelected = function(fp, fakeFilePath) { - var fileApi = fp.fileInput.dom.files; - fp.el.dom.value = (typeof fileApi != 'undefined') ? fileApi[0].name : fakeFilePath.replace("C:\\fakepath\\", ""); - }; - fp.on('fileselected', onFileUploadFieldFileSelected); + MODx.window.ImportProperties.superclass.constructor.call(this, config); + const + fileCmp = Ext.getCmp('modx-impp-file'), + onFileUploadFieldFileSelected = function(fileCmp, fakeFilePath) { + const fileApi = fileCmp.fileInput.dom.files; + fileCmp.el.dom.value = (typeof fileApi != 'undefined') + ? fileApi[0].name + : fakeFilePath.replace('C:\\fakepath\\', '') + ; + } + ; + fileCmp.on('fileselected', onFileUploadFieldFileSelected); }; -Ext.extend(MODx.window.ImportProperties,MODx.Window); -Ext.reg('modx-window-properties-import',MODx.window.ImportProperties); +Ext.extend(MODx.window.ImportProperties, MODx.Window); +Ext.reg('modx-window-properties-import', MODx.window.ImportProperties); 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 c24f4a80597..dd74f138ded 100644 --- a/manager/assets/modext/widgets/element/modx.grid.template.tv.js +++ b/manager/assets/modext/widgets/element/modx.grid.template.tv.js @@ -24,7 +24,6 @@ MODx.grid.TemplateTV = function(config = {}) { 'caption', 'tv_rank', 'access', - 'perm', 'category_name', 'category' ], @@ -64,10 +63,13 @@ MODx.grid.TemplateTV = function(config = {}) { sortable: true, renderer: { fn: function(value, metadata, record) { - return this.renderLink(value, { - href: `?a=element/tv/update&id=${record.data.id}`, - target: '_blank' - }); + return this.userCanEditTv && this.userCanEditRecord(record, 'updateTv') + ? this.renderLink(value, { + href: `?a=element/tv/update&id=${record.data.id}`, + target: '_blank' + }) + : value + ; }, scope: this } @@ -112,16 +114,22 @@ MODx.grid.TemplateTV = function(config = {}) { ] }); MODx.grid.TemplateTV.superclass.constructor.call(this, config); + + // In this case, edit grid action indicates ability to edit the TV, not the Template + this.gridMenuActions = ['editTv']; + this.setUserCanEdit(['edit_template', 'save_template']); + this.setUserHasPermissions('editTv', ['edit_tv', 'save_tv']); + this.setShowActionsMenu(); + this.on('render', this.prepareDDSort, this); }; Ext.extend(MODx.grid.TemplateTV, MODx.grid.Grid, { getMenu: function() { const record = this.getSelectionModel().getSelected(), - permissions = record.data.perm, menu = [] ; - if (permissions.indexOf('pedit') !== -1) { + if (this.userCanEditTv && this.userCanEditRecord(record, 'updateTv')) { menu.push({ text: _('edit'), handler: this.updateTV diff --git a/manager/assets/modext/widgets/element/modx.grid.tv.security.js b/manager/assets/modext/widgets/element/modx.grid.tv.security.js index 297e1247b0d..ffdefc19cb0 100644 --- a/manager/assets/modext/widgets/element/modx.grid.tv.security.js +++ b/manager/assets/modext/widgets/element/modx.grid.tv.security.js @@ -6,44 +6,60 @@ * @param {Object} config An object of options. * @xtype modx-grid-tv-security */ -MODx.grid.TVSecurity = function(config) { - config = config || {}; - var tt = new Ext.ux.grid.CheckColumn({ - header: _('access') - ,dataIndex: 'access' - ,width: 40 - ,sortable: false +MODx.grid.TVSecurity = function(config = {}) { + const accessCheckboxCol = new Ext.ux.grid.CheckColumn({ + header: _('access'), + dataIndex: 'access', + width: 40, + sortable: false }); - Ext.applyIf(config,{ - id: 'modx-grid-tv-security' - ,url: MODx.config.connector_url - ,showActionsColumn: false - ,fields: ['id','name','access','menu'] - ,baseParams: { - action: 'Element/TemplateVar/ResourceGroup/GetList' - ,tv: config.tv - } - ,saveParams: { + Ext.applyIf(config, { + id: 'modx-grid-tv-security', + url: MODx.config.connector_url, + showActionsColumn: false, + fields: [ + 'id', + 'name', + 'access', + 'menu' + ], + baseParams: { + action: 'Element/TemplateVar/ResourceGroup/GetList', tv: config.tv - } - ,width: 800 - ,paging: true - ,remoteSort: true - ,plugins: tt - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/resourcegroup' - ,target: '_blank' - }); - }, scope: this } - },tt] + }, + saveParams: { + tv: config.tv + }, + width: 800, + paging: true, + remoteSort: true, + plugins: accessCheckboxCol, + columns: [ + { + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditResourceGroups + ? this.renderLink(value, { + href: '?a=security/resourcegroup', + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, + accessCheckboxCol + ] }); - MODx.grid.TVSecurity.superclass.constructor.call(this,config); + MODx.grid.TVSecurity.superclass.constructor.call(this, config); + + this.setUserCanEdit(['edit_tv', 'save_tv']); + this.setUserHasPermissions('editResourceGroups', ['resourcegroup_view', 'resourcegroup_resource_list']); }; -Ext.extend(MODx.grid.TVSecurity,MODx.grid.Grid); -Ext.reg('modx-grid-tv-security',MODx.grid.TVSecurity); +Ext.extend(MODx.grid.TVSecurity, MODx.grid.Grid); +Ext.reg('modx-grid-tv-security', MODx.grid.TVSecurity); 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 2acf0adfa10..b7c89383eaa 100644 --- a/manager/assets/modext/widgets/element/modx.grid.tv.template.js +++ b/manager/assets/modext/widgets/element/modx.grid.tv.template.js @@ -46,10 +46,13 @@ MODx.grid.TemplateVarTemplate = function(config = {}) { sortable: true, renderer: { fn: function(value, metadata, record) { - return this.renderLink(value, { - href: `?a=element/template/update&id=${record.data.id}`, - target: '_blank' - }); + return this.userCanEditTemplate && this.userCanEditRecord(record, 'updateTemplate') + ? this.renderLink(value, { + href: `?a=element/template/update&id=${record.data.id}`, + target: '_blank' + }) + : value + ; }, scope: this } @@ -86,6 +89,9 @@ MODx.grid.TemplateVarTemplate = function(config = {}) { ] }); MODx.grid.TemplateVarTemplate.superclass.constructor.call(this, config); + + this.setUserCanEdit(['edit_tv', 'save_tv']); + this.setUserHasPermissions('editTemplate', ['edit_template', 'save_template']); }; Ext.extend(MODx.grid.TemplateVarTemplate, MODx.grid.Grid); Ext.reg('modx-grid-tv-template', MODx.grid.TemplateVarTemplate); diff --git a/manager/assets/modext/widgets/element/modx.panel.property.set.js b/manager/assets/modext/widgets/element/modx.panel.property.set.js index f51af014cf9..0002703bcce 100644 --- a/manager/assets/modext/widgets/element/modx.panel.property.set.js +++ b/manager/assets/modext/widgets/element/modx.panel.property.set.js @@ -4,60 +4,59 @@ * @param {Object} config An object of config properties * @xtype modx-panel-property-sets */ -MODx.panel.PropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-property-sets' - ,cls: 'container' - ,items: [{ - html: _('propertysets') - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('propertysets') - ,layout: 'form' - ,id: 'modx-property-set-form' - ,border: true - ,items: [{ - html: '

    '+_('propertysets_desc')+'

    ' - ,id: 'modx-property-set-msg' - ,xtype: 'modx-description' - },{ - layout: 'column' - ,border: false - ,cls: 'main-wrapper' - ,items: [{ - columnWidth: .3 - ,cls: 'left-col' - ,border: false - ,layout: 'anchor' - ,items: [{ - xtype: 'modx-tree-property-sets' - ,preventRender: true - ,anchor: '100%' +MODx.panel.PropertySet = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-property-sets', + cls: 'container', + items: [{ + html: _('propertysets'), + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('propertysets'), + layout: 'form', + id: 'modx-property-set-form', + border: true, + items: [{ + html: `

    ${_('propertysets_desc')}

    `, + id: 'modx-property-set-msg', + xtype: 'modx-description' + }, { + layout: 'column', + border: false, + cls: 'main-wrapper', + items: [{ + columnWidth: 0.3, + cls: 'left-col', + border: false, + layout: 'anchor', + items: [{ + xtype: 'modx-tree-property-sets', + preventRender: true, + anchor: '100%' }] - },{ - columnWidth: .7 - ,layout: 'form' - ,border: false - ,autoHeight: true - ,id: 'right-column' - ,items: [] + }, { + columnWidth: 0.7, + layout: 'form', + border: false, + autoHeight: true, + id: 'right-column', + items: [] }] }] }])] }); - MODx.panel.PropertySet.superclass.constructor.call(this,config); + MODx.panel.PropertySet.superclass.constructor.call(this, config); /* load after b/c of safari/ie focus bug */ (function() { Ext.getCmp('right-column').add({ - xtype: 'modx-grid-property-set-properties' - ,id: 'modx-grid-element-properties' + xtype: 'modx-grid-property-set-properties', + id: 'modx-grid-element-properties' }); }).defer(50, this); }; -Ext.extend(MODx.panel.PropertySet,MODx.FormPanel); -Ext.reg('modx-panel-property-sets',MODx.panel.PropertySet); +Ext.extend(MODx.panel.PropertySet, MODx.FormPanel); +Ext.reg('modx-panel-property-sets', MODx.panel.PropertySet); /** * @class MODx.grid.PropertySetProperties @@ -65,43 +64,50 @@ Ext.reg('modx-panel-property-sets',MODx.panel.PropertySet); * @param {Object} config An object of config properties * @xtype modx-grid-property-set-properties */ -MODx.grid.PropertySetProperties = function(config) { - config = config || {}; - Ext.applyIf(config,{ - autoHeight: true - ,lockProperties: false - ,tbar: [{ - xtype: 'modx-combo-property-set' - ,id: 'modx-combo-property-set' - ,baseParams: { - action: 'Element/PropertySet/GetList' - } - ,listeners: { - 'select': {fn:function(cb) { Ext.getCmp('modx-grid-element-properties').changePropertySet(cb); },scope:this} - } - ,value: '' - },{ - text: _('property_create') - ,handler: function(btn,e) { - if (Ext.getCmp('modx-combo-property-set').value != '') { - Ext.getCmp('modx-grid-element-properties').create(btn,e); +MODx.grid.PropertySetProperties = function(config = {}) { + Ext.applyIf(config, { + autoHeight: true, + lockProperties: false, + tbar: [{ + xtype: 'modx-combo-property-set', + id: 'modx-combo-property-set', + baseParams: { + action: 'Element/PropertySet/GetList', + combo: true + }, + listeners: { + select: { + fn: function(cb) { + Ext.getCmp('modx-grid-element-properties').changePropertySet(cb); + }, + scope: this + } + }, + value: '' + }, { + text: _('property_create'), + id: 'modx-btn-property-create', + handler: function(btn, e) { + if (Ext.getCmp('modx-combo-property-set').value !== '') { + Ext.getCmp('modx-grid-element-properties').create(btn, e); } else { MODx.msg.alert('', _('propertyset_err_ns')); } - } - ,scope: this - },'->',{ - text: _('propertyset_save') - ,cls: 'primary-button' - ,handler: function() { Ext.getCmp('modx-grid-element-properties').save(); } - ,scope: this + }, + scope: this + }, '->', { + text: _('propertyset_save'), + id: 'modx-btn-property-set-save', + cls: 'primary-button', + handler: function() { Ext.getCmp('modx-grid-element-properties').save(); }, + scope: this }] }); Ext.getCmp('right-column').disable(); - MODx.grid.PropertySetProperties.superclass.constructor.call(this,config); + MODx.grid.PropertySetProperties.superclass.constructor.call(this, config); }; -Ext.extend(MODx.grid.PropertySetProperties,MODx.grid.ElementProperties); -Ext.reg('modx-grid-property-set-properties',MODx.grid.PropertySetProperties); +Ext.extend(MODx.grid.PropertySetProperties, MODx.grid.ElementProperties); +Ext.reg('modx-grid-property-set-properties', MODx.grid.PropertySetProperties); /** * @class MODx.tree.PropertySets @@ -109,199 +115,232 @@ Ext.reg('modx-grid-property-set-properties',MODx.grid.PropertySetProperties); * @param {Object} config An object of config properties * @xtype modx-tree-property-sets */ -MODx.tree.PropertySets = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertysets') - ,url: MODx.config.connector_url - ,action: 'Element/PropertySet/GetNodes' - ,rootIconCls: 'icon-sitemap' - ,root_name: _('propertysets') - ,rootVisible: false - ,enableDD: false - ,tbar: ['->', { - text: _('propertyset_new') - ,cls: 'primary-button' - ,handler: this.createSet - ,scope: this - }] - ,useDefaultToolbar: true +MODx.tree.PropertySets = function(config = {}) { + Ext.applyIf(config, { + title: _('propertysets'), + url: MODx.config.connector_url, + action: 'Element/PropertySet/GetNodes', + rootIconCls: 'icon-sitemap', + root_name: _('propertysets'), + rootVisible: false, + enableDD: false, + tbar: ['->', { + text: _('propertyset_new'), + cls: 'primary-button', + handler: this.createSet, + hidden: !MODx.perm.new_propertyset || !MODx.perm.save_propertyset, + scope: this + }], + useDefaultToolbar: true }); - MODx.tree.PropertySets.superclass.constructor.call(this,config); - this.on('click',this.loadGrid,this); + MODx.tree.PropertySets.superclass.constructor.call(this, config); + this.on('click', this.loadGrid, this); }; -Ext.extend(MODx.tree.PropertySets,MODx.tree.Tree,{ - loadGrid: function(n,e) { - Ext.getCmp('right-column').enable(); - var ar = n.id.split('_'); - if (ar[0] == 'ps') { - MODx.Ajax.request({ - url: MODx.config.connector_url - ,params: { - action: 'Element/PropertySet/GetProperties' - ,id: ar[1] +Ext.extend(MODx.tree.PropertySets, MODx.tree.Tree, { + loadGrid: function(node, e) { + const + [recordType, setId, elId, elType] = node.id.split('_'), + propsGrid = Ext.getCmp('modx-grid-element-properties'), + propSetCombo = Ext.getCmp('modx-combo-property-set'), + setGridData = (response, setId, elId = null, elType = null) => { + const + data = response.object, + store = propsGrid.getStore() + ; + propsGrid.defaultProperties = data; + if (elId && elType) { + propsGrid.config.elementId = elId; + propsGrid.config.elementType = elType; + } else { + delete propsGrid.config.elementId; + delete propsGrid.config.elementType; } - ,listeners: { - 'success': {fn:function(r) { - var d = r.object; - var g = Ext.getCmp('modx-grid-element-properties'); - var s = g.getStore(); - g.defaultProperties = d; - delete g.config.elementId; - delete g.config.elementType; - s.removeAll(); - s.loadData(d); + store.removeAll(); + store.loadData(data); + propSetCombo.setValue(setId); + } + ; + Ext.getCmp('right-column').enable(); - Ext.getCmp('modx-combo-property-set').setValue(ar[1]); - },scope:this} + if (recordType === 'ps') { + MODx.Ajax.request({ + url: MODx.config.connector_url, + params: { + action: 'Element/PropertySet/GetProperties', + id: setId + }, + listeners: { + success: { + fn: response => { + setGridData(response, setId); + } + } } }); - } else if (ar[0] == 'el' && ar[2] && ar[3]) { + } else if (recordType === 'el' && elId && elType) { MODx.Ajax.request({ - url: MODx.config.connector_url - ,params: { - action: 'Element/PropertySet/GetProperties' - ,id: ar[1] - ,element: ar[2] - ,element_class: ar[3] - } - ,listeners: { - 'success': {fn:function(r) { - var d = r.object; - var g = Ext.getCmp('modx-grid-element-properties'); - var s = g.getStore(); - g.defaultProperties = d; - g.config.elementId = ar[2]; - g.config.elementType = ar[3]; - s.removeAll(); - s.loadData(d); - - Ext.getCmp('modx-combo-property-set').setValue(ar[1]); - },scope:this} + url: MODx.config.connector_url, + params: { + action: 'Element/PropertySet/GetProperties', + id: setId, + element: elId, + element_class: elType + }, + listeners: { + success: { + fn: response => { + setGridData(response, setId, elId, elType); + } + } } }); } - } + }, - ,createSet: function(btn,e) { + createSet: function(btn, e) { if (!this.winCreateSet) { this.winCreateSet = MODx.load({ - xtype: 'modx-window-property-set-create' - ,listeners: { - 'success':{fn:function() { - this.refresh(); - Ext.getCmp('modx-combo-property-set').store.reload(); - },scope:this} + xtype: 'modx-window-property-set-create', + listeners: { + success: { + fn: function() { + this.refresh(); + Ext.getCmp('modx-combo-property-set').store.reload(); + }, + scope: this + } } }); } this.winCreateSet.reset(); this.winCreateSet.show(e.target); - } + }, - ,duplicateSet: function(btn,e) { - var id = this.cm.activeNode.id.split('_'); - var r = this.cm.activeNode.attributes.data; - r.id = id[1]; - r.new_name = _('duplicate_of',{name:r.name}); + duplicateSet: function(btn, e) { + const + [, setId] = this.cm.activeNode.id.split('_'), + record = this.cm.activeNode.attributes.data + ; + record.id = setId; + record.new_name = _('duplicate_of', { name: record.name }); if (!this.winDupeSet) { this.winDupeSet = MODx.load({ - xtype: 'modx-window-property-set-duplicate' - ,record: r - ,listeners: { - 'success':{fn:function() { - this.refresh(); - Ext.getCmp('modx-combo-property-set').store.reload(); - },scope:this} + xtype: 'modx-window-property-set-duplicate', + record: record, + listeners: { + success: { + fn: function() { + this.refresh(); + Ext.getCmp('modx-combo-property-set').store.reload(); + }, + scope: this + } } }); } - this.winDupeSet.setValues(r); + this.winDupeSet.setValues(record); this.winDupeSet.show(e.target); - } + }, - ,updateSet: function(btn,e) { - var id = this.cm.activeNode.id.split('_'); - var r = this.cm.activeNode.attributes.data; - r.id = id[1]; + updateSet: function(btn, e) { + const + [, setId] = this.cm.activeNode.id.split('_'), + record = this.cm.activeNode.attributes.data + ; + record.id = setId; if (!this.winUpdateSet) { this.winUpdateSet = MODx.load({ - xtype: 'modx-window-property-set-update' - ,record: r - ,listeners: { - 'success':{fn:function() { - this.refresh(); - Ext.getCmp('modx-combo-property-set').store.reload(); - },scope:this} + xtype: 'modx-window-property-set-update', + record: record, + listeners: { + success: { + fn: function() { + this.refresh(); + Ext.getCmp('modx-combo-property-set').store.reload(); + }, + scope: this + } } }); } - this.winUpdateSet.setValues(r); + this.winUpdateSet.setValues(record); this.winUpdateSet.show(e.target); - } + }, - ,removeSet: function(btn,e) { - var id = this.cm.activeNode.id.split('_'); - id = id[1]; + removeSet: function(btn, e) { + const [, setId] = this.cm.activeNode.id.split('_'); MODx.msg.confirm({ - text: _('propertyset_remove_confirm') - ,url: MODx.config.connector_url - ,params: { - action: 'Element/PropertySet/Remove' - ,id: id - } - ,listeners: { - 'success': {fn:function() { - this.refreshNode(this.cm.activeNode.id); - var g = Ext.getCmp('modx-grid-element-properties'); - g.getStore().removeAll(); - g.defaultProperties = []; - Ext.getCmp('modx-combo-property-set').setValue(''); - },scope:this} + text: _('propertyset_remove_confirm'), + url: MODx.config.connector_url, + params: { + action: 'Element/PropertySet/Remove', + id: setId + }, + listeners: { + success: { + fn: function() { + this.refreshNode(this.cm.activeNode.id); + const propsGrid = Ext.getCmp('modx-grid-element-properties'); + propsGrid.getStore().removeAll(); + propsGrid.defaultProperties = []; + Ext.getCmp('modx-combo-property-set').setValue(''); + }, + scope: this + } } }); - } - - ,addElement: function(btn,e) { - var id = this.cm.activeNode.id.split('_'); id = id[1]; - var t = this.cm.activeNode.text; - var r = { - propertysetName: this.cm.activeNode.text - ,propertyset: id - }; + }, + addElement: function(btn, e) { + const + [, setId] = this.cm.activeNode.id.split('_'), + record = { + propertysetName: this.cm.activeNode.text, + propertyset: setId + } + ; if (!this.winPSEA) { this.winPSEA = MODx.load({ - xtype: 'modx-window-propertyset-element-add' - ,record: r - ,listeners: { - 'success':{fn:function() { this.refreshNode(this.cm.activeNode.id,true); },scope:this} + xtype: 'modx-window-propertyset-element-add', + record: record, + listeners: { + success: { + fn: function() { + this.refreshNode(this.cm.activeNode.id, true); + }, + scope: this + } } }); } this.winPSEA.fp.getForm().reset(); - this.winPSEA.fp.getForm().setValues(r); + this.winPSEA.fp.getForm().setValues(record); this.winPSEA.show(e.target); - } + }, - ,removeElement: function(btn,e) { - var d = this.cm.activeNode.attributes; + removeElement: function(btn, e) { + const { attributes } = this.cm.activeNode; MODx.msg.confirm({ - text: _('propertyset_element_remove_confirm') - ,url: MODx.config.connector_url - ,params: { - action: 'Element/PropertySet/RemoveElement' - ,element: d.pk - ,element_class: d.element_class - ,propertyset: d.propertyset - } - ,listeners: { - 'success': {fn:function() { this.refreshNode(this.cm.activeNode.id); },scope:this} + text: _('propertyset_element_remove_confirm'), + url: MODx.config.connector_url, + params: { + action: 'Element/PropertySet/RemoveElement', + element: attributes.pk, + element_class: attributes.element_class, + propertyset: attributes.propertyset + }, + listeners: { + success: { + fn: function() { + this.refreshNode(this.cm.activeNode.id); + }, + scope: this + } } }); } }); -Ext.reg('modx-tree-property-sets',MODx.tree.PropertySets); +Ext.reg('modx-tree-property-sets', MODx.tree.PropertySets); /** * @class MODx.window.AddElementToPropertySet @@ -309,60 +348,61 @@ Ext.reg('modx-tree-property-sets',MODx.tree.PropertySets); * @param {Object} config An object of configuration properties * @xtype modx-window-propertyset-element-add */ -MODx.window.AddElementToPropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertyset_element_add') - ,url: MODx.config.connector_url - ,baseParams: { +MODx.window.AddElementToPropertySet = function(config = {}) { + Ext.applyIf(config, { + title: _('propertyset_element_add'), + url: MODx.config.connector_url, + baseParams: { action: 'Element/PropertySet/AddElement' - } - ,fields: [{ - xtype: 'hidden' - ,name: 'propertyset' - },{ - xtype: 'statictextfield' - ,fieldLabel: _('propertyset') - ,name: 'propertysetName' - ,anchor: '100%' - },{ - xtype: 'modx-combo-element-class' - ,fieldLabel: _('class_name') - ,name: 'element_class' - ,id: 'modx-combo-element-class' - ,anchor: '100%' - ,listeners: { - 'select': {fn:this.onClassSelect,scope:this} + }, + fields: [{ + xtype: 'hidden', + name: 'propertyset' + }, { + xtype: 'statictextfield', + fieldLabel: _('propertyset'), + name: 'propertysetName', + anchor: '100%' + }, { + xtype: 'modx-combo-element-class', + fieldLabel: _('class_name'), + name: 'element_class', + id: 'modx-combo-element-class', + anchor: '100%', + listeners: { + select: { fn: this.onClassSelect, scope: this } } - },{ - xtype: 'modx-combo-elements' - ,fieldLabel: _('element') - ,name: 'element' - ,id: 'modx-combo-elements' - ,anchor: '100%' - ,listeners: { - 'select': {fn:this.onElementSelect,scope:this} + }, { + xtype: 'modx-combo-elements', + fieldLabel: _('element'), + name: 'element', + id: 'modx-combo-elements', + anchor: '100%', + listeners: { + select: { fn: this.onElementSelect, scope: this } } }] }); - MODx.window.AddElementToPropertySet.superclass.constructor.call(this,config); + MODx.window.AddElementToPropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.AddElementToPropertySet,MODx.Window,{ - onClassSelect: function(cb) { - var e = Ext.getCmp('modx-combo-elements'); - var s = e.store; - s.baseParams.element_class = cb.getValue(); - s.load(); - e.setValue(''); - } - ,onElementSelect: function(cb) { - var ec = Ext.getCmp('modx-combo-element-class'); - if (ec.getValue() === '') { - ec.setValue('MODX\\Revolution\\modSnippet'); +Ext.extend(MODx.window.AddElementToPropertySet, MODx.Window, { + onClassSelect: function(classCombo) { + const + elCombo = Ext.getCmp('modx-combo-elements'), + { store } = elCombo + ; + store.baseParams.element_class = classCombo.getValue(); + store.load(); + elCombo.setValue(''); + }, + onElementSelect: function(elCombo) { + const elType = Ext.getCmp('modx-combo-element-class'); + if (elType.getValue() === '') { + elType.setValue('MODX\\Revolution\\modSnippet'); } } }); -Ext.reg('modx-window-propertyset-element-add',MODx.window.AddElementToPropertySet); +Ext.reg('modx-window-propertyset-element-add', MODx.window.AddElementToPropertySet); /** * @class MODx.combo.ElementClass @@ -370,25 +410,24 @@ Ext.reg('modx-window-propertyset-element-add',MODx.window.AddElementToPropertySe * @param {Object} config An object of configuration properties * @xtype modx-combo-element-class */ -MODx.combo.ElementClass = function(config) { - config = config || {}; - Ext.applyIf(config,{ - name: 'element_class' - ,hiddenName: 'element_class' - ,displayField: 'name' - ,valueField: 'name' - ,fields: ['name'] - ,pageSize: 20 - ,editable: false - ,url: MODx.config.connector_url - ,baseParams: { +MODx.combo.ElementClass = function(config = {}) { + Ext.applyIf(config, { + name: 'element_class', + hiddenName: 'element_class', + displayField: 'name', + valueField: 'name', + fields: ['name'], + pageSize: 20, + editable: false, + url: MODx.config.connector_url, + baseParams: { action: 'Element/GetClasses' } }); - MODx.combo.ElementClass.superclass.constructor.call(this,config); + MODx.combo.ElementClass.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.ElementClass,MODx.combo.ComboBox); -Ext.reg('modx-combo-element-class',MODx.combo.ElementClass); +Ext.extend(MODx.combo.ElementClass, MODx.combo.ComboBox); +Ext.reg('modx-combo-element-class', MODx.combo.ElementClass); /** * @class MODx.combo.Elements @@ -396,26 +435,25 @@ Ext.reg('modx-combo-element-class',MODx.combo.ElementClass); * @param {Object} config An object of configuration properties * @xtype modx-combo-elements */ -MODx.combo.Elements = function(config) { - config = config || {}; - Ext.applyIf(config,{ - name: 'element' - ,hiddenName: 'element' - ,displayField: 'name' - ,valueField: 'id' - ,fields: ['id','name'] - ,pageSize: 20 - ,editable: false - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Element/GetListByClass' - ,element_class: 'MODX\\Revolution\\modSnippet' +MODx.combo.Elements = function(config = {}) { + Ext.applyIf(config, { + name: 'element', + hiddenName: 'element', + displayField: 'name', + valueField: 'id', + fields: ['id', 'name'], + pageSize: 20, + editable: false, + url: MODx.config.connector_url, + baseParams: { + action: 'Element/GetListByClass', + element_class: 'MODX\\Revolution\\modSnippet' } }); - MODx.combo.Elements.superclass.constructor.call(this,config); + MODx.combo.Elements.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.Elements,MODx.combo.ComboBox); -Ext.reg('modx-combo-elements',MODx.combo.Elements); +Ext.extend(MODx.combo.Elements, MODx.combo.ComboBox); +Ext.reg('modx-combo-elements', MODx.combo.Elements); /** * @class MODx.window.CreatePropertySet @@ -423,45 +461,43 @@ Ext.reg('modx-combo-elements',MODx.combo.Elements); * @param {Object} config An object of configuration properties * @xtype modx-window-property-set-create */ -MODx.window.CreatePropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertyset_create') - ,url: MODx.config.connector_url - ,baseParams: { +MODx.window.CreatePropertySet = function(config = {}) { + Ext.applyIf(config, { + title: _('propertyset_create'), + url: MODx.config.connector_url, + baseParams: { action: 'Element/PropertySet/Create' - } - ,autoHeight: true - ,fields: [{ - xtype: 'hidden' - ,name: 'id' - },{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,anchor: '100%' - ,allowBlank: false - ,maxLength: 50 - },{ - xtype: 'modx-combo-category' - ,fieldLabel: _('category') - ,name: 'category' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,anchor: '100%' - ,grow: true - ,maxLength: 255 - }] - ,keys: [] + }, + autoHeight: true, + fields: [{ + xtype: 'hidden', + name: 'id' + }, { + xtype: 'textfield', + fieldLabel: _('name'), + name: 'name', + anchor: '100%', + allowBlank: false, + maxLength: 50 + }, { + xtype: 'modx-combo-category', + fieldLabel: _('category'), + name: 'category', + anchor: '100%' + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + anchor: '100%', + grow: true, + maxLength: 255 + }], + keys: [] }); - MODx.window.CreatePropertySet.superclass.constructor.call(this,config); + MODx.window.CreatePropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreatePropertySet,MODx.Window); -Ext.reg('modx-window-property-set-create',MODx.window.CreatePropertySet); +Ext.extend(MODx.window.CreatePropertySet, MODx.Window); +Ext.reg('modx-window-property-set-create', MODx.window.CreatePropertySet); /** * @class MODx.window.UpdatePropertySet @@ -469,19 +505,18 @@ Ext.reg('modx-window-property-set-create',MODx.window.CreatePropertySet); * @param {Object} config An object of configuration properties * @xtype modx-window-property-set-update */ -MODx.window.UpdatePropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertyset_update') - ,baseParams: { +MODx.window.UpdatePropertySet = function(config = {}) { + Ext.applyIf(config, { + title: _('propertyset_update'), + baseParams: { action: 'Element/PropertySet/Update' - } - ,autoHeight: true + }, + autoHeight: true }); - MODx.window.UpdatePropertySet.superclass.constructor.call(this,config); + MODx.window.UpdatePropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.UpdatePropertySet,MODx.window.CreatePropertySet); -Ext.reg('modx-window-property-set-update',MODx.window.UpdatePropertySet); +Ext.extend(MODx.window.UpdatePropertySet, MODx.window.CreatePropertySet); +Ext.reg('modx-window-property-set-update', MODx.window.UpdatePropertySet); /** * @class MODx.window.DuplicatePropertySet @@ -489,36 +524,35 @@ Ext.reg('modx-window-property-set-update',MODx.window.UpdatePropertySet); * @param {Object} config An object of configuration properties * @xtype modx-window-property-set-duplicate */ -MODx.window.DuplicatePropertySet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('propertyset_duplicate') - ,url: MODx.config.connector_url - ,baseParams: { +MODx.window.DuplicatePropertySet = function(config = {}) { + Ext.applyIf(config, { + title: _('propertyset_duplicate'), + url: MODx.config.connector_url, + baseParams: { action: 'Element/PropertySet/Duplicate' - } - ,autoHeight: true - ,fields: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-dpropset-id' - },{ - xtype: 'textfield' - ,fieldLabel: _('new_name') - ,name: 'name' - ,anchor: '100%' - ,value: _('duplicate_of',{name:config.record.name}) - ,maxLength: 50 - },{ - xtype: 'xcheckbox' - ,boxLabel: _('propertyset_duplicate_copyels') - ,hideLabel: true - ,name: 'copyels' - ,id: 'modx-dpropset-copyels' - ,checked: true + }, + autoHeight: true, + fields: [{ + xtype: 'hidden', + name: 'id', + id: 'modx-dpropset-id' + }, { + xtype: 'textfield', + fieldLabel: _('new_name'), + name: 'name', + anchor: '100%', + value: _('duplicate_of', { name: config.record.name }), + maxLength: 50 + }, { + xtype: 'xcheckbox', + boxLabel: _('propertyset_duplicate_copyels'), + hideLabel: true, + name: 'copyels', + id: 'modx-dpropset-copyels', + checked: true }] }); - MODx.window.DuplicatePropertySet.superclass.constructor.call(this,config); + MODx.window.DuplicatePropertySet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.DuplicatePropertySet,MODx.Window); -Ext.reg('modx-window-property-set-duplicate',MODx.window.DuplicatePropertySet); +Ext.extend(MODx.window.DuplicatePropertySet, MODx.Window); +Ext.reg('modx-window-property-set-duplicate', MODx.window.DuplicatePropertySet); diff --git a/manager/assets/modext/widgets/element/modx.panel.tv.js b/manager/assets/modext/widgets/element/modx.panel.tv.js index 68480d95677..b02049f199d 100644 --- a/manager/assets/modext/widgets/element/modx.panel.tv.js +++ b/manager/assets/modext/widgets/element/modx.panel.tv.js @@ -1651,7 +1651,11 @@ MODx.grid.ElementSources = function(config = {}) { src.getStore().load(); Ext.applyIf(config, { id: 'modx-grid-element-sources', - fields: ['context_key', 'source', 'name'], + fields: [ + 'context_key', + 'source', + 'name' + ], showActionsColumn: false, autoHeight: true, primaryKey: 'id', @@ -1659,11 +1663,14 @@ MODx.grid.ElementSources = function(config = {}) { header: _('context'), dataIndex: 'context_key', renderer: { - fn: function(v, md, record) { - return this.renderLink(v, { - href: `?a=context/update&key=${v}`, - target: '_blank' - }); + fn: function(value, metaData, record) { + return this.userCanEditContexts + ? this.renderLink(value, { + href: `?a=context/update&key=${value}`, + target: '_blank' + }) + : value + ; }, scope: this } @@ -1676,11 +1683,14 @@ MODx.grid.ElementSources = function(config = {}) { }] }); MODx.grid.ElementSources.superclass.constructor.call(this, config); - this.propRecord = Ext.data.Record.create(['context_key', 'source']); + + this.propRecord = Ext.data.Record.create([ + 'context_key', + 'source' + ]); + + this.setUserCanEdit(['edit_tv', 'save_tv']); + this.setUserHasPermissions('editContexts', ['edit_context', 'save_context']); }; -Ext.extend(MODx.grid.ElementSources, MODx.grid.LocalGrid, { - getMenu: function() { - return []; - } -}); +Ext.extend(MODx.grid.ElementSources, MODx.grid.LocalGrid); Ext.reg('modx-grid-element-sources', MODx.grid.ElementSources); diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js index d397e471b2d..6ceab8488ba 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcprofile.js @@ -4,37 +4,39 @@ * @param {Object} config An object of configuration options * @xtype modx-panel-fc-profiles */ -MODx.panel.FCProfiles = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-fc-profiles' - ,cls: 'container' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('form_customization') - ,id: 'modx-fcp-header' - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('profiles') - ,autoHeight: true - ,layout: "form" - ,items: [{ - html: '

    '+_('form_customization_msg')+'

    ' - ,xtype: 'modx-description' - },{ - title: '' - ,preventRender: true - ,xtype: 'modx-grid-fc-profile' - ,cls:'main-wrapper' +MODx.panel.FCProfiles = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-fc-profiles', + cls: 'container', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [{ + html: _('form_customization'), + id: 'modx-fcp-header', + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('profiles'), + autoHeight: true, + layout: 'form', + items: [{ + html: `

    ${_('form_customization_msg')}

    `, + xtype: 'modx-description' + }, { + title: '', + preventRender: true, + xtype: 'modx-grid-fc-profile', + cls: 'main-wrapper' }] - }],{ + }], { id: 'modx-form-customization-tabs' })] }); - MODx.panel.FCProfiles.superclass.constructor.call(this,config); + MODx.panel.FCProfiles.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.FCProfiles,MODx.FormPanel); -Ext.reg('modx-panel-fc-profiles',MODx.panel.FCProfiles); +Ext.extend(MODx.panel.FCProfiles, MODx.FormPanel); +Ext.reg('modx-panel-fc-profiles', MODx.panel.FCProfiles); /** * @class MODx.grid.FCProfile @@ -42,273 +44,276 @@ Ext.reg('modx-panel-fc-profiles',MODx.panel.FCProfiles); * @param {Object} config An object of configuration properties * @xtype modx-grid-fc-profile */ -MODx.grid.FCProfile = function(config) { - config = config || {}; +MODx.grid.FCProfile = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config,{ - id: 'modx-grid-fc-profile' - ,url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-fc-profile', + url: MODx.config.connector_url, + baseParams: { action: 'Security/Forms/Profile/GetList' - } - ,fields: [ + }, + fields: [ 'id', 'name', 'description', 'usergroups', 'active', 'rank', - 'sets', - 'perm' - ] - ,paging: true - ,autosave: true - ,save_action: 'Security/Forms/Profile/UpdateFromGrid' - ,sm: this.sm - ,remoteSort: true - ,columns: [this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 40 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - ,editor: { xtype: 'textfield' } - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/forms/profile/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 250 - ,sortable: true - ,editor: { xtype: 'textarea' } - },{ - header: _('usergroups') - ,dataIndex: 'usergroups' - ,width: 150 - }] - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec, ri, p){ - return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; + 'sets' + ], + paging: true, + autosave: true, + save_action: 'Security/Forms/Profile/UpdateFromGrid', + sm: this.sm, + remoteSort: true, + columns: [this.sm, { + header: _('id'), + dataIndex: 'id', + width: 40, + sortable: true + }, { + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + editor: { + xtype: 'textfield' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return this.renderLink(value, { + href: `?a=security/forms/profile/update&id=${record.data.id}` + }); + }, + scope: this } - } - ,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 - ,scope: this - },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] + }, { + header: _('description'), + dataIndex: 'description', + width: 250, + sortable: true, + editor: { + xtype: 'textarea' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this + } + }, { + header: _('usergroups'), + dataIndex: 'usergroups', + width: 150 + }], + tbar: [ + this.getCreateButton('profile', 'createProfile'), + this.getBulkActionsButton('profile', 'Security/Forms/Profile/RemoveMultiple', 'int', 'activate', 'deactivate'), '->', this.getQueryFilterField(), this.getClearFiltersButton() - ] + ], + viewConfig: this.getViewConfig(true, false, true) + }); + MODx.grid.FCProfile.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate']; + + // Note there are currently no action-specific, object-specific permissions for FC Profiles + this.setUserCanEdit(['customize_forms', 'save']); + this.setUserCanCreate(['customize_forms', 'save']); + this.setUserCanDelete(['customize_forms', 'remove']); + this.setShowActionsMenu(); + + this.on({ + render: function() { + this.getStore().reload(); + }, + beforeedit: function(e) { + if (!this.userCanEdit) { + return false; + } + } }); - MODx.grid.FCProfile.superclass.constructor.call(this,config); - this.on('render',function() { this.getStore().reload(); },this); }; -Ext.extend(MODx.grid.FCProfile,MODx.grid.Grid,{ +Ext.extend(MODx.grid.FCProfile, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.perm; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_activate') - ,handler: this.activateSelected - }); - m.push({ - text: _('selected_deactivate') - ,handler: this.deactivateSelected - }); - m.push('-'); - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - }); + const + model = this.getSelectionModel(), + record = model.getSelected(), + menu = [] + ; + if (model.getCount() > 1) { + if (this.userCanEdit) { + menu.push({ + text: _('selected_activate'), + handler: this.activateSelected + }); + menu.push({ + text: _('selected_deactivate'), + handler: this.deactivateSelected + }); + } + if (this.userCanDelete) { + menu.push('-'); + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected.bind(this, 'profile', 'Security/Forms/Profile/RemoveMultiple') + }); + } } else { - if (p.indexOf('pedit') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateProfile - },{ - text: _('duplicate') - ,handler: this.duplicateProfile - },'-'); - if (r.data.active) { - m.push({ - text: _('deactivate') - ,handler: this.deactivateProfile + if (this.userCanEdit) { + menu.push({ + text: _('edit'), + handler: this.updateProfile + }, { + text: _('duplicate'), + handler: this.duplicateProfile + }, '-'); + if (record.data.active) { + menu.push({ + text: _('deactivate'), + handler: this.deactivateProfile }); } else { - m.push({ - text: _('activate') - ,handler: this.activateProfile + menu.push({ + text: _('activate'), + handler: this.activateProfile }); } } - if (p.indexOf('premove') != -1) { - m.push('-',{ - text: _('delete') - ,handler: this.confirm.createDelegate(this,['Security/Forms/Profile/Remove','profile_remove_confirm']) + if (this.userCanDelete) { + menu.push('-', { + text: _('delete'), + handler: this.confirm.createDelegate(this, ['Security/Forms/Profile/Remove', 'profile_remove_confirm']) }); } } + return menu; + }, - if (m.length > 0) { - this.addContextMenuItem(m); - } - } - - ,createProfile: function(btn,e) { + createProfile: function(btn, e) { if (!this.windows.cpro) { this.windows.cpro = MODx.load({ - xtype: 'modx-window-fc-profile-create' - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + xtype: 'modx-window-fc-profile-create', + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); } this.windows.cpro.reset(); this.windows.cpro.show(e.target); - } + }, - ,updateProfile: function(btn,e) { - var r = this.menu.record; - location.href = '?a=security/forms/profile/update&id='+r.id; - } + updateProfile: function(btn, e) { + const { record } = this.menu; + window.location.href = `?a=security/forms/profile/update&id=${record.id}`; + }, - ,duplicateProfile: function(btn,e) { + duplicateProfile: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'security/forms/profile/duplicate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'security/forms/profile/duplicate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } + }, - ,activateProfile: function(btn,e) { + activateProfile: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Profile/Activate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Profile/Activate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } + }, - ,deactivateProfile: function(btn,e) { + deactivateProfile: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Profile/Deactivate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Profile/Deactivate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } - - ,activateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + activateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Profile/ActivateMultiple' - ,profiles: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Profile/ActivateMultiple', + profiles: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; - } - - ,deactivateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + deactivateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Profile/DeactivateMultiple' - ,profiles: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('profile_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Security/Forms/Profile/RemoveMultiple' - ,profiles: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Profile/DeactivateMultiple', + profiles: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; } }); -Ext.reg('modx-grid-fc-profile',MODx.grid.FCProfile); +Ext.reg('modx-grid-fc-profile', MODx.grid.FCProfile); /** * @class MODx.window.CreateFCProfile @@ -316,39 +321,38 @@ Ext.reg('modx-grid-fc-profile',MODx.grid.FCProfile); * @param {Object} config An object of options. * @xtype modx-window-fc-profile-create */ -MODx.window.CreateFCProfile = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Security/Forms/Profile/Create' - ,fields: [{ - xtype: 'textfield' - ,name: 'name' - ,fieldLabel: _('name') - ,id: 'modx-fccp-name' - ,allowBlank: false - ,anchor: '100%' - },{ - xtype: 'textarea' - ,name: 'description' - ,fieldLabel: _('description') - ,id: 'modx-fccp-description' - ,anchor: '100%' - },{ - xtype: 'xcheckbox' - ,boxLabel: _('active') - ,hideLabel: true - ,name: 'active' - ,id: 'modx-fccp-active' - ,inputValue: 1 - ,value: 1 - ,checked: true - ,anchor: '100%' - }] - ,keys: [] +MODx.window.CreateFCProfile = function(config = {}) { + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + action: 'Security/Forms/Profile/Create', + formDefaults: { + anchor: '100%', + msgTarget: 'under', + validationEvent: 'change', + validateOnBlur: false + }, + fields: [{ + xtype: 'textfield', + name: 'name', + fieldLabel: _('name'), + allowBlank: false + }, { + xtype: 'textarea', + name: 'description', + fieldLabel: _('description') + }, { + xtype: 'xcheckbox', + boxLabel: _('active'), + hideLabel: true, + name: 'active', + inputValue: 1, + value: 1, + checked: true + }], + keys: [] }); - MODx.window.CreateFCProfile.superclass.constructor.call(this,config); + MODx.window.CreateFCProfile.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateFCProfile,MODx.Window); -Ext.reg('modx-window-fc-profile-create',MODx.window.CreateFCProfile); +Ext.extend(MODx.window.CreateFCProfile, MODx.Window); +Ext.reg('modx-window-fc-profile-create', MODx.window.CreateFCProfile); diff --git a/manager/assets/modext/widgets/fc/modx.grid.fcset.js b/manager/assets/modext/widgets/fc/modx.grid.fcset.js index c4f95b1c740..68c036c82ee 100644 --- a/manager/assets/modext/widgets/fc/modx.grid.fcset.js +++ b/manager/assets/modext/widgets/fc/modx.grid.fcset.js @@ -1,12 +1,13 @@ MODx.grid.FCSet = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config,{ - id: 'modx-grid-fc-set' - ,url: MODx.config.connector_url - ,baseParams: { + const actionCombo = new MODx.combo.FCAction(); + Ext.applyIf(config, { + id: 'modx-grid-fc-set', + url: MODx.config.connector_url, + baseParams: { action: 'Security/Forms/Set/GetList' - } - ,fields: [ + }, + fields: [ 'id', 'profile', 'action', @@ -18,339 +19,381 @@ MODx.grid.FCSet = function(config = {}) { 'constraint', 'constraint_field', 'constraint_class', - 'rules', - 'perm' - ] - ,paging: true - ,autosave: true - ,preventSaveRefresh: false - ,save_action: 'Security/Forms/Set/UpdateFromGrid' - ,sm: this.sm - ,remoteSort: true - ,autoExpandColumn: 'controller' - ,columns: [this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 40 - ,sortable: true - },{ - header: _('action') - ,dataIndex: 'action' - ,width: 200 - ,editable: true - ,sortable: true - ,editor: { - xtype: 'modx-combo-fc-action', - renderer: true - } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 200 - ,editable: true - ,sortable: true - ,editor: { - xtype: 'textarea', - renderer: true + 'rules' + ], + paging: true, + autosave: true, + preventSaveRefresh: false, + save_action: 'Security/Forms/Set/UpdateFromGrid', + sm: this.sm, + remoteSort: true, + autoExpandColumn: 'controller', + columns: [this.sm, { + header: _('id'), + dataIndex: 'id', + width: 40, + sortable: true + }, { + header: _('template'), + dataIndex: 'template', + width: 150, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + let + displayValue = record.json.templatename, + linkDescripton = _('set_edit') + ; + if (Ext.isEmpty(record.json.templatename)) { + if (record.json.template > 0) { + displayValue = _('template_missing'); + linkDescripton += `\n${_('template_missing_desc')}`; + } else { + displayValue = _('template_empty'); + linkDescripton += `\n${_('template_empty_desc')}`; + } + } + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return this.renderLink(displayValue, { + href: `?a=security/forms/set/update&id=${record.id}`, + title: linkDescripton + }); + }, + scope: this } - },{ - header: _('template') - ,dataIndex: 'template' - ,width: 150 - ,sortable: true - ,editable: true - ,editor: { - xtype: 'modx-combo-template', - renderer: true + }, { + header: _('action'), + dataIndex: 'action', + width: 200, + sortable: true, + editor: actionCombo, + renderer: { + fn: function(value, metaData, record, rowIndex, colIndex) { + const actionRecord = actionCombo.findRecord(actionCombo.valueField, value); + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return actionRecord ? actionRecord.get(actionCombo.displayField) : value; + }, + scope: this } - },{ - header: _('constraint_field') - ,dataIndex: 'constraint_field' - ,width: 200 - ,editable: true - ,sortable: false - ,editor: { - xtype: 'textfield', - renderer: true + }, { + header: _('description'), + dataIndex: 'description', + width: 200, + sortable: true, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this } - },{ - header: _('constraint') - ,dataIndex: 'constraint' - ,width: 200 - ,editable: true - ,sortable: false - ,editor: { - xtype: 'textfield', - renderer: true + }, { + header: _('constraint_field'), + dataIndex: 'constraint_field', + width: 200, + sortable: false, + editor: { + xtype: 'textfield' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this } - }] - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec, ri, p){ - return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; + }, { + header: _('constraint'), + dataIndex: 'constraint', + width: 200, + sortable: false, + editor: { + xtype: 'textfield' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this } - } - ,tbar: [ + }], + tbar: [ + this.getCreateButton('set', 'createSet'), + this.getBulkActionsButton('set', 'Security/Forms/Set/RemoveMultiple', 'int', 'activate', 'deactivate'), { - text: _('create') - ,cls: 'primary-button' - ,scope: this - ,handler: this.createSet - },{ - 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 - }] - },{ - text: _('import') - ,handler: this.importSet - ,scope: this + text: _('import'), + handler: this.importSet, + scope: this, + listeners: { + render: { + fn: function(btn) { + if (!this.userCanEdit) { + btn.hide(); + } + }, + scope: this + } + } }, '->', this.getQueryFilterField(), this.getClearFiltersButton() - ] + ], + viewConfig: this.getViewConfig(true, false, true) + }); + MODx.grid.FCSet.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate']; + + // Note there are currently no action-specific, object-specific permissions for FC Sets + this.setUserCanEdit(['customize_forms', 'save']); + this.setUserCanCreate(['customize_forms', 'save']); + this.setUserCanDelete(['customize_forms', 'remove']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit) { + return false; + } + } }); - MODx.grid.FCSet.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.FCSet,MODx.grid.Grid,{ +Ext.extend(MODx.grid.FCSet, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.perm; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_activate') - ,handler: this.activateSelected - }); - m.push({ - text: _('selected_deactivate') - ,handler: this.deactivateSelected - }); - m.push('-'); - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - }); + const + model = this.getSelectionModel(), + record = model.getSelected(), + menu = [] + ; + if (model.getCount() > 1) { + if (this.userCanEdit) { + menu.push({ + text: _('selected_activate'), + handler: this.activateSelected + }); + menu.push({ + text: _('selected_deactivate'), + handler: this.deactivateSelected + }); + } + if (this.userCanDelete) { + menu.push('-'); + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected.bind(this, 'set', 'Security/Forms/Set/RemoveMultiple') + }); + } } else { - if (p.indexOf('pedit') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateSet + if (this.userCanEdit) { + menu.push({ + text: _('edit'), + handler: this.updateSet }); - m.push({ - text: _('duplicate') - ,handler: this.duplicateSet + menu.push({ + text: _('duplicate'), + handler: this.duplicateSet }); - m.push({ - text: _('export') - ,handler: this.exportSet + menu.push({ + text: _('export'), + handler: this.exportSet }); - m.push('-'); - if (r.data.active) { - m.push({ - text: _('deactivate') - ,handler: this.deactivateSet + menu.push('-'); + if (record.data.active) { + menu.push({ + text: _('deactivate'), + handler: this.deactivateSet }); } else { - m.push({ - text: _('activate') - ,handler: this.activateSet + menu.push({ + text: _('activate'), + handler: this.activateSet }); } } - if (p.indexOf('premove') != -1) { - m.push('-',{ - text: _('delete') - ,handler: this.confirm.createDelegate(this,['Security/Forms/Set/Remove','set_remove_confirm']) + if (this.userCanDelete) { + menu.push('-', { + text: _('delete'), + handler: this.confirm.createDelegate(this, ['Security/Forms/Set/Remove', 'set_remove_confirm']) }); } } + return menu; + }, - if (m.length > 0) { - this.addContextMenuItem(m); - } - } - - ,exportSet: function(btn,e) { - var id = this.menu.record.id; + exportSet: function(btn, e) { + const { id } = this.menu.record; MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Set/Export' - ,id: id - } - ,listeners: { - 'success': {fn:function(r) { - location.href = this.config.url+'?action=Security/Forms/Set/Export&download='+r.message+'&id='+id+'&HTTP_MODAUTH='+MODx.siteId; - },scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Set/Export', + id: id + }, + listeners: { + success: { + fn: function(response) { + window.location.href = `${this.config.url}?action=Security/Forms/Set/Export&download=${response.message}&id=${id}&HTTP_MODAUTH=${MODx.siteId}`; + }, + scope: this + } } }); - } + }, - ,importSet: function(btn,e) { - var r = { + importSet: function(btn, e) { + const record = { profile: MODx.request.id }; if (!this.windows.impset) { this.windows.impset = MODx.load({ - xtype: 'modx-window-fc-set-import' - ,record: r - ,listeners: { - 'success': {fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-fc-set-import', + record: record, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); } this.windows.impset.reset(); - this.windows.impset.setValues(r); + this.windows.impset.setValues(record); this.windows.impset.show(e.target); - } + }, - ,createSet: function(btn,e) { - var r = { - profile: MODx.request.id - ,active: true + createSet: function(btn, e) { + const record = { + profile: MODx.request.id, + active: true }; if (!this.windows.cset) { this.windows.cset = MODx.load({ - xtype: 'modx-window-fc-set-create' - ,record: r - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + xtype: 'modx-window-fc-set-create', + record: record, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); } this.windows.cset.reset(); - this.windows.cset.setValues(r); + this.windows.cset.setValues(record); this.windows.cset.show(e.target); - } + }, - ,updateSet: function(btn,e) { - var r = this.menu.record; - location.href = '?a=security/forms/set/update&id='+r.id; - } + updateSet: function(btn, e) { + const { record } = this.menu; + window.location.href = `?a=security/forms/set/update&id=${record.id}`; + }, - ,duplicateSet: function(btn,e) { + duplicateSet: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'security/forms/set/duplicate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'security/forms/set/duplicate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } + }, - ,activateSet: function(btn,e) { + activateSet: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Set/Activate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Set/Activate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } + }, - ,deactivateSet: function(btn,e) { + deactivateSet: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Set/Deactivate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Set/Deactivate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - } - - ,activateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + activateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Set/ActivateMultiple' - ,sets: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Set/ActivateMultiple', + sets: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; - } - - ,deactivateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + deactivateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Forms/Set/DeactivateMultiple' - ,sets: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('set_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Security/Forms/Set/RemoveMultiple' - ,sets: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/Forms/Set/DeactivateMultiple', + sets: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; } }); -Ext.reg('modx-grid-fc-set',MODx.grid.FCSet); +Ext.reg('modx-grid-fc-set', MODx.grid.FCSet); /** * @class MODx.window.CreateFCSet @@ -360,85 +403,85 @@ Ext.reg('modx-grid-fc-set',MODx.grid.FCSet); */ MODx.window.CreateFCSet = function(config = {}) { Ext.applyIf(config, { - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Security/Forms/Set/Create' - ,width: 600 - ,fields: [{ - xtype: 'hidden' - ,name: 'profile' - ,value: MODx.request.id - },{ - xtype: 'hidden' - ,fieldLabel: _('constraint_class') - ,name: 'constraint_class' - ,allowBlank: true - ,value: 'MODX\\Revolution\\modResource' - },{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,border: false - } - ,items: [{ - columnWidth: .5 - ,defaults: { + title: _('create'), + url: MODx.config.connector_url, + action: 'Security/Forms/Set/Create', + width: 600, + fields: [{ + xtype: 'hidden', + name: 'profile', + value: MODx.request.id + }, { + xtype: 'hidden', + fieldLabel: _('constraint_class'), + name: 'constraint_class', + allowBlank: true, + value: 'MODX\\Revolution\\modResource' + }, { + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelAlign: 'top', + border: false + }, + items: [{ + columnWidth: 0.5, + defaults: { anchor: '100%', msgTarget: 'under', validationEvent: 'change', validateOnBlur: false - } - ,items: [{ - fieldLabel: _('action') - ,name: 'action_id' - ,hiddenName: 'action_id' - ,id: 'modx-fcsc-action' - ,xtype: 'modx-combo-fc-action' - ,editable: false - ,allowBlank: false - },{ - xtype: 'textarea' - ,name: 'description' - ,fieldLabel: _('description') - ,id: 'modx-fcsc-description' - },{ - xtype: 'xcheckbox' - ,boxLabel: _('active') - ,hideLabel: true - ,name: 'active' - ,inputValue: 1 - ,value: 1 - ,checked: true + }, + items: [{ + fieldLabel: _('action'), + name: 'action_id', + hiddenName: 'action_id', + id: 'modx-fcsc-action', + xtype: 'modx-combo-fc-action', + editable: false, + allowBlank: false + }, { + xtype: 'textarea', + name: 'description', + fieldLabel: _('description'), + id: 'modx-fcsc-description' + }, { + xtype: 'xcheckbox', + boxLabel: _('active'), + hideLabel: true, + name: 'active', + inputValue: 1, + value: 1, + checked: true }] - },{ - columnWidth: .5 - ,defaults: { + }, { + columnWidth: 0.5, + defaults: { anchor: '100%', msgTarget: 'under', validationEvent: 'change', validateOnBlur: false - } - ,items: [{ - xtype: 'modx-combo-template' - ,name: 'template' - ,hiddenName: 'template' - ,fieldLabel: _('template') - ,description: MODx.expandHelp ? '' : _('set_template_desc') - ,id: 'modx-fcsc-template' - ,baseParams: { action: 'Element/Template/GetList', combo: true } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-fcsc-template' - ,html: _('set_template_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('constraint_field') - ,description: MODx.expandHelp ? '' : _('set_constraint_field_desc') - ,name: 'constraint_field' - ,listeners: { + }, + items: [{ + xtype: 'modx-combo-template', + name: 'template', + hiddenName: 'template', + fieldLabel: _('template'), + description: MODx.expandHelp ? '' : _('set_template_desc'), + id: 'modx-fcsc-template', + baseParams: { action: 'Element/Template/GetList', combo: true } + }, { + xtype: MODx.expandHelp ? 'label' : 'hidden', + forId: 'modx-fcsc-template', + html: _('set_template_desc'), + cls: 'desc-under' + }, { + xtype: 'textfield', + fieldLabel: _('constraint_field'), + description: MODx.expandHelp ? '' : _('set_constraint_field_desc'), + name: 'constraint_field', + listeners: { change: { fn: function(cmp, newValue, oldValue) { if (!Ext.isEmpty(newValue)) { @@ -451,16 +494,16 @@ MODx.window.CreateFCSet = function(config = {}) { scope: this } } - },{ - xtype: MODx.expandHelp ? 'box' : 'hidden' - ,html: _('set_constraint_field_desc') - ,cls: 'desc-under' - },{ - xtype: 'textfield' - ,fieldLabel: _('constraint') - ,description: MODx.expandHelp ? '' : _('set_constraint_desc') - ,name: 'constraint' - ,listeners: { + }, { + xtype: MODx.expandHelp ? 'box' : 'hidden', + html: _('set_constraint_field_desc'), + cls: 'desc-under' + }, { + xtype: 'textfield', + fieldLabel: _('constraint'), + description: MODx.expandHelp ? '' : _('set_constraint_desc'), + name: 'constraint', + listeners: { change: { fn: function(cmp, newValue, oldValue) { if (!Ext.isEmpty(newValue)) { @@ -473,19 +516,19 @@ MODx.window.CreateFCSet = function(config = {}) { scope: this } } - },{ - xtype: MODx.expandHelp ? 'box' : 'hidden' - ,html: _('set_constraint_desc') - ,cls: 'desc-under' + }, { + xtype: MODx.expandHelp ? 'box' : 'hidden', + html: _('set_constraint_desc'), + cls: 'desc-under' }] }] - }] - ,keys: [] + }], + keys: [] }); - MODx.window.CreateFCSet.superclass.constructor.call(this,config); + MODx.window.CreateFCSet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateFCSet,MODx.Window); -Ext.reg('modx-window-fc-set-create',MODx.window.CreateFCSet); +Ext.extend(MODx.window.CreateFCSet, MODx.Window); +Ext.reg('modx-window-fc-set-create', MODx.window.CreateFCSet); /** * @class MODx.window.ImportFCSet @@ -493,34 +536,33 @@ Ext.reg('modx-window-fc-set-create',MODx.window.CreateFCSet); * @param {Object} config An object of options. * @xtype modx-window-fc-set-import */ -MODx.window.ImportFCSet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('import') - ,id: 'modx-window-fc-set-import' - ,url: MODx.config.connector_url - ,action: 'Security/Forms/Set/Import' - ,fileUpload: true - ,saveBtnText: _('import') - ,fields: [{ - xtype: 'hidden' - ,name: 'profile' - ,value: MODx.request.id - },{ - html: _('set_import_msg') - ,id: 'modx-impset-desc' - ,xtype: 'modx-description' - ,style: 'margin-bottom: 10px;' - },{ - xtype: 'fileuploadfield' - ,fieldLabel: _('file') - ,buttonText: _('upload.buttons.upload') - ,name: 'file' - ,id: 'modx-impset-file' - ,anchor: '100%' +MODx.window.ImportFCSet = function(config = {}) { + Ext.applyIf(config, { + title: _('import'), + id: 'modx-window-fc-set-import', + url: MODx.config.connector_url, + action: 'Security/Forms/Set/Import', + fileUpload: true, + saveBtnText: _('import'), + fields: [{ + xtype: 'hidden', + name: 'profile', + value: MODx.request.id + }, { + html: _('set_import_msg'), + id: 'modx-impset-desc', + xtype: 'modx-description', + style: 'margin-bottom: 10px;' + }, { + xtype: 'fileuploadfield', + fieldLabel: _('file'), + buttonText: _('upload.buttons.upload'), + name: 'file', + id: 'modx-impset-file', + anchor: '100%' }] }); - MODx.window.ImportFCSet.superclass.constructor.call(this,config); + MODx.window.ImportFCSet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.ImportFCSet,MODx.Window); -Ext.reg('modx-window-fc-set-import',MODx.window.ImportFCSet); +Ext.extend(MODx.window.ImportFCSet, MODx.Window); +Ext.reg('modx-window-fc-set-import', MODx.window.ImportFCSet); diff --git a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js index 002b5e6ed8d..5b288d76b24 100644 --- a/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js +++ b/manager/assets/modext/widgets/fc/modx.panel.fcprofile.js @@ -4,140 +4,142 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-fc-profile */ -MODx.panel.FCProfile = function(config) { - config = config || {}; - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { +MODx.panel.FCProfile = function(config = {}) { + Ext.applyIf(config, { + url: MODx.config.connector_url, + baseParams: { action: 'Security/Forms/Profile/Update' - } - ,id: 'modx-panel-fc-profile' - ,cls: 'container' - ,class_key: 'MODX\\Revolution\\modFormCustomizationProfile' - ,bodyStyle: '' - ,items: [this.getPageHeader(config), MODx.getPageStructure([{ - title: _('profile') - ,defaults: { border: false ,msgTarget: 'side' } - ,layout: 'form' - ,id: 'modx-fcp-form' - ,labelWidth: 150 - ,items: [{ - html: '

    '+_('profile_msg')+'

    ' - ,id: 'modx-fcp-msg' - ,xtype: 'modx-description' - },{ - xtype: 'panel' - ,border: false - ,cls:'main-wrapper' - ,layout: 'form' - ,items: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-fcp-id' - ,value: config.record.id || MODx.request.id - },{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,id: 'modx-fcp-name' - ,anchor: '100%' - ,maxLength: 191 - ,enableKeyEvents: true - ,allowBlank: false - ,value: config.record.name - ,listeners: { - 'keyup': {scope:this,fn:function(f,e) { - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); - }} + }, + id: 'modx-panel-fc-profile', + cls: 'container', + class_key: 'MODX\\Revolution\\modFormCustomizationProfile', + bodyStyle: '', + items: [this.getPageHeader(config), MODx.getPageStructure([{ + title: _('profile'), + defaults: { border: false, msgTarget: 'side' }, + layout: 'form', + id: 'modx-fcp-form', + labelWidth: 150, + items: [{ + html: `

    ${_('profile_msg')}

    `, + id: 'modx-fcp-msg', + xtype: 'modx-description' + }, { + xtype: 'panel', + border: false, + cls: 'main-wrapper', + layout: 'form', + items: [{ + xtype: 'hidden', + name: 'id', + id: 'modx-fcp-id', + value: config.record.id || MODx.request.id + }, { + xtype: 'textfield', + fieldLabel: _('name'), + name: 'name', + id: 'modx-fcp-name', + anchor: '100%', + maxLength: 191, + enableKeyEvents: true, + allowBlank: false, + value: config.record.name, + listeners: { + keyup: { + fn: function(f, e) { + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); + }, + scope: this + } } - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,id: 'modx-fcp-description' - ,anchor: '100%' - ,maxLength: 255 - ,grow: false - ,value: config.record.description - },{ - xtype: 'xcheckbox' - ,fieldLabel: _('active') - ,name: 'active' - ,id: 'modx-fcp-active' - ,inputValue: true - ,value: config.record.active ? true : false - ,anchor: '100%' - ,allowBlank: true + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + id: 'modx-fcp-description', + anchor: '100%', + maxLength: 255, + grow: false, + value: config.record.description + }, { + xtype: 'xcheckbox', + fieldLabel: _('active'), + ctCls: 'display-switch', + name: 'active', + id: 'modx-fcp-active', + inputValue: true, + value: Boolean(config.record.active), + anchor: '100%' }] - },{ - xtype: 'modx-grid-fc-set' - ,cls:'main-wrapper' - ,baseParams: { - action: 'Security/Forms/Set/GetList' - ,profile: config.record.id - } - ,preventRender: true + }, { + xtype: 'modx-grid-fc-set', + cls: 'main-wrapper', + baseParams: { + action: 'Security/Forms/Set/GetList', + profile: config.record.id + }, + preventRender: true }] - },{ - title: _('usergroups') - ,layout: 'anchor' - ,items: [{ - html: '

    '+_('profile_usergroups_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-fc-profile-usergroups' - ,cls:'main-wrapper' - ,data: config.record.usergroups || [] - ,preventRender: true + }, { + title: _('usergroups'), + layout: 'anchor', + items: [{ + html: `

    ${_('profile_usergroups_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-fc-profile-usergroups', + cls: 'main-wrapper', + data: config.record.usergroups || [], + preventRender: true }] - }],{ + }], { id: 'modx-fc-profile-tabs' - })] - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + })], + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.FCProfile.superclass.constructor.call(this,config); + MODx.panel.FCProfile.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.FCProfile,MODx.FormPanel,{ - initialized: false +Ext.extend(MODx.panel.FCProfile, MODx.FormPanel, { + initialized: false, - ,setup: function() { + setup: function() { if (!this.initialized) { this.getForm().setValues(this.config.record); } if (!Ext.isEmpty(this.config.record.name)) { Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(this.config.record.name)); } - this.fireEvent('ready',this.config.record); + this.fireEvent('ready', this.config.record); this.clearDirty(); this.initialized = true; MODx.fireEvent('ready'); return true; - } + }, - ,beforeSubmit: function(o) { - Ext.apply(o.form.baseParams,{ + beforeSubmit: function(o) { + Ext.apply(o.form.baseParams, { usergroups: Ext.getCmp('modx-grid-fc-profile-usergroups').encode() }); - return this.fireEvent('save',{ + return this.fireEvent('save', { values: this.getForm().getValues() }); - } + }, - ,success: function(r) { + success: function(r) { Ext.getCmp('modx-grid-fc-profile-usergroups').getStore().commitChanges(); this.getForm().setValues(r.result.object); - } + }, - ,getPageHeader: function(config) { + getPageHeader: function(config) { return MODx.util.getHeaderBreadCrumbs('modx-fcp-header', [{ text: _('form_customization'), href: MODx.getPage('security/forms') }]); } }); -Ext.reg('modx-panel-fc-profile',MODx.panel.FCProfile); +Ext.reg('modx-panel-fc-profile', MODx.panel.FCProfile); /** * @class MODx.grid.FCProfileUserGroups @@ -145,65 +147,79 @@ Ext.reg('modx-panel-fc-profile',MODx.panel.FCProfile); * @param {Object} config An object of configuration properties * @xtype modx-grid-fc-profile-usergroups */ -MODx.grid.FCProfileUserGroups = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-grid-fc-profile-usergroups' - ,fields: ['id','name'] - ,autoHeight: true - ,stateful: false - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/usergroup/update&id=' + record.data.id - ,target: '_blank' - }); - }, scope: this } - }] - ,tbar: [{ - text: _('usergroup_create') - ,cls: 'primary-button' - ,handler: this.addUserGroup - ,scope: this +MODx.grid.FCProfileUserGroups = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-grid-fc-profile-usergroups', + fields: [ + 'id', + 'name' + ], + autoHeight: true, + stateful: false, + columns: [{ + header: _('name'), + dataIndex: 'name', + renderer: { + fn: function(value, metaData, record) { + const canEditGroups = MODx.perm.usergroup_edit && MODx.perm.usergroup_save; + return canEditGroups + ? this.renderLink(value, { + href: `?a=security/usergroup/update&id=${record.data.id}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }], + tbar: [{ + text: _('usergroup_create'), + cls: 'primary-button', + handler: this.addUserGroup, + scope: this }] }); - MODx.grid.FCProfileUserGroups.superclass.constructor.call(this,config); + MODx.grid.FCProfileUserGroups.superclass.constructor.call(this, config); this.fcugRecord = Ext.data.Record.create(config.fields); }; -Ext.extend(MODx.grid.FCProfileUserGroups,MODx.grid.LocalGrid,{ - getMenu: function(g,ri) { +Ext.extend(MODx.grid.FCProfileUserGroups, MODx.grid.LocalGrid, { + getMenu: function(g, ri) { return [{ - text: _('usergroup_remove') - ,handler: this.removeUserGroup - ,scope: this + text: _('usergroup_remove'), + handler: this.removeUserGroup, + scope: this }]; - } + }, - ,addUserGroup: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-fc-profile-add-usergroup' - ,listeners: { - 'success': {fn:function(r) { - var s = this.getStore(); - var rec = new this.fcugRecord(r); - s.add(rec); - },scope:this} + addUserGroup: function(btn, e) { + this.loadWindow(btn, e, { + xtype: 'modx-window-fc-profile-add-usergroup', + listeners: { + success: { + fn: function(response) { + const + store = this.getStore(), + record = new this.fcugRecord(response) + ; + store.add(record); + }, + scope: this + } } }); - } + }, - ,removeUserGroup: function(btn,e) { - var rec = this.getSelectionModel().getSelected(); - Ext.Msg.confirm(_('usergroup_remove'),_('usergroup_remove_confirm'),function(e) { - if (e == 'yes') { - this.getStore().remove(rec); + removeUserGroup: function(btn, e) { + const record = this.getSelectionModel().getSelected(); + Ext.Msg.confirm(_('usergroup_remove'), _('usergroup_remove_confirm'), function(e) { + if (e === 'yes') { + this.getStore().remove(record); } - },this); + }, this); } }); -Ext.reg('modx-grid-fc-profile-usergroups',MODx.grid.FCProfileUserGroups); +Ext.reg('modx-grid-fc-profile-usergroups', MODx.grid.FCProfileUserGroups); /** * @class MODx.window.AddGroupToProfile @@ -211,40 +227,41 @@ Ext.reg('modx-grid-fc-profile-usergroups',MODx.grid.FCProfileUserGroups); * @param {Object} config An object of options. * @xtype modx-window-fc-profile-add-usergroup */ -MODx.window.AddGroupToProfile = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('usergroup_create') - ,fields: [{ - fieldLabel: _('user_group') - ,name: 'usergroup' - ,hiddenName: 'usergroup' - ,id: 'modx-fcaug-usergroup' - ,xtype: 'modx-combo-usergroup' - ,editable: false - ,allowBlank: false - ,anchor: '100%' +MODx.window.AddGroupToProfile = function(config = {}) { + Ext.applyIf(config, { + title: _('usergroup_create'), + fields: [{ + fieldLabel: _('user_group'), + name: 'usergroup', + hiddenName: 'usergroup', + id: 'modx-fcaug-usergroup', + xtype: 'modx-combo-usergroup', + editable: false, + allowBlank: false, + anchor: '100%' }] }); - MODx.window.AddGroupToProfile.superclass.constructor.call(this,config); + MODx.window.AddGroupToProfile.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.AddGroupToProfile,MODx.Window,{ +Ext.extend(MODx.window.AddGroupToProfile, MODx.Window, { submit: function() { - var rec = {}; - rec.id = Ext.getCmp('modx-fcaug-usergroup').getValue(); - rec.name = Ext.getCmp('modx-fcaug-usergroup').getRawValue(); + const + record = {}, + grid = Ext.getCmp('modx-grid-fc-profile-usergroups'), + store = grid.getStore(), + match = store.findExact('id', record.id) + ; + record.id = Ext.getCmp('modx-fcaug-usergroup').getValue(); + record.name = Ext.getCmp('modx-fcaug-usergroup').getRawValue(); - var g = Ext.getCmp('modx-grid-fc-profile-usergroups'); - var s = g.getStore(); - var v = s.findExact('id',rec.id); - if (v != '-1') { - MODx.msg.alert(_('error'),_('profile_usergroup_err_ae')); + if (match !== -1) { + MODx.msg.alert(_('error'), _('profile_usergroup_err_ae')); return false; } - this.fireEvent('success',rec); + this.fireEvent('success', record); this.hide(); return false; } }); -Ext.reg('modx-window-fc-profile-add-usergroup',MODx.window.AddGroupToProfile); +Ext.reg('modx-window-fc-profile-add-usergroup', MODx.window.AddGroupToProfile); diff --git a/manager/assets/modext/widgets/fc/modx.panel.fcset.js b/manager/assets/modext/widgets/fc/modx.panel.fcset.js index c02841caf41..4fbd1aed282 100644 --- a/manager/assets/modext/widgets/fc/modx.panel.fcset.js +++ b/manager/assets/modext/widgets/fc/modx.panel.fcset.js @@ -6,88 +6,95 @@ */ MODx.panel.FCSet = function(config = {}) { Ext.applyIf(config, { - url: MODx.config.connector_url - ,baseParams: { + url: MODx.config.connector_url, + baseParams: { action: 'Security/Forms/Set/Update' - } - ,id: 'modx-panel-fc-set' - ,class_key: 'MODX\\Revolution\\modFormCustomizationSet' - ,cls: 'container' - ,items: [this.getPageHeader(config), MODx.getPageStructure([{ - title: _('set_and_fields') - ,xtype: 'panel' - ,border: false - ,defaults: { border: false } - ,items: [{ - html: '

    '+_('set_msg')+'

    ' - ,id: 'modx-fcs-msg' - ,xtype: 'modx-description' - },{ - layout: 'form' - ,id: 'modx-fcs-form' - ,defaults: { - anchor: '100%' - ,msgTarget: 'under' - ,validationEvent: 'change' - ,validateOnBlur: false - } - ,cls: 'main-wrapper' - ,labelWidth: 150 - ,items: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-fcs-id' - ,value: config.record.id || MODx.request.id - },{ - xtype: 'modx-combo-fc-action' - ,fieldLabel: _('action') - ,name: 'action_id' - ,hiddenName: 'action_id' - ,id: 'modx-fcs-action' - ,allowBlank: false - ,value: config.record.action - ,listeners: { - 'select': {scope:this,fn:function(f,e) { - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getRawValue())); - }} - } - },{ - xtype: 'modx-combo-template' - ,fieldLabel: _('template') - ,description: _('set_template_desc') - ,name: 'template' - ,hiddenName: 'template' - ,value: config.record.template || 0 - ,lazyInit: false - ,lazyRender: false - ,baseParams: { - action: 'Element/Template/GetList' - ,combo: true + }, + id: 'modx-panel-fc-set', + class_key: 'MODX\\Revolution\\modFormCustomizationSet', + cls: 'container', + items: [this.getPageHeader(config), MODx.getPageStructure([{ + title: _('set_and_fields'), + xtype: 'panel', + border: false, + defaults: { border: false }, + items: [{ + html: `

    ${_('set_msg')}

    `, + id: 'modx-fcs-msg', + xtype: 'modx-description' + }, { + layout: 'form', + id: 'modx-fcs-form', + defaults: { + anchor: '100%', + msgTarget: 'under', + validationEvent: 'change', + validateOnBlur: false + }, + cls: 'main-wrapper', + labelWidth: 150, + items: [{ + xtype: 'hidden', + name: 'id', + id: 'modx-fcs-id', + value: config.record.id || MODx.request.id + }, { + xtype: 'modx-combo-fc-action', + fieldLabel: _('action'), + name: 'action_id', + hiddenName: 'action_id', + id: 'modx-fcs-action', + allowBlank: false, + value: config.record.action, + listeners: { + select: { + fn: function(f, e) { + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getRawValue())); + }, + scope: this + } } - ,listeners: { - 'select': {fn:this.changeTemplate,scope:this} + }, { + xtype: 'modx-combo-template', + fieldLabel: _('template'), + description: _('set_template_desc'), + name: 'template', + hiddenName: 'template', + value: config.record.template || 0, + valueNotFoundText: _('template_missing_reassign'), + lazyInit: false, + lazyRender: false, + baseParams: { + action: 'Element/Template/GetList', + combo: true + }, + listeners: { + select: { + fn: this.changeTemplate, + scope: this + } } - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,id: 'modx-fcs-description' - ,maxLength: 255 - ,grow: false - ,value: config.record.description - },{ - xtype: 'hidden' - ,fieldLabel: _('constraint_class') - ,name: 'constraint_class' - ,value: 'MODX\\Revolution\\modResource' - ,allowBlank: true - },{ - xtype: 'textfield' - ,fieldLabel: _('constraint_field') - ,description: _('set_constraint_field_desc') - ,name: 'constraint_field' - ,value: config.record.constraint_field - ,listeners: { + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + id: 'modx-fcs-description', + maxLength: 255, + grow: false, + value: config.record.description + }, { + xtype: 'hidden', + fieldLabel: _('constraint_class'), + name: 'constraint_class', + value: 'MODX\\Revolution\\modResource', + allowBlank: true + }, { + xtype: 'textfield', + fieldLabel: _('constraint_field'), + description: _('set_constraint_field_desc'), + name: 'constraint_field', + value: config.record.constraint_field, + listeners: { change: { fn: function(cmp, newValue, oldValue) { if (!Ext.isEmpty(newValue)) { @@ -100,13 +107,13 @@ MODx.panel.FCSet = function(config = {}) { scope: this } } - },{ - xtype: 'textfield' - ,fieldLabel: _('constraint') - ,description: _('set_constraint_desc') - ,name: 'constraint' - ,value: config.record.constraint - ,listeners: { + }, { + xtype: 'textfield', + fieldLabel: _('constraint'), + description: _('set_constraint_desc'), + name: 'constraint', + value: config.record.constraint, + listeners: { change: { fn: function(cmp, newValue, oldValue) { if (!Ext.isEmpty(newValue)) { @@ -119,125 +126,128 @@ MODx.panel.FCSet = function(config = {}) { scope: this } } - },{ - xtype: 'xcheckbox' - ,fieldLabel: _('active') - ,name: 'active' - ,inputValue: true - ,value: Boolean(config.record.active) + }, { + xtype: 'xcheckbox', + fieldLabel: _('active'), + ctCls: 'display-switch', + name: 'active', + inputValue: true, + value: Boolean(config.record.active) }] - },{ - html: '

    '+_('set_fields_msg')+'

    ' - ,xtype: 'modx-description' - },{ - id: 'modx-fcs-fields-form' - ,msgTarget: 'side' - ,cls: 'main-wrapper' - ,layout: 'anchor' - ,items: [{ - xtype: 'modx-grid-fc-set-fields' - ,data: config.record.fields || [] - ,preventRender: true + }, { + html: `

    ${_('set_fields_msg')}

    `, + xtype: 'modx-description' + }, { + id: 'modx-fcs-fields-form', + msgTarget: 'side', + cls: 'main-wrapper', + layout: 'anchor', + items: [{ + xtype: 'modx-grid-fc-set-fields', + data: config.record.fields || [], + preventRender: true }] }] - },{ - title: _('regions') - ,border: false - ,layout: 'anchor' - ,items: [{ - html: '

    '+_('set_tabs_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-fc-set-tabs' - ,cls: 'main-wrapper' - ,data: config.record.tabs || [] - ,preventRender: true + }, { + title: _('regions'), + border: false, + layout: 'anchor', + items: [{ + html: `

    ${_('set_tabs_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-fc-set-tabs', + cls: 'main-wrapper', + data: config.record.tabs || [], + preventRender: true }] - },{ - title: _('tvs') - ,border: false - ,layout: 'anchor' - ,items: [{ - html: '

    '+_('set_tvs_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-fc-set-tvs' - ,cls: 'main-wrapper' - ,data: config.record.tvs || [] - ,preventRender: true + }, { + title: _('tvs'), + border: false, + layout: 'anchor', + items: [{ + html: `

    ${_('set_tvs_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-fc-set-tvs', + cls: 'main-wrapper', + data: config.record.tvs || [], + preventRender: true }] - }],{ + }], { id: 'modx-fc-set-tabs' - })] - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + })], + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.FCSet.superclass.constructor.call(this,config); + MODx.panel.FCSet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.FCSet,MODx.FormPanel,{ - initialized: false +Ext.extend(MODx.panel.FCSet, MODx.FormPanel, { + initialized: false, - ,setup: function() { - if (!this.initialized) {this.getForm().setValues(this.config.record);} + setup: function() { + if (!this.initialized) { + this.getForm().setValues(this.config.record); + } Ext.getCmp('modx-header-breadcrumbs').updateHeader(_('set')); - this.fireEvent('ready',this.config.record); + this.fireEvent('ready', this.config.record); this.clearDirty(); this.initialized = true; MODx.fireEvent('ready'); return true; - } + }, - ,beforeSubmit: function(o) { - Ext.apply(o.form.baseParams,{ - fields: Ext.getCmp('modx-grid-fc-set-fields').encode() - ,tabs: Ext.getCmp('modx-grid-fc-set-tabs').encode() - ,tvs: Ext.getCmp('modx-grid-fc-set-tvs').encode() + beforeSubmit: function(o) { + Ext.apply(o.form.baseParams, { + fields: Ext.getCmp('modx-grid-fc-set-fields').encode(), + tabs: Ext.getCmp('modx-grid-fc-set-tabs').encode(), + tvs: Ext.getCmp('modx-grid-fc-set-tvs').encode() }); - return this.fireEvent('save',{ + return this.fireEvent('save', { values: this.getForm().getValues() }); - } + }, - ,success: function(r) { + success: function(r) { this.getForm().setValues(r.result.object); Ext.getCmp('modx-grid-fc-set-fields').getStore().commitChanges(); Ext.getCmp('modx-grid-fc-set-tabs').getStore().commitChanges(); Ext.getCmp('modx-grid-fc-set-tvs').getStore().commitChanges(); - } + }, - ,changeTemplate: function(cb) { - if (cb.getValue() != this.config.record.template) { - Ext.Msg.confirm(_('set_change_template'),_('set_change_template_confirm'),function(e) { - if (e == 'yes') { - this.on('success',function() { - location.href = location.href; - },this); + changeTemplate: function(cb) { + if (cb.getValue() !== this.config.record.template) { + Ext.Msg.confirm(_('set_change_template'), _('set_change_template_confirm'), function(e) { + if (e === 'yes') { + this.on('success', function() { + window.location.reload(); + }, this); this.submit(); } else { cb.setValue(this.config.record.template); } - },this); + }, this); } return false; - } + }, - ,getPageHeader: function(config) { - var profile = config.record.profile; + getPageHeader: function(config) { + const { profile } = config.record; return MODx.util.getHeaderBreadCrumbs('modx-fcs-header', [{ text: _('form_customization'), href: MODx.getPage('security/forms') - },{ + }, { text: _('profile'), - href: MODx.getPage('security/forms/profile/update&id='+profile) + href: MODx.getPage(`security/forms/profile/update&id=${profile}`) }]); } }); -Ext.reg('modx-panel-fc-set',MODx.panel.FCSet); +Ext.reg('modx-panel-fc-set', MODx.panel.FCSet); /** * @class MODx.grid.FCSetFields @@ -245,71 +255,72 @@ Ext.reg('modx-panel-fc-set',MODx.panel.FCSet); * @param {Object} config An object of configuration properties * @xtype modx-grid-fc-set-fields */ -MODx.grid.FCSetFields = function(config) { - config = config || {}; +MODx.grid.FCSetFields = function(config = {}) { this.vcb = new Ext.ux.grid.CheckColumn({ - header: _('visible') - ,dataIndex: 'visible' - ,width: 40 - ,sortable: false + header: _('visible'), + dataIndex: 'visible', + width: 40, + sortable: false }); - Ext.applyIf(config,{ - id: 'modx-grid-fc-set-fields' - ,showActionsColumn: false - ,fields: ['id','action','name','tab','tab_rank','other','rank','visible','label','default_value'] - ,autoHeight: true - ,grouping: true - ,groupBy: 'tab_rank' - ,plugins: [this.vcb] - ,stateful: false - ,remoteSort: false - ,sortBy: 'rank' - ,sortDir: 'ASC' - ,hideGroupedColumn: true - ,groupTextTpl: '{[values.rs[0].data.tab]} ({[values.rs.length]} {[values.rs.length > 1 ? "'+_('fields')+'" : "'+_('field')+'"]})' - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - },{ - header: _('region')+' ('+_('tab_id')+')' - ,dataIndex: 'tab' - ,width: 100 - },{ - header: _('tab_rank') - ,dataIndex: 'tab_rank' - ,width: 100 - },this.vcb,{ - header: _('label') - ,dataIndex: 'label' - ,editor: { xtype: 'textfield' } - ,renderer: function(v,md) { - return Ext.util.Format.htmlEncode(v); + Ext.applyIf(config, { + id: 'modx-grid-fc-set-fields', + showActionsColumn: false, + fields: [ + 'id', + 'action', + 'name', + 'tab', + 'tab_rank', + 'other', + 'rank', + 'visible', + 'label', + 'default_value' + ], + autoHeight: true, + grouping: true, + groupBy: 'tab_rank', + plugins: [this.vcb], + stateful: false, + remoteSort: false, + sortBy: 'rank', + sortDir: 'ASC', + hideGroupedColumn: true, + groupTextTpl: `{[values.rs[0].data.tab]} ({[values.rs.length]} {[values.rs.length > 1 ? "${_('fields')}" : "${_('field')}"]})`, + columns: [{ + header: _('name'), + dataIndex: 'name', + width: 200 + }, { + header: `${_('region')} (${_('tab_id')})`, + dataIndex: 'tab', + width: 100 + }, { + header: _('tab_rank'), + dataIndex: 'tab_rank', + width: 100 + }, this.vcb, { + header: _('label'), + dataIndex: 'label', + editor: { xtype: 'textfield' }, + renderer: function(value, metaData) { + return Ext.util.Format.htmlEncode(value); } - },{ - header: _('default_value') - ,dataIndex: 'default_value' - ,editor: { xtype: 'textfield' } - ,renderer: function(v,md) { + }, { + header: _('default_value'), + dataIndex: 'default_value', + editor: { xtype: 'textfield' }, + renderer: function(v, md) { return Ext.util.Format.htmlEncode(v); } - }] - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec, ri, p){ - return rec.data.visible ? 'grid-row-active' : 'grid-row-inactive'; - } - } + }], + viewConfig: this.getViewConfig(false, false, true) }); - MODx.grid.FCSetFields.superclass.constructor.call(this,config); + MODx.grid.FCSetFields.superclass.constructor.call(this, config); this.propRecord = Ext.data.Record.create(config.fields); }; -Ext.extend(MODx.grid.FCSetFields,MODx.grid.LocalGrid); -Ext.reg('modx-grid-fc-set-fields',MODx.grid.FCSetFields); +Ext.extend(MODx.grid.FCSetFields, MODx.grid.LocalGrid); +Ext.reg('modx-grid-fc-set-fields', MODx.grid.FCSetFields); /** * @class MODx.grid.FCSetTabs @@ -317,90 +328,95 @@ Ext.reg('modx-grid-fc-set-fields',MODx.grid.FCSetFields); * @param {Object} config An object of configuration properties * @xtype modx-grid-fc-set-tabs */ -MODx.grid.FCSetTabs = function(config) { - config = config || {}; +MODx.grid.FCSetTabs = function(config = {}) { this.vcb = new Ext.ux.grid.CheckColumn({ - header: _('visible') - ,dataIndex: 'visible' - ,width: 40 - ,sortable: false + header: _('visible'), + dataIndex: 'visible', + width: 40, + sortable: false }); - Ext.applyIf(config,{ - id: 'modx-grid-fc-set-tabs' - ,showActionsColumn: false - ,fields: ['id','action','name','form','other','rank','visible','label','type'] - ,autoHeight: true - ,plugins: [this.vcb] - ,stateful: false - ,columns: [{ - header: _('region')+' ('+_('tab_id')+')' - ,dataIndex: 'name' - ,width: 100 - },this.vcb,{ - header: _('tab_title') - ,dataIndex: 'label' - ,editor: { xtype: 'textfield' } - }] - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec, ri, p){ - return rec.data.visible ? 'grid-row-active' : 'grid-row-inactive'; - } - } - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,handler: this.createTab - ,scope: this + Ext.applyIf(config, { + id: 'modx-grid-fc-set-tabs', + showActionsColumn: false, + fields: [ + 'id', + 'action', + 'name', + 'form', + 'other', + 'rank', + 'visible', + 'label', + 'type' + ], + autoHeight: true, + plugins: [this.vcb], + stateful: false, + columns: [{ + header: `${_('region')} (${_('tab_id')})`, + dataIndex: 'name', + width: 100 + }, this.vcb, { + header: _('tab_title'), + dataIndex: 'label', + editor: { xtype: 'textfield' } + }], + viewConfig: this.getViewConfig(false, false, true), + tbar: [{ + text: _('create'), + cls: 'primary-button', + handler: this.createTab, + scope: this }] }); - MODx.grid.FCSetTabs.superclass.constructor.call(this,config); + MODx.grid.FCSetTabs.superclass.constructor.call(this, config); this.propRecord = Ext.data.Record.create(config.fields); }; -Ext.extend(MODx.grid.FCSetTabs,MODx.grid.LocalGrid,{ - getMenu: function(g,ri) { - var rec = this.getStore().getAt(ri); - if (rec.data.type == 'new') { +Ext.extend(MODx.grid.FCSetTabs, MODx.grid.LocalGrid, { + getMenu: function(g, ri) { + const record = this.getStore().getAt(ri); + if (record.data.type === 'new') { return [{ - text: _('delete') - ,handler: this.removeTab - ,scope: this + text: _('delete'), + handler: this.removeTab, + scope: this }]; } return []; - } + }, - ,createTab: function(btn,e) { + createTab: function(btn, e) { if (!this.windows.ctab) { this.windows.ctab = MODx.load({ - xtype: 'modx-window-fc-set-add-tab' - ,listeners: { - 'success': {fn:function(r) { - var s = this.getStore(); - var rec = new this.propRecord(r); - s.add(rec); - },scope:this} + xtype: 'modx-window-fc-set-add-tab', + listeners: { + success: { + fn: function(response) { + const + store = this.getStore(), + record = new this.propRecord(response) + ; + store.add(record); + }, + scope: this + } } }); } this.windows.ctab.reset(); this.windows.ctab.show(e.target); - } + }, - ,removeTab: function(btn,e) { - var rec = this.getSelectionModel().getSelected(); - Ext.Msg.confirm(_('delete'),_('tab_remove_confirm'),function(e) { - if (e == 'yes') { - this.getStore().remove(rec); + removeTab: function(btn, e) { + const record = this.getSelectionModel().getSelected(); + Ext.Msg.confirm(_('delete'), _('tab_remove_confirm'), function(e) { + if (e === 'yes') { + this.getStore().remove(record); } - },this); + }, this); } }); -Ext.reg('modx-grid-fc-set-tabs',MODx.grid.FCSetTabs); +Ext.reg('modx-grid-fc-set-tabs', MODx.grid.FCSetTabs); /** * @class MODx.grid.FCSetTVs @@ -408,81 +424,88 @@ Ext.reg('modx-grid-fc-set-tabs',MODx.grid.FCSetTabs); * @param {Object} config An object of configuration properties * @xtype modx-grid-fc-set-tvs */ -MODx.grid.FCSetTVs = function(config) { - config = config || {}; +MODx.grid.FCSetTVs = function(config = {}) { this.vcb = new Ext.ux.grid.CheckColumn({ - header: _('visible') - ,dataIndex: 'visible' - ,width: 40 - ,sortable: false + header: _('visible'), + dataIndex: 'visible', + width: 40, + sortable: false }); - Ext.applyIf(config,{ - id: 'modx-grid-fc-set-tvs' - ,showActionsColumn: false - ,fields: ['id','name','tab','rank','visible','label','default_value','category','default_text'] - ,autoHeight: true - ,grouping: true - ,groupBy: 'category' - ,sortBy: 'rank' - ,sortDir: 'ASC' - ,stateful: false - ,groupTextTpl: '{group} ({[values.rs.length]} {[values.rs.length > 1 ? "'+_('tvs')+'" : "'+_('tv')+'"]})' - ,plugins: [this.vcb] - ,hideGroupedColumn: true - ,columns: [{ - header: _('category') - ,dataIndex: 'category' - },{ - header: _('tv_name') - ,dataIndex: 'name' - ,width: 200 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=element/tv/update&id=' + record.data.id - ,target: '_blank' - }); - }, scope: this } - },this.vcb,{ - header: _('label') - ,dataIndex: 'label' - ,editor: { xtype: 'textfield' } - },{ - header: _('default_value') - ,dataIndex: 'default_value' - ,editor: { xtype: 'textfield' } - ,renderer: function(v) { return Ext.util.Format.htmlEncode(v); } - },{ - header: _('original_value') - ,dataIndex: 'default_text' - ,editable: false - },{ - header: _('region')+' ('+_('tab_id')+')' - ,dataIndex: 'tab' - ,width: 100 - ,editor: { xtype: 'textfield' } - },{ - header: _('tab_rank') - ,dataIndex: 'rank' - ,width: 70 - ,editor: { xtype: 'textfield' } - }] - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec, ri, p){ - return rec.data.visible ? 'grid-row-active' : 'grid-row-inactive'; + Ext.applyIf(config, { + id: 'modx-grid-fc-set-tvs', + showActionsColumn: false, + fields: [ + 'id', + 'name', + 'tab', + 'rank', + 'visible', + 'label', + 'default_value', + 'category', + 'default_text' + ], + autoHeight: true, + grouping: true, + groupBy: 'category', + sortBy: 'rank', + sortDir: 'ASC', + stateful: false, + groupTextTpl: `{group} ({[values.rs.length]} {[values.rs.length > 1 ? "${_('tvs')}" : "${_('tv')}"]})`, + plugins: [this.vcb], + hideGroupedColumn: true, + columns: [{ + header: _('category'), + dataIndex: 'category' + }, { + header: _('tv_name'), + dataIndex: 'name', + width: 200, + renderer: { + fn: function(value, metaData, record) { + const canEditTvs = MODx.perm.edit_tv && MODx.perm.save_tv; + return canEditTvs + ? this.renderLink(value, { + href: `?a=element/tv/update&id=${record.data.id}`, + target: '_blank' + }) + : value + ; + }, + scope: this } - } + }, this.vcb, { + header: _('label'), + dataIndex: 'label', + editor: { xtype: 'textfield' } + }, { + header: _('default_value'), + dataIndex: 'default_value', + editor: { xtype: 'textfield' }, + renderer: function(v) { return Ext.util.Format.htmlEncode(v); } + }, { + header: _('original_value'), + dataIndex: 'default_text', + editable: false + }, { + header: `${_('region')} (${_('tab_id')})`, + dataIndex: 'tab', + width: 100, + editor: { xtype: 'textfield' } + }, { + header: _('tab_rank'), + dataIndex: 'rank', + width: 70, + editor: { xtype: 'textfield' } + }], + viewConfig: this.getViewConfig(false, false, true) }); - MODx.grid.FCSetTVs.superclass.constructor.call(this,config); + MODx.grid.FCSetTVs.superclass.constructor.call(this, config); this.propRecord = Ext.data.Record.create(config.fields); }; -Ext.extend(MODx.grid.FCSetTVs,MODx.grid.LocalGrid,{ +Ext.extend(MODx.grid.FCSetTVs, MODx.grid.LocalGrid, { }); -Ext.reg('modx-grid-fc-set-tvs',MODx.grid.FCSetTVs); +Ext.reg('modx-grid-fc-set-tvs', MODx.grid.FCSetTVs); /** * @class MODx.window.AddTabToSet @@ -490,58 +513,58 @@ Ext.reg('modx-grid-fc-set-tvs',MODx.grid.FCSetTVs); * @param {Object} config An object of options. * @xtype modx-window-fc-set-add-tab */ -MODx.window.AddTabToSet = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('create') - ,fields: [{ - xtype: 'hidden' - ,name: 'container' - ,value: 'modx-resource-tabs' - },{ - xtype: 'hidden' - ,name: 'visible' - ,value: true - },{ - xtype: 'hidden' - ,name: 'type' - ,value: 'new' - },{ - xtype: 'textfield' - ,name: 'name' - ,fieldLabel: _('region')+' ('+_('tab_id')+')' - ,id: 'modx-fcatab-id' - ,allowBlank: false - ,anchor: '100%' - },{ - xtype: 'textfield' - ,fieldLabel: _('tab_title') - ,name: 'value' - ,id: 'modx-fcatab-name' - ,allowBlank: false - ,anchor: '100%' +MODx.window.AddTabToSet = function(config = {}) { + Ext.applyIf(config, { + title: _('create'), + fields: [{ + xtype: 'hidden', + name: 'container', + value: 'modx-resource-tabs' + }, { + xtype: 'hidden', + name: 'visible', + value: true + }, { + xtype: 'hidden', + name: 'type', + value: 'new' + }, { + xtype: 'textfield', + name: 'name', + fieldLabel: `${_('region')} (${_('tab_id')})`, + id: 'modx-fcatab-id', + allowBlank: false, + anchor: '100%' + }, { + xtype: 'textfield', + fieldLabel: _('tab_title'), + name: 'value', + id: 'modx-fcatab-name', + allowBlank: false, + anchor: '100%' }] }); - MODx.window.AddTabToSet.superclass.constructor.call(this,config); + MODx.window.AddTabToSet.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.AddTabToSet,MODx.Window,{ +Ext.extend(MODx.window.AddTabToSet, MODx.Window, { submit: function() { - var rec = this.fp.getForm().getValues(); - - var g = Ext.getCmp('modx-grid-fc-set-tabs'); - var s = g.getStore(); - var v = s.query('name',rec.name).items; - if (v.length > 0) { - MODx.msg.alert(_('error'),_('set_tab_err_ae')); + const + record = this.fp.getForm().getValues(), + grid = Ext.getCmp('modx-grid-fc-set-tabs'), + store = grid.getStore(), + matches = store.query('name', record.name).items + ; + if (matches.length > 0) { + MODx.msg.alert(_('error'), _('set_tab_err_ae')); return false; } - rec.label = rec.value; - rec.visible = true; - rec.type = 'new'; + record.label = record.value; + record.visible = true; + record.type = 'new'; - this.fireEvent('success',rec); + this.fireEvent('success', record); this.hide(); return false; } }); -Ext.reg('modx-window-fc-set-add-tab',MODx.window.AddTabToSet); +Ext.reg('modx-window-fc-set-add-tab', MODx.window.AddTabToSet); diff --git a/manager/assets/modext/widgets/resource/modx.grid.resource.security.local.js b/manager/assets/modext/widgets/resource/modx.grid.resource.security.local.js index db27310fdd0..6f6cae30997 100644 --- a/manager/assets/modext/widgets/resource/modx.grid.resource.security.local.js +++ b/manager/assets/modext/widgets/resource/modx.grid.resource.security.local.js @@ -4,43 +4,56 @@ * @param {Object} config An object of configuration properties * @xtype modx-grid-resource-security */ -MODx.grid.ResourceSecurity = function(config) { - config = config || {}; - var ac = new Ext.ux.grid.CheckColumn({ - header: _('access') - ,dataIndex: 'access' - ,width: 40 - ,sortable: true - ,hidden: MODx.perm.resourcegroup_resource_edit != 1 +MODx.grid.ResourceSecurity = function(config = {}) { + const accessCheckboxCol = new Ext.ux.grid.CheckColumn({ + header: _('access'), + dataIndex: 'access', + width: 40, + sortable: true, + hidden: !MODx.perm.resourcegroup_resource_edit }); - Ext.applyIf(config,{ - id: 'modx-grid-resource-security' - ,fields: ['id','name','access'] - ,showActionsColumn: false - ,paging: false - ,remoteSort: false - ,autoHeight: true - ,plugins: ac - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/resourcegroup' - ,target: '_blank' - }); - }, scope: this } - },ac] + Ext.applyIf(config, { + id: 'modx-grid-resource-security', + fields: [ + 'id', + 'name', + 'access' + ], + showActionsColumn: false, + paging: false, + remoteSort: false, + autoHeight: true, + plugins: accessCheckboxCol, + columns: [ + { + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + const canEditResourceGroups = MODx.perm.resourcegroup_edit || MODx.perm.resourcegroup_resource_edit; + return canEditResourceGroups + ? this.renderLink(value, { + href: `?a=security/resourcegroup&id=${record.data.id}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, + accessCheckboxCol + ] }); - MODx.grid.ResourceSecurity.superclass.constructor.call(this,config); + MODx.grid.ResourceSecurity.superclass.constructor.call(this, config); this.propRecord = Ext.data.Record.create(config.fields); - this.on('rowclick',MODx.fireResourceFormChange); + this.on('rowclick', MODx.fireResourceFormChange); this.store.sortInfo = { field: 'access', direction: 'DESC' }; }; -Ext.extend(MODx.grid.ResourceSecurity,MODx.grid.LocalGrid); -Ext.reg('modx-grid-resource-security',MODx.grid.ResourceSecurity); +Ext.extend(MODx.grid.ResourceSecurity, MODx.grid.LocalGrid); +Ext.reg('modx-grid-resource-security', MODx.grid.ResourceSecurity); diff --git a/manager/assets/modext/widgets/resource/modx.grid.trash.js b/manager/assets/modext/widgets/resource/modx.grid.trash.js index 0849f12cbcb..bbd1e2f814f 100644 --- a/manager/assets/modext/widgets/resource/modx.grid.trash.js +++ b/manager/assets/modext/widgets/resource/modx.grid.trash.js @@ -16,8 +16,7 @@ MODx.grid.Trash = function(config = {}) { 'parentPath', 'deletedon', 'deletedby', - 'deletedby_name', - 'cls' + 'deletedby_name' ], paging: true, autosave: true, @@ -46,7 +45,23 @@ MODx.grid.Trash = function(config = {}) { dataIndex: 'published', width: 40, sortable: true, - editor: {xtype: 'combo-boolean', renderer: 'boolean'} + editor: { + xtype: 'combo-boolean' + }, + renderer: { + fn: function(value, metaData, record) { + /* + This field depends on permission other than the typicaledit, + thus not using the base setEditableCellClasses() method here + */ + if (!record.json.permissions.publish) { + // eslint-disable-next-line no-param-reassign + metaData.css = 'editor-disabled'; + } + return this.rendYesNo(value, metaData); + }, + scope: this + } }, { header: _('trash.deletedon_title'), dataIndex: 'deletedon', @@ -61,39 +76,57 @@ MODx.grid.Trash = function(config = {}) { return record.data.deletedby_name; } }], - tbar: [ + /* + Not using base getBulkActionsButton() method here, as this menu utilizes + methods/actions specific to this class not supported by that method + */ { text: _('bulk_actions'), + id: 'modx-btn-bulk-actions', menu: [{ text: _('trash.selected_purge'), + itemId: 'modx-bulk-menu-opt-purge', handler: this.purgeSelected, scope: this }, { text: _('trash.selected_restore'), + itemId: 'modx-bulk-menu-opt-restore', handler: this.restoreSelected, scope: this - }] - }, { - text: _('trash.purge_all'), - id: 'modx-purge-all', - cls: 'x-btn-purge-all', + }], listeners: { click: { - fn: this.purgeAll, + fn: function(btn) { + const + menuOptPurge = btn.menu.getComponent('modx-bulk-menu-opt-purge'), + menuOptUndelete = btn.menu.getComponent('modx-bulk-menu-opt-restore') + ; + if (this.getSelectionModel().getCount() === 0) { + menuOptPurge.disable(); + menuOptUndelete.disable(); + } else { + if (this.userCanPurge) { + menuOptPurge.enable(); + } + if (this.userCanUndelete) { + menuOptUndelete.enable(); + } + } + }, scope: this } } + }, { + text: _('trash.purge_all'), + id: 'modx-btn-purge-all', + cls: 'x-btn-purge-all', + handler: this.purgeAll }, { text: _('trash.restore_all'), - id: 'modx-restore-all', + id: 'modx-btn-restore-all', cls: 'x-btn-restore-all', - listeners: { - click: { - fn: this.restoreAll, - scope: this - } - } + handler: this.restoreAll }, '->', { @@ -122,51 +155,88 @@ MODx.grid.Trash = function(config = {}) { }); MODx.grid.Trash.superclass.constructor.call(this, config); + + this.gridMenuActions = ['purge', 'undelete']; + this.setUserHasPermissions('purge', ['purge_deleted']); + this.setUserHasPermissions('undelete', ['undelete_document']); + this.setShowActionsMenu(); + + this.on({ + render: grid => { + const buttonsToHide = []; + if (!this.userCanPurge && !this.userCanUndelete) { + buttonsToHide.push('modx-btn-bulk-actions', 'modx-btn-purge-all', 'modx-btn-restore-all'); + } else { + const bulkMenu = Ext.getCmp('modx-btn-bulk-actions').menu; + if (!this.userCanPurge) { + buttonsToHide.push('modx-btn-purge-all'); + bulkMenu.getComponent('modx-bulk-menu-opt-purge').disable(); + } + if (!this.userCanUndelete) { + buttonsToHide.push('modx-btn-restore-all'); + bulkMenu.getComponent('modx-bulk-menu-opt-restore').disable(); + } + } + if (buttonsToHide.length > 0) { + buttonsToHide.forEach(btnId => Ext.getCmp(btnId)?.hide()); + } + }, + beforeedit: function(e) { + if (e.field === 'published' && !this.userCanEditRecord(e.record, 'publish')) { + return false; + } + } + }); }; Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { - getMenu: function () { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('trash.selected_purge'), - handler: this.purgeSelected, - scope: this - }); - m.push({ - text: _('trash.selected_restore'), - handler: this.restoreSelected, - scope: this - }); + getMenu: function() { + const + model = this.getSelectionModel(), + record = model.getSelected(), + canPurge = this.userCanPurge && this.userCanDeleteRecord(record, 'purge'), + canUndelete = this.userCanUndelete && this.userCanEditRecord(record, 'undelete'), + menu = [] + ; + if (model.getCount() > 1) { + if (canPurge) { + menu.push({ + text: _('trash.selected_purge'), + handler: this.purgeSelected, + scope: this + }); + } + if (canUndelete) { + menu.push({ + text: _('trash.selected_restore'), + handler: this.restoreSelected, + scope: this + }); + } } else { - if (p.indexOf('trashpurge') !== -1) { - m.push({ + if (canPurge) { + menu.push({ text: _('trash.purge'), handler: this.purgeResource }); } - if (p.indexOf('trashundelete') !== -1) { - m.push({ + if (canUndelete) { + menu.push({ text: _('trash.restore'), handler: this.restoreResource }); } } - if (m.length > 0) { - this.addContextMenuItem(m); - } + return menu; }, - purgeResource: function () { + purgeResource: function() { MODx.msg.confirm({ minWidth: 500, title: _('trash.purge_confirm_title'), text: _('trash.purge_confirm_message', { - 'list': this.listResources('') + list: this.listResources('') }), url: this.config.url, params: { @@ -174,33 +244,35 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { ids: this.menu.record.id }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { this.refreshEverything(data.total); - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } }); }, - restoreResource: function () { - var withPublish = ''; + restoreResource: function() { + let withPublish = ''; if (this.menu.record.published) { withPublish = '_with_publish'; } MODx.msg.confirm({ minWidth: 500, title: _('trash.restore_confirm_title'), - text: _('trash.restore_confirm_message' + withPublish, { - 'list': this.listResources('') + text: _(`trash.restore_confirm_message${withPublish}`, { + list: this.listResources('') }), url: this.config.url, params: { @@ -208,52 +280,57 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { id: this.menu.record.id }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { this.refreshEverything(data.total); - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } }); }, - purgeSelected: function () { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - + purgeSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.msg.confirm({ minWidth: 500, title: _('trash.purge_confirm_title'), text: _('trash.purge_confirm_message', { - 'list': this.listResources('') + list: this.listResources('') }), url: this.config.url, params: { action: 'Resource/Trash/Purge', - ids: cs + ids: selections }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { this.getSelectionModel().clearSelections(true); this.refreshEverything(data.object.deletedCount); - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } }); @@ -261,52 +338,56 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { return true; }, - restoreSelected: function () { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - + restoreSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.msg.confirm({ minWidth: 500, title: _('trash.restore_confirm_title'), text: _('trash.restore_confirm_message', { - 'list': this.listResources('') + list: this.listResources('') }), url: this.config.url, params: { action: 'Resource/Trash/Restore', - ids: cs + ids: selections }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { this.refreshEverything(data.total); - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } }); return true; }, - purgeAll: function () { - var sm = this.getSelectionModel(); - sm.selectAll(); - var cs = this.getSelectedAsList(); - if (cs === false) return false; - + purgeAll: function() { + const model = this.getSelectionModel(); + model.selectAll(); + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.msg.confirm({ minWidth: 500, title: _('trash.purge_confirm_title'), text: _('trash.purge_all_confirm_message', { - 'count': sm.selections.length, - 'list': this.listResources('') + count: model.selections.length, + list: this.listResources('') }), url: this.config.url, params: { @@ -316,11 +397,11 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { // shown in the trash manager list because of missing reload. // in that case we would purge something unreviewed/blindly. // therefore we have to pass all ids which are shown in our list here - ids: cs + ids: selections }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { MODx.msg.status({ title: _('success'), message: data.message @@ -329,32 +410,35 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { this.refreshEverything(data.total); // no need to refresh if nothing was purged this.fireEvent('emptyTrash'); } - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } - }) + }); }, - restoreAll: function () { - var sm = this.getSelectionModel(); - sm.selectAll(); - var cs = this.getSelectedAsList(); - if (cs === false) return false; - + restoreAll: function() { + const model = this.getSelectionModel(); + model.selectAll(); + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.msg.confirm({ minWidth: 500, title: _('trash.restore_confirm_title'), text: _('trash.restore_all_confirm_message', { - 'count': sm.selections.length, - 'list': this.listResources('') + count: model.selections.length, + list: this.listResources('') }), url: this.config.url, params: { @@ -364,11 +448,11 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { // shown in the trash manager list because of missing reload. // in that case we would restore something unreviewed/blindly. // therefore we have to pass all ids which are shown in our list here - ids: cs + ids: selections }, listeners: { - 'success': { - fn: function (data) { + success: { + fn: function(data) { MODx.msg.status({ title: _('success'), message: data.message @@ -377,65 +461,67 @@ Ext.extend(MODx.grid.Trash, MODx.grid.Grid, { this.refreshEverything(data.total); // no need to refresh if nothing was purged this.fireEvent('emptyTrash'); } - }, scope: this + }, + scope: this }, - 'error': { - fn: function (data) { + error: { + fn: function(data) { MODx.msg.status({ title: _('error'), message: data.message }); - }, scope: this + }, + scope: this } } - }) + }); }, - refreshTree: function () { - var t = Ext.getCmp('modx-resource-tree'); - t.refresh(); + refreshTree: function() { + const tree = Ext.getCmp('modx-resource-tree'); + tree.refresh(); this.refreshRecycleBinButton(); }, - refreshEverything: function (total) { + refreshEverything: function(total) { this.refresh(); this.refreshTree(); this.refreshRecycleBinButton(total); }, - refreshRecycleBinButton: function (total) { + refreshRecycleBinButton: function(total) { Ext.getCmp('modx-trash-link')?.updateState(+total); }, - listResources: function (separator) { - separator = separator || ''; - + listResources: function(separator = '') { // creates a textual representation of the selected resources // we create a textlist of the resources here to show them again in the confirmation box - var selections = this.getSelectionModel().getSelections(); - var text = [], t; - selections.forEach(function (selection) { - t = selection.data.parentPath + "" + selection.data.pagetitle + " (" + selection.data.id + ")" + ""; + const + selections = this.getSelectionModel().getSelections(), + text = [] + ; + let resourceRef; + selections.forEach(function(selection) { + resourceRef = `${selection.data.parentPath}${selection.data.pagetitle} (${selection.data.id})`; if (selection.data.published) { - t = '' + t + ''; + resourceRef = `${resourceRef}`; } - t = "
    " + t + "
    "; - text.push(t); + resourceRef = `
    ${resourceRef}
    `; + text.push(resourceRef); }); return text.join(separator); }, - renderTooltip: function (value, metadata, record) { + renderTooltip: function(value, metadata, record) { if (value) { - var preview = ((record.json.pagetitle) ? '

    ' + _('pagetitle') + ': ' + record.json.pagetitle + '

    ' : '') - + ((record.json.longtitle) ? '

    ' + _('long_title') + ': ' + record.json.longtitle + '

    ' : '') - + ((record.data.parentPath) ? '

    ' + _('trash.parent_path') + ': ' + record.data.parentPath + '

    ' : '') - + ((record.json.content) ? '

    ' + _('content') + ': ' + Ext.util.Format.ellipsis(record.json.content.replace(/<\/?[^>]+>/gi, ''), 100) + '

    ' : ''); + let preview = ((record.json.pagetitle) ? `

    ${_('pagetitle')}: ${record.json.pagetitle}

    ` : '') + + ((record.json.longtitle) ? `

    ${_('long_title')}: ${record.json.longtitle}

    ` : '') + + ((record.data.parentPath) ? `

    ${_('trash.parent_path')}: ${record.data.parentPath}

    ` : '') + + ((record.json.content) ? `

    ${_('content')}: ${Ext.util.Format.ellipsis(record.json.content.replace(/<\/?[^>]+>/gi, ''), 100)}

    ` : ''); preview = Ext.util.Format.htmlEncode(preview); - return '
    ' + value + '
    '; - } else { - return ''; + return `
    ${value}
    `; } + return ''; } }); Ext.reg('modx-grid-trash', MODx.grid.Trash); diff --git a/manager/assets/modext/widgets/security/modx.grid.access.context.js b/manager/assets/modext/widgets/security/modx.grid.access.context.js index 3410bc15646..9e2bae55610 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.context.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.context.js @@ -6,148 +6,191 @@ * @param {Object} config An object of options. * @xtype modx-grid-access-context */ -MODx.grid.AccessContext = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-grid-access-context' - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Security/Access/GetList' - ,type: config.type || 'MODX\\Revolution\\modAccessContext' - ,target: config.context_key - } - ,fields: ['id','target','target_name','principal_class','principal','principal_name','authority','policy','policy_name','cls'] - ,type: 'modAccessContext' - ,paging: true - ,columns: [{ - header: _('context') - ,dataIndex: 'target_name' - ,width: 100 - },{ - header: _('user_group') - ,dataIndex: 'principal_name' - ,width: 120 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/usergroup/update&id=' + record.data.principal - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('authority') - ,dataIndex: 'authority' - ,width: 50 - },{ - header: _('policy') - ,dataIndex: 'policy_name' - ,width: 175 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/access/policy/update&id=' + record.data.policy - ,target: '_blank' - }); - }, scope: this } - }] - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,scope: this - ,handler: this.createAcl +MODx.grid.AccessContext = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-grid-access-context', + url: MODx.config.connector_url, + baseParams: { + action: 'Security/Access/GetList', + type: config.type || 'MODX\\Revolution\\modAccessContext', + target: config.context_key + }, + fields: ['id', + 'target', + 'target_name', + 'principal_class', + 'principal', + 'principal_name', + 'authority', + 'policy', + 'policy_name' + ], + type: 'modAccessContext', + paging: true, + columns: [{ + header: _('context'), + dataIndex: 'target_name', + width: 100 + }, { + header: _('user_group'), + dataIndex: 'principal_name', + width: 120, + renderer: { + fn: function(value, metaData, record) { + return record.json.canEditGroups + ? this.renderLink(value, { + href: `?a=security/usergroup/update&id=${record.data.principal}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, { + header: _('authority'), + dataIndex: 'authority', + width: 50 + }, { + header: _('policy'), + dataIndex: 'policy_name', + width: 175, + renderer: { + fn: function(value, metaData, record) { + return record.json.canEditGroups + ? this.renderLink(value, { + href: `?a=security/access/policy/update&id=${record.data.policy}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }], + tbar: [{ + text: _('create'), + cls: 'primary-button', + scope: this, + handler: this.createAcl }] }); - MODx.grid.AccessContext.superclass.constructor.call(this,config); -}; -Ext.extend(MODx.grid.AccessContext,MODx.grid.Grid,{ - combos: {} - ,windows: {} + MODx.grid.AccessContext.superclass.constructor.call(this, config); - ,getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; + this.gridMenuActions = ['edit', 'delete']; - var m = []; - if (this.getSelectionModel().getCount() > 1) { + // Note there are currently no action-specific permissions for Access Permissions + this.setUserCanEdit(['access_permissions']); + this.setUserCanCreate(['access_permissions']); + this.setUserCanDelete(['access_permissions']); + this.setShowActionsMenu(); - } else { - if (p.indexOf('pedit') != -1) { - m.push({ - text: _('edit') - ,handler: this.editAcl + this.on({ + beforerender: function(grid) { + grid.view = new Ext.grid.GridView(grid.getViewConfig(false, false)); + } + }); +}; +Ext.extend(MODx.grid.AccessContext, MODx.grid.Grid, { + combos: {}, + windows: {}, + + getMenu: function() { + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.getSelectionModel().getCount() === 1) { + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.editAcl }); } - if (p.indexOf('premove') != -1) { - if (m.length > 0) { m.push('-'); } - m.push({ - text: _('delete') - ,handler: this.removeAcl + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('delete'), + handler: this.removeAcl }); } } - if (m.length > 0) { - this.addContextMenuItem(m); + if (menu.length > 0) { + this.addContextMenuItem(menu); } - } + }, - ,createAcl: function(itm,e) { - var r = { - target: this.config.context_key - ,principal_class: 'MODX\\Revolution\\modUserGroup' + createAcl: function(itm, e) { + const record = { + target: this.config.context_key, + principal_class: 'MODX\\Revolution\\modUserGroup' }; if (!this.windows.create_acl) { this.windows.create_acl = MODx.load({ - xtype: 'modx-window-access-context-create' - ,record: r - ,listeners: { - 'success': {fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-access-context-create', + record: record, + listeners: { + success: { + fn: function() { + this.refresh(); + }, + scope: this + } } }); } this.windows.create_acl.fp.getForm().reset(); - this.windows.create_acl.setValues(r); + this.windows.create_acl.setValues(record); this.windows.create_acl.show(e.target); - } + }, - ,editAcl: function(itm,e) { - var r = this.menu.record; - Ext.applyIf(r,{ - context: r.target - ,user_group: r.principal + editAcl: function(itm, e) { + const { record } = this.menu; + Ext.applyIf(record, { + context: record.target, + user_group: record.principal }); if (!this.windows.update_acl) { this.windows.update_acl = MODx.load({ - xtype: 'modx-window-access-context-update' - ,acl: r.id - ,record: r - ,listeners: { - 'success': {fn:this.refresh,scope:this} + xtype: 'modx-window-access-context-update', + acl: record.id, + record: record, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); } - this.windows.update_acl.setValues(r); + this.windows.update_acl.setValues(record); this.windows.update_acl.show(e.target); - } + }, - ,removeAcl: function(itm,e) { + removeAcl: function(itm, e) { MODx.msg.confirm({ - title: _('ugc_remove') - ,text: _('access_confirm_remove') - ,url: this.config.url - ,params: { - action: 'Security/Access/RemoveAcl' - ,id: this.menu.record.id - ,type: this.config.type || 'modAccessContext' - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + title: _('ugc_remove'), + text: _('access_confirm_remove'), + url: this.config.url, + params: { + action: 'Security/Access/RemoveAcl', + id: this.menu.record.id, + type: this.config.type || 'modAccessContext' + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); } }); -Ext.reg('modx-grid-access-context',MODx.grid.AccessContext); +Ext.reg('modx-grid-access-context', MODx.grid.AccessContext); /** * @class MODx.window.CreateAccessContext @@ -155,61 +198,59 @@ Ext.reg('modx-grid-access-context',MODx.grid.AccessContext); * @param {Object} config An object of options. * @xtype modx-window-access-context-create */ -MODx.window.CreateAccessContext = function(config) { - config = config || {}; - var r = config.record; - - Ext.applyIf(config,{ - title: _('ugc_mutate') - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Security/Access/AddAcl' - ,type: config.type || 'modAccessContext' - } - ,type: 'modAccessContext' - ,acl: 0 - ,fields: [{ - xtype: 'hidden' - ,name: 'id' - ,value: r.id || '' - },{ - xtype: 'hidden' - ,name: 'target' - ,value: r.context || '' - },{ - xtype: 'hidden' - ,name: 'principal_class' - ,value: 'MODX\\Revolution\\modUserGroup' - },{ - xtype: 'modx-combo-usergroup' - ,fieldLabel: _('user_group') - ,name: 'principal' - ,hiddenName: 'principal' - ,anchor: '100%' - ,value: r.principal || '' - ,baseParams: { - action: 'Security/Group/GetList' - ,combo: true +MODx.window.CreateAccessContext = function(config = {}) { + const { record } = config; + Ext.applyIf(config, { + title: _('ugc_mutate'), + url: MODx.config.connector_url, + baseParams: { + action: 'Security/Access/AddAcl', + type: config.type || 'modAccessContext' + }, + type: 'modAccessContext', + acl: 0, + fields: [{ + xtype: 'hidden', + name: 'id', + value: record.id || '' + }, { + xtype: 'hidden', + name: 'target', + value: record.context || '' + }, { + xtype: 'hidden', + name: 'principal_class', + value: 'MODX\\Revolution\\modUserGroup' + }, { + xtype: 'modx-combo-usergroup', + fieldLabel: _('user_group'), + name: 'principal', + hiddenName: 'principal', + anchor: '100%', + value: record.principal || '', + baseParams: { + action: 'Security/Group/GetList', + combo: true } - },{ - xtype: 'modx-combo-policy' - ,fieldLabel: _('policy') - ,name: 'policy' - ,hiddenName: 'policy' - ,value: r.policy || '' - ,anchor: '100%' - },{ - xtype: 'textfield' - ,fieldLabel: _('authority') - ,name: 'authority' - ,anchor: '100%' - ,value: r.authority || '' + }, { + xtype: 'modx-combo-policy', + fieldLabel: _('policy'), + name: 'policy', + hiddenName: 'policy', + value: record.policy || '', + anchor: '100%' + }, { + xtype: 'textfield', + fieldLabel: _('authority'), + name: 'authority', + anchor: '100%', + value: record.authority || '' }] }); - MODx.window.CreateAccessContext.superclass.constructor.call(this,config); + MODx.window.CreateAccessContext.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateAccessContext,MODx.Window); -Ext.reg('modx-window-access-context-create',MODx.window.CreateAccessContext); +Ext.extend(MODx.window.CreateAccessContext, MODx.Window); +Ext.reg('modx-window-access-context-create', MODx.window.CreateAccessContext); /** * @class MODx.window.UpdateAccessContext @@ -217,18 +258,16 @@ Ext.reg('modx-window-access-context-create',MODx.window.CreateAccessContext); * @param {Object} config An object of options. * @xtype modx-window-access-context-update */ -MODx.window.UpdateAccessContext = function(config) { - config = config || {}; - var r = config.record; - this.ident = config.ident || 'uactx'+Ext.id(); - Ext.applyIf(config,{ - title: _('ugc_mutate') - ,baseParams: { - action: 'Security/Access/UpdateAcl' - ,type: config.type || 'modAccessContext' +MODx.window.UpdateAccessContext = function(config = {}) { + this.ident = config.ident || `uactx${Ext.id()}`; + Ext.applyIf(config, { + title: _('ugc_mutate'), + baseParams: { + action: 'Security/Access/UpdateAcl', + type: config.type || 'modAccessContext' } }); - MODx.window.UpdateAccessContext.superclass.constructor.call(this,config); + MODx.window.UpdateAccessContext.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.UpdateAccessContext,MODx.window.CreateAccessContext); -Ext.reg('modx-window-access-context-update',MODx.window.UpdateAccessContext); +Ext.extend(MODx.window.UpdateAccessContext, MODx.window.CreateAccessContext); +Ext.reg('modx-window-access-context-update', MODx.window.UpdateAccessContext); 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 4631ca43d44..c5d5cf9626a 100644 --- a/manager/assets/modext/widgets/security/modx.grid.access.policy.js +++ b/manager/assets/modext/widgets/security/modx.grid.access.policy.js @@ -7,30 +7,30 @@ * @xtype modx-panel-access-policies */ MODx.panel.AccessPolicies = function(config = {}) { - Ext.applyIf(config,{ - id: 'modx-panel-access-policies' - ,bodyStyle: '' - ,defaults: { collapsible: false, autoHeight: true } - ,items: [{ - html: _('policies') - ,id: 'modx-policies-header' - ,xtype: 'modx-header' - },{ - layout: 'form' - ,cls: 'main-wrapper' - ,items: [{ - html: '

    '+_('policy_management_msg')+'

    ' - ,border: false - },{ - xtype: 'modx-grid-access-policy' - ,preventRender: true + Ext.applyIf(config, { + id: 'modx-panel-access-policies', + bodyStyle: '', + defaults: { collapsible: false, autoHeight: true }, + items: [{ + html: _('policies'), + id: 'modx-policies-header', + xtype: 'modx-header' + }, { + layout: 'form', + cls: 'main-wrapper', + items: [{ + html: `

    ${_('policy_management_msg')}

    `, + border: false + }, { + xtype: 'modx-grid-access-policy', + preventRender: true }] }] }); - MODx.panel.AccessPolicies.superclass.constructor.call(this,config); + MODx.panel.AccessPolicies.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.AccessPolicies,MODx.FormPanel); -Ext.reg('modx-panel-access-policies',MODx.panel.AccessPolicies); +Ext.extend(MODx.panel.AccessPolicies, MODx.FormPanel); +Ext.reg('modx-panel-access-policies', MODx.panel.AccessPolicies); /** * Loads a grid of modAccessPolicies. @@ -43,212 +43,252 @@ Ext.reg('modx-panel-access-policies',MODx.panel.AccessPolicies); 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' - ,url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-access-policy', + url: MODx.config.connector_url, + baseParams: { action: 'Security/Access/Policy/GetList' - } - ,fields: [ + }, + 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' - ,remoteSort: true - ,sm: this.sm - ,columns: [this.sm,{ - header: _('policy_name') - ,dataIndex: 'name' - ,width: 200 - ,editor: { xtype: 'textfield' ,allowBlank: false } - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/access/policy/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 375 - ,renderer: function(value, metaData, record) { - return Ext.util.Format.htmlEncode(record['data']['description_trans']); + 'creator' + ], + paging: true, + autosave: true, + save_action: 'Security/Access/Policy/UpdateFromGrid', + remoteSort: true, + sm: this.sm, + columns: [this.sm, { + header: _('policy_name'), + dataIndex: 'name', + width: 200, + editor: { + xtype: 'textfield', + allowBlank: false + }, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=security/access/policy/update&id=${record.data.id}`, + title: _('policy_edit') + }) + : value + ; + }, + scope: this } - ,editable: false - },{ - header: _('policy_template') - ,dataIndex: 'template_name' - ,width: 375 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/access/policy/template/update&id=' + record.data.template - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('active_permissions') - ,dataIndex: 'active_of' - ,width: 100 - ,editable: false - }] - ,tbar: [ + }, { + header: _('description'), + dataIndex: 'description', + width: 375, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return record.json.description_trans || value; + }, + scope: this + } + }, { + header: _('policy_template'), + dataIndex: 'template_name', + width: 375, + renderer: { + fn: function(value, metaData, record) { + const objPermissions = record.json.permissions; + return !Ext.isEmpty(objPermissions) && objPermissions.updateTemplate === true + ? this.renderLink(value, { + href: `?a=security/access/policy/template/update&id=${record.data.template}`, + title: _('policy_template_edit'), + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, + this.getCreatorColumnConfig('policy'), + { + header: _('active_permissions'), + dataIndex: 'active_of', + width: 100, + editable: false + }], + tbar: [ + this.getCreateButton('policy', 'createPolicy'), { - 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 - ,scope: this - }] + text: _('import'), + scope: this, + handler: this.importPolicy, + listeners: { + render: { + fn: function(btn) { + if (!this.userCanCreate) { + btn.hide(); + } + }, + scope: this + } + } }, + this.getBulkActionsButton('policy', 'Security/Access/Policy/RemoveMultiple'), '->', this.getQueryFilterField(`filter-query-policy:${queryValue}`), this.getClearFiltersButton('filter-query-policy') - ] + ], + viewConfig: this.getViewConfig() + }); + MODx.grid.AccessPolicy.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate', 'export']; + + this.setUserCanEdit(['policy_save', 'policy_edit']); + this.setUserCanCreate(['policy_save', 'policy_new']); + this.setUserCanDelete(['policy_delete']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit || e.record.json.isProtected || !this.userCanEditRecord(e.record)) { + return false; + } + }, + afteredit: function(e) { + this.refresh(); + } }); - MODx.grid.AccessPolicy.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.AccessPolicy,MODx.grid.Grid,{ - editPolicy: function(itm,e) { - MODx.loadPage('security/access/policy/update', 'id='+this.menu.record.id); - } +Ext.extend(MODx.grid.AccessPolicy, MODx.grid.Grid, { + getMenu: function() { + const + model = this.getSelectionModel(), + record = model.getSelected(), + menu = [] + ; + if (model.getCount() > 1) { + const records = model.getSelections(); + if (this.userCanDelete && this.userCanDeleteRecords(records)) { + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected.bind(this, 'policy', 'Security/Access/Policy/RemoveMultiple') + }); + } + } else { + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.editPolicy + }); + } + if (this.userCanCreate && this.userCanDuplicateRecord(record)) { + menu.push({ + text: _('duplicate'), + handler: this.confirm.createDelegate(this, ['Security/Access/Policy/Duplicate', 'policy_duplicate_confirm']) + }); + } + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('export'), + handler: this.exportPolicy + }); + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('delete'), + handler: this.confirm.createDelegate(this, ['Security/Access/Policy/Remove', 'policy_remove_confirm']) + }); + } + } + return menu; + }, + + editPolicy: function(itm, e) { + MODx.loadPage('security/access/policy/update', `id=${this.menu.record.id}`); + }, - ,createPolicy: function(btn,e) { - var r = this.menu.record; + createPolicy: function(btn, e) { + const { record } = this.menu; if (!this.windows.apc) { this.windows.apc = MODx.load({ - xtype: 'modx-window-access-policy-create' - ,record: r - ,plugin: this.config.plugin - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + xtype: 'modx-window-access-policy-create', + record: record, + plugin: this.config.plugin, + listeners: { + success: { + fn: function() { + this.refresh(); + }, + scope: this + } } }); } this.windows.apc.reset(); this.windows.apc.show(e.target); - } + }, - ,exportPolicy: function(btn,e) { - var id = this.menu.record.id; + exportPolicy: function(btn, e) { + const { id } = this.menu.record; MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Access/Policy/Export' - ,id: id - } - ,listeners: { - 'success': {fn:function(r) { - location.href = this.config.url+'?action=Security/Access/Policy/Export&download=1&id='+id+'&HTTP_MODAUTH='+MODx.siteId; - },scope:this} + url: this.config.url, + params: { + action: 'Security/Access/Policy/Export', + id: id + }, + listeners: { + success: { + fn: function(r) { + window.location.href = `${this.config.url}?action=Security/Access/Policy/Export&download=1&id=${id}&HTTP_MODAUTH=${MODx.siteId}`; + }, + scope: this + } } }); - } + }, - ,importPolicy: function(btn,e) { - var r = {}; + importPolicy: function(btn, e) { + const record = {}; if (!this.windows.importPolicy) { this.windows.importPolicy = MODx.load({ - xtype: 'modx-window-policy-import' - ,record: r - ,listeners: { - 'success': {fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-policy-import', + record: record, + listeners: { + success: { + fn: function(o) { + this.refresh(); + }, + scope: this + } } }); } this.windows.importPolicy.reset(); - this.windows.importPolicy.setValues(r); + this.windows.importPolicy.setValues(record); this.windows.importPolicy.show(e.target); } - - ,getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - }); - } else { - if (p.indexOf('pedit') != -1) { - m.push({ - text: _('edit') - ,handler: this.editPolicy - }); - m.push({ - text: _('duplicate') - ,handler: this.confirm.createDelegate(this,["Security/Access/Policy/Duplicate","policy_duplicate_confirm"]) - }); - } - if (m.length > 0) { m.push('-'); } - m.push({ - text: _('export') - ,handler: this.exportPolicy - }); - if (p.indexOf('premove') != -1) { - if (m.length > 0) m.push('-'); - m.push({ - text: _('delete') - ,handler: this.confirm.createDelegate(this,["Security/Access/Policy/Remove","policy_remove_confirm"]) - }); - } - } - - if (m.length > 0) { - this.addContextMenuItem(m); - } - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('policy_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Security/Access/Policy/RemoveMultiple' - ,policies: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; - } }); -Ext.reg('modx-grid-access-policy',MODx.grid.AccessPolicy); +Ext.reg('modx-grid-access-policy', MODx.grid.AccessPolicy); /** * Generates a window for creating Access Policies. @@ -259,65 +299,60 @@ Ext.reg('modx-grid-access-policy',MODx.grid.AccessPolicy); * @xtype modx-window-access-policy-create */ MODx.window.CreateAccessPolicy = function(config = {}) { - this.ident = config.ident || 'cacp'+Ext.id(); - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Security/Access/Policy/Create' - ,fields: [{ - fieldLabel: _('name') - ,description: MODx.expandHelp ? '' : _('policy_desc_name') - ,name: 'name' - ,id: 'modx-'+this.ident+'-name' - ,xtype: 'textfield' - ,allowBlank: false - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-name' - ,html: _('policy_desc_name') - ,cls: 'desc-under' - },{ - fieldLabel: _('policy_template') - ,description: MODx.expandHelp ? '' : _('policy_desc_template') - ,name: 'template' - ,hiddenName: 'template' - ,id: 'modx-'+this.ident+'-template' - ,xtype: 'modx-combo-access-policy-template' - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-template' - ,html: _('policy_desc_template') - ,cls: 'desc-under' - },{ - fieldLabel: _('description') - ,description: MODx.expandHelp ? '' : _('policy_desc_description') - ,name: 'description' - ,id: 'modx-'+this.ident+'-description' - ,xtype: 'textarea' - ,anchor: '100%' - ,height: 50 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-description' - ,html: _('policy_desc_description') - ,cls: 'desc-under' - },{ - name: 'class' - ,id: 'modx-'+this.ident+'-class' - ,xtype: 'hidden' - },{ - name: 'id' - ,id: 'modx-'+this.ident+'-id' - ,xtype: 'hidden' - }] - ,keys: [] + this.ident = config.ident || `window--create-policy-${Ext.id()}`; + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + action: 'Security/Access/Policy/Create', + fields: [{ + fieldLabel: _('name'), + description: MODx.expandHelp ? '' : _('policy_desc_name'), + name: 'name', + xtype: 'textfield', + allowBlank: false, + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_desc_name'), + cls: 'desc-under' + }, { + fieldLabel: _('policy_template'), + description: MODx.expandHelp ? '' : _('policy_desc_template'), + name: 'template', + hiddenName: 'template', + xtype: 'modx-combo-access-policy-template', + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_desc_template'), + cls: 'desc-under' + }, { + fieldLabel: _('description'), + description: MODx.expandHelp ? '' : _('policy_desc_description'), + name: 'description', + xtype: 'textarea', + anchor: '100%', + height: 50 + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_desc_description'), + cls: 'desc-under' + }, { + name: 'class', + xtype: 'hidden' + }, { + name: 'id', + xtype: 'hidden' + }], + keys: [] }); - MODx.window.CreateAccessPolicy.superclass.constructor.call(this,config); + MODx.window.CreateAccessPolicy.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateAccessPolicy,MODx.Window); -Ext.reg('modx-window-access-policy-create',MODx.window.CreateAccessPolicy); +Ext.extend(MODx.window.CreateAccessPolicy, MODx.Window); +Ext.reg('modx-window-access-policy-create', MODx.window.CreateAccessPolicy); /** * @class MODx.window.AccessPolicyTemplate @@ -326,26 +361,37 @@ Ext.reg('modx-window-access-policy-create',MODx.window.CreateAccessPolicy); * @xtype modx-combo-access-policy-template */ MODx.combo.AccessPolicyTemplate = function(config = {}) { - Ext.applyIf(config,{ - name: 'template' - ,hiddenName: 'template' - ,fields: ['id','name','description','description_trans'] - ,forceSelection: true - ,typeAhead: false - ,editable: false - ,allowBlank: false - ,pageSize: 20 - ,url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + name: 'template', + hiddenName: 'template', + fields: [ + 'id', + 'name', + 'description', + 'description_trans' + ], + forceSelection: true, + typeAhead: false, + editable: false, + allowBlank: false, + pageSize: 20, + url: MODx.config.connector_url, + baseParams: { action: 'Security/Access/Policy/Template/GetList' - } - ,tpl: new Ext.XTemplate('
    {name:htmlEncode}' - ,'

    {description_trans:htmlEncode}

    ') + }, + tpl: new Ext.XTemplate(` + +
    + {name:htmlEncode} +

    {description_trans:htmlEncode}

    +
    +
    + `) }); - MODx.combo.AccessPolicyTemplate.superclass.constructor.call(this,config); + MODx.combo.AccessPolicyTemplate.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.AccessPolicyTemplate,MODx.combo.ComboBox); -Ext.reg('modx-combo-access-policy-template',MODx.combo.AccessPolicyTemplate); +Ext.extend(MODx.combo.AccessPolicyTemplate, MODx.combo.ComboBox); +Ext.reg('modx-combo-access-policy-template', MODx.combo.AccessPolicyTemplate); /** * @class MODx.window.ImportPolicy @@ -354,29 +400,27 @@ Ext.reg('modx-combo-access-policy-template',MODx.combo.AccessPolicyTemplate); * @xtype modx-window-policy-import */ MODx.window.ImportPolicy = function(config = {}) { - this.ident = config.ident || 'imppol-'+Ext.id(); - Ext.applyIf(config,{ - title: _('import') - ,id: 'modx-window-policy-import' - ,url: MODx.config.connector_url - ,action: 'Security/Access/Policy/Import' - ,fileUpload: true - ,saveBtnText: _('import') - ,fields: [{ - html: _('policy_import_msg') - ,id: this.ident+'-desc' - ,xtype: 'modx-description' - ,style: 'margin-bottom: 10px;' - },{ - xtype: 'fileuploadfield' - ,fieldLabel: _('file') - ,buttonText: _('upload.buttons.upload') - ,name: 'file' - ,id: this.ident+'-file' - ,anchor: '100%' + this.ident = config.ident || `window--import-policy-${Ext.id()}`; + Ext.applyIf(config, { + title: _('import'), + id: 'modx-window-policy-import', + url: MODx.config.connector_url, + action: 'Security/Access/Policy/Import', + fileUpload: true, + saveBtnText: _('import'), + fields: [{ + html: _('policy_import_msg'), + xtype: 'modx-description', + style: 'margin-bottom: 10px;' + }, { + xtype: 'fileuploadfield', + fieldLabel: _('file'), + buttonText: _('upload.buttons.upload'), + name: 'file', + anchor: '100%' }] }); - MODx.window.ImportPolicy.superclass.constructor.call(this,config); + MODx.window.ImportPolicy.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.ImportPolicy,MODx.Window); -Ext.reg('modx-window-policy-import',MODx.window.ImportPolicy); +Ext.extend(MODx.window.ImportPolicy, MODx.Window); +Ext.reg('modx-window-policy-import', MODx.window.ImportPolicy); 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 8fee8c4f595..fa5ac267113 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 @@ -7,30 +7,30 @@ * @xtype modx-panel-access-policy-templates */ MODx.panel.AccessPolicyTemplates = function(config = {}) { - Ext.applyIf(config,{ - id: 'modx-panel-access-policy-templates' - ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('policies') - ,id: 'modx-policy-templates-header' - ,xtype: 'modx-header' - },{ - layout: 'form' - ,bodyStyle: 'padding: 15px' - ,items: [{ - html: '

    '+_('policy_templates.intro_msg')+'

    ' - ,border: false - },{ - xtype: 'modx-grid-access-policy-templates' - ,preventRender: true + Ext.applyIf(config, { + id: 'modx-panel-access-policy-templates', + bodyStyle: '', + defaults: { collapsible: false, autoHeight: true }, + items: [{ + html: _('policies'), + id: 'modx-policy-templates-header', + xtype: 'modx-header' + }, { + layout: 'form', + bodyStyle: 'padding: 15px', + items: [{ + html: `

    ${_('policy_templates.intro_msg')}

    `, + border: false + }, { + xtype: 'modx-grid-access-policy-templates', + preventRender: true }] }] }); - MODx.panel.AccessPolicyTemplates.superclass.constructor.call(this,config); + MODx.panel.AccessPolicyTemplates.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.AccessPolicyTemplates,MODx.FormPanel); -Ext.reg('modx-panel-access-policy-templates',MODx.panel.AccessPolicyTemplates); +Ext.extend(MODx.panel.AccessPolicyTemplates, MODx.FormPanel); +Ext.reg('modx-panel-access-policy-templates', MODx.panel.AccessPolicyTemplates); /** * Loads a grid of modAccessPolicyTemplates. @@ -43,13 +43,13 @@ Ext.reg('modx-panel-access-policy-templates',MODx.panel.AccessPolicyTemplates); 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' - ,url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-access-policy-template', + url: MODx.config.connector_url, + baseParams: { action: 'Security/Access/Policy/Template/GetList' - } - ,fields: [ + }, + fields: [ 'id', 'name', 'description', @@ -58,214 +58,337 @@ MODx.grid.AccessPolicyTemplate = function(config = {}) { 'template_group_name', 'total_permissions', 'policy_count', - 'cls' - ] - ,paging: true - ,autosave: true - ,save_action: 'Security/Access/Policy/Template/UpdateFromGrid' - ,remoteSort: true - ,sm: this.sm - ,columns: [this.sm,{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,editor: { xtype: 'textfield' ,allowBlank: false } - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/access/policy/template/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 375 - ,editable: false - ,renderer: function(value, metaData, record) { - return Ext.util.Format.htmlEncode(record['data']['description_trans']); + 'creator' + ], + paging: true, + autosave: true, + save_action: 'Security/Access/Policy/Template/UpdateFromGrid', + remoteSort: true, + sm: this.sm, + columns: [this.sm, { + header: _('name'), + dataIndex: 'name', + width: 200, + editor: { + xtype: 'textfield', + allowBlank: false + }, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=security/access/policy/template/update&id=${record.data.id}`, + title: _('policy_template_edit') + }) + : value + ; + }, + scope: this } - ,sortable: true - },{ - header: _('template_group') - ,dataIndex: 'template_group_name' - ,width: 375 - ,sortable: true - },{ - header: _('policy_count') - ,dataIndex: 'policy_count' - ,width: 100 - ,editable: false - ,sortable: true - },{ - header: _('permissions') - ,dataIndex: 'total_permissions' - ,width: 100 - ,editable: false - ,sortable: true - }] - ,tbar: [ + }, { + header: _('description'), + dataIndex: 'description', + width: 375, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return record.json.description_trans || value; + }, + scope: this + } + }, { + header: _('template_group'), + dataIndex: 'template_group_name', + width: 375, + sortable: true + }, { + header: _('policy_count'), + dataIndex: 'policy_count', + width: 100, + editable: false, + sortable: true + }, { + header: _('permissions'), + dataIndex: 'total_permissions', + width: 100, + editable: false, + sortable: true + }, + this.getCreatorColumnConfig('policy-template') + ], + tbar: [ + this.getCreateButton('policy_template', 'createPolicyTemplate'), { - 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 - ,scope: this - }] + text: _('import'), + scope: this, + handler: this.importPolicyTemplate, + listeners: { + render: { + fn: function(btn) { + if (!this.userCanCreate) { + btn.hide(); + } + }, + scope: this + } + } + }, { + /* + * Note: Using local this.removeSelected method instead of shared base this.getBulkActionsButton() method here, + * as additional validation processing is needed for removal of Policy Templates + */ + text: _('bulk_actions'), + menu: [{ + text: _('selected_remove'), + itemId: 'modx-bulk-menu-opt-remove', + handler: this.removeSelected, + scope: this + }], + listeners: { + render: { + fn: function(btn) { + if (!this.userCanDelete) { + btn.hide(); + } + }, + scope: this + }, + click: { + fn: function(btn) { + const + removableItems = this.getRemovableItemsFromSelection('int'), + menuOptRemove = btn.menu.getComponent('modx-bulk-menu-opt-remove') + ; + if (removableItems.length === 0) { + menuOptRemove.disable(); + } else { + menuOptRemove.enable(); + } + }, + scope: this + } + } }, '->', this.getQueryFilterField(`filter-query-policy-template:${queryValue}`), this.getClearFiltersButton('filter-query-policy-template') - ] + ], + viewConfig: this.getViewConfig() + }); + MODx.grid.AccessPolicyTemplate.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate', 'export']; + + this.setUserCanEdit(['policy_template_save', 'policy_template_edit']); + this.setUserCanCreate(['policy_template_save', 'policy_template_new']); + this.setUserCanDelete(['policy_template_delete']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit || e.record.json.isProtected || !this.userCanEditRecord(e.record)) { + return false; + } + }, + afteredit: function(e) { + this.refresh(); + } }); - MODx.grid.AccessPolicyTemplate.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.AccessPolicyTemplate,MODx.grid.Grid,{ +Ext.extend(MODx.grid.AccessPolicyTemplate, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; + const + model = this.getSelectionModel(), + record = model.getSelected(), + menu = [] + ; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - }); + if (model.getCount() > 1) { + const records = model.getSelections(); + if (this.userCanDelete && this.userCanDeleteRecords(records)) { + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected + }); + } } else { - if (p.indexOf('pedit') !== -1) { - m.push({ - text: _('edit') - ,handler: this.editPolicyTemplate + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.editPolicyTemplate }); - m.push({ - text: _('duplicate') - ,handler: this.confirm.createDelegate(this,["Security/Access/Policy/Template/Duplicate","policy_template_duplicate_confirm"]) + } + if (this.userCanCreate && this.userCanDuplicateRecord(record)) { + menu.push({ + text: _('duplicate'), + handler: this.confirm.createDelegate( + this, + [ + 'Security/Access/Policy/Template/Duplicate', + 'policy_template_duplicate_confirm' + ] + ) }); } - if (m.length > 0) { m.push('-'); } - m.push({ - text: _('export') - ,handler: this.exportPolicyTemplate + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('export'), + handler: this.exportPolicyTemplate }); - - if (p.indexOf('premove') !== -1) { - if (m.length > 0) m.push('-'); - m.push({ + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); + } + /* + * Note: Using local this.removePolicyTemplate method instead of shared base this.remove() method here, + * as additional validation processing is needed for removal of Policy Templates + */ + menu.push({ text: _('delete'), handler: this.removePolicyTemplate }); } } + return menu; + }, - if (m.length > 0) { - this.addContextMenuItem(m); - } - } - - ,createPolicyTemplate: function(btn,e) { - var r = this.menu.record; - if (!this.windows.aptc) { - this.windows.aptc = MODx.load({ - xtype: 'modx-window-access-policy-template-create' - ,record: r - ,plugin: this.config.plugin - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + createPolicyTemplate: function(btn, e) { + const { record } = this.menu; + if (!this.windows.create_policy_template) { + this.windows.create_policy_template = MODx.load({ + xtype: 'modx-window-access-policy-template-create', + record: record, + plugin: this.config.plugin, + listeners: { + success: { + fn: function(response) { + this.refresh(); + }, + scope: this + } } }); } - this.windows.aptc.reset(); - this.windows.aptc.show(e.target); - } + this.windows.create_policy_template.reset(); + this.windows.create_policy_template.show(e.target); + }, - ,importPolicyTemplate: function(btn,e) { - var r = {}; + importPolicyTemplate: function(btn, e) { + const record = {}; if (!this.windows.importPolicyTemplate) { this.windows.importPolicyTemplate = MODx.load({ - xtype: 'modx-window-policy-template-import' - ,record: r - ,listeners: { - 'success': {fn:function(o) { - this.refresh(); - },scope:this} + xtype: 'modx-window-policy-template-import', + record: record, + listeners: { + success: { + fn: function(response) { + this.refresh(); + }, + scope: this + } } }); } this.windows.importPolicyTemplate.reset(); - this.windows.importPolicyTemplate.setValues(r); + this.windows.importPolicyTemplate.setValues(record); this.windows.importPolicyTemplate.show(e.target); - } + }, - ,exportPolicyTemplate: function(btn,e) { - var id = this.menu.record.id; + exportPolicyTemplate: function(btn, e) { + const { id } = this.menu.record; MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/Access/Policy/Template/Export' - ,id: id - } - ,listeners: { - 'success': {fn:function(r) { - location.href = this.config.url+'?action=Security/Access/Policy/Template/Export&download=1&id='+id+'&HTTP_MODAUTH='+MODx.siteId; - },scope:this} + url: this.config.url, + params: { + action: 'Security/Access/Policy/Template/Export', + id: id + }, + listeners: { + success: { + fn: function(r) { + window.location.href = `${this.config.url}?action=Security/Access/Policy/Template/Export&download=1&id=${id}&HTTP_MODAUTH=${MODx.siteId}`; + }, + scope: this + } } }); - } + }, - ,editPolicyTemplate: function(itm,e) { - MODx.loadPage('security/access/policy/template/update', 'id='+this.menu.record.id); - } + editPolicyTemplate: function(itm, e) { + MODx.loadPage('security/access/policy/template/update', `id=${this.menu.record.id}`); + }, - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - var store = this.getStore(); - var policiesCount = 0; - cs.split(',').forEach(function(item){ + removeSelected: function() { + const selectedTemplates = this.getSelectedAsList(); + if (selectedTemplates === false) { + return false; + } + const + store = this.getStore(), + selectedTemplatesArr = selectedTemplates.split(','), + totalSelected = selectedTemplatesArr.length + ; + let + policiesCount = 0, + selectionsProtected = 0, + confirmationMessage + ; + selectedTemplatesArr.forEach(item => { const record = store.getById(item); - if (record) { - policiesCount += parseInt(record.data.policy_count); + if (!record.json.isProtected) { + policiesCount += parseInt(record.data.policy_count, 10); + } else { + selectionsProtected++; + } } - - }) - + }); + if (policiesCount) { + confirmationMessage = selectionsProtected > 0 + ? _('policy_template_remove_multiple_confirm_in_use_ignoring_protected', { 'count-policies': policiesCount, protected: selectionsProtected, 'count-templates': totalSelected }) + : _('policy_template_remove_multiple_confirm_in_use', { count: policiesCount, total: totalSelected }) + ; + } else { + confirmationMessage = _('policy_template_remove_multiple_confirm'); + } MODx.msg.confirm({ - title: _('selected_remove') - ,text: policiesCount ? _('policy_template_remove_multiple_confirm_in_use', {count: policiesCount}) : _('policy_template_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Security/Access/Policy/Template/RemoveMultiple' - ,templates: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + title: _('selected_remove'), + text: confirmationMessage, + url: this.config.url, + params: { + action: 'Security/Access/Policy/Template/RemoveMultiple', + templates: selectedTemplates + }, + listeners: { + success: { + fn: function(response) { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; - } - - ,removePolicyTemplate: function() { - if (!this.menu.record) return; + }, + removePolicyTemplate: function() { + if (!this.menu.record) { + return; + } MODx.msg.confirm({ title: _('warning'), - text: parseInt(this.menu.record.policy_count) ? _('policy_template_remove_confirm_in_use', {count: this.menu.record.policy_count}) : _('policy_template_remove_confirm'), + text: parseInt(this.menu.record.policy_count, 10) + ? _('policy_template_remove_confirm_in_use', { count: this.menu.record.policy_count }) + : _('policy_template_remove_confirm'), url: this.config.url, params: { action: 'Security/Access/Policy/Template/Remove', @@ -274,13 +397,13 @@ Ext.extend(MODx.grid.AccessPolicyTemplate,MODx.grid.Grid,{ listeners: { success: { fn: this.refresh, - scope:this + scope: this } } }); } }); -Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); +Ext.reg('modx-grid-access-policy-templates', MODx.grid.AccessPolicyTemplate); /** * Generates a window for creating Access Policies. @@ -291,53 +414,50 @@ Ext.reg('modx-grid-access-policy-templates',MODx.grid.AccessPolicyTemplate); * @xtype modx-window-access-policy-create */ MODx.window.CreateAccessPolicyTemplate = function(config = {}) { - this.ident = config.ident || 'cacpt'+Ext.id(); - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Security/Access/Policy/Template/Create' - ,fields: [{ - fieldLabel: _('name') - ,name: 'name' - ,id: 'modx-'+this.ident+'-name' - ,xtype: 'textfield' - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-name' - ,html: _('policy_template_desc_name') - ,cls: 'desc-under' - },{ - fieldLabel: _('template_group') - ,name: 'template_group' - ,id: 'modx-'+this.ident+'-template-group' - ,xtype: 'modx-combo-access-policy-template-group' - ,anchor: '100%' - ,value: 1 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-template-group' - ,html: _('policy_template_desc_template_group') - ,cls: 'desc-under' - },{ - fieldLabel: _('description') - ,name: 'description' - ,id: 'modx-'+this.ident+'-description' - ,xtype: 'textarea' - ,anchor: '100%' - ,height: 50 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-description' - ,html: _('policy_template_desc_description') - ,cls: 'desc-under' - }] - ,keys: [] + this.ident = config.ident || `window-import-policy-template-${Ext.id()}`; + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + action: 'Security/Access/Policy/Template/Create', + fields: [{ + fieldLabel: _('name'), + name: 'name', + xtype: 'textfield', + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_template_desc_name'), + cls: 'desc-under' + }, { + fieldLabel: _('template_group'), + name: 'template_group', + xtype: 'modx-combo-access-policy-template-group', + anchor: '100%', + value: 1 + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_template_desc_template_group'), + cls: 'desc-under' + }, { + fieldLabel: _('description'), + name: 'description', + xtype: 'textarea', + anchor: '100%', + height: 50 + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('policy_template_desc_description'), + cls: 'desc-under' + }], + keys: [] }); - MODx.window.CreateAccessPolicyTemplate.superclass.constructor.call(this,config); + MODx.window.CreateAccessPolicyTemplate.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateAccessPolicyTemplate,MODx.Window); -Ext.reg('modx-window-access-policy-template-create',MODx.window.CreateAccessPolicyTemplate); +Ext.extend(MODx.window.CreateAccessPolicyTemplate, MODx.Window); +Ext.reg('modx-window-access-policy-template-create', MODx.window.CreateAccessPolicyTemplate); /** * @class MODx.window.ImportPolicyTemplate @@ -346,29 +466,27 @@ Ext.reg('modx-window-access-policy-template-create',MODx.window.CreateAccessPoli * @xtype modx-window-policy-template-import */ MODx.window.ImportPolicyTemplate = function(config = {}) { - this.ident = config.ident || 'imppt-'+Ext.id(); - Ext.applyIf(config,{ - title: _('import') - ,id: 'modx-window-policy-template-import' - ,url: MODx.config.connector_url - ,action: 'Security/Access/Policy/Template/Import' - ,fileUpload: true - ,saveBtnText: _('import') - ,fields: [{ - html: _('policy_template_import_msg') - ,id: this.ident+'-desc' - ,xtype: 'modx-description' - ,style: 'margin-bottom: 10px;' - },{ - xtype: 'fileuploadfield' - ,fieldLabel: _('file') - ,buttonText: _('upload.buttons.upload') - ,name: 'file' - ,id: this.ident+'-file' - ,anchor: '100%' + this.ident = config.ident || `window-import-policy-template-${Ext.id()}`; + Ext.applyIf(config, { + title: _('import'), + id: 'modx-window-policy-template-import', + url: MODx.config.connector_url, + action: 'Security/Access/Policy/Template/Import', + fileUpload: true, + saveBtnText: _('import'), + fields: [{ + html: _('policy_template_import_msg'), + xtype: 'modx-description', + style: 'margin-bottom: 10px;' + }, { + xtype: 'fileuploadfield', + fieldLabel: _('file'), + buttonText: _('upload.buttons.upload'), + name: 'file', + anchor: '100%' }] }); - MODx.window.ImportPolicyTemplate.superclass.constructor.call(this,config); + MODx.window.ImportPolicyTemplate.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.ImportPolicyTemplate,MODx.Window); -Ext.reg('modx-window-policy-template-import',MODx.window.ImportPolicyTemplate); +Ext.extend(MODx.window.ImportPolicyTemplate, MODx.Window); +Ext.reg('modx-window-policy-template-import', MODx.window.ImportPolicyTemplate); diff --git a/manager/assets/modext/widgets/security/modx.grid.role.js b/manager/assets/modext/widgets/security/modx.grid.role.js index c3323f3a385..a32c20def67 100644 --- a/manager/assets/modext/widgets/security/modx.grid.role.js +++ b/manager/assets/modext/widgets/security/modx.grid.role.js @@ -20,7 +20,7 @@ MODx.grid.Role = function(config = {}) { 'name', 'description', 'authority', - 'perm' + 'creator' ], paging: true, autosave: true, @@ -36,11 +36,27 @@ MODx.grid.Role = function(config = {}) { width: 150, sortable: true, editor: { - xtype: 'textfield' + xtype: 'textfield', + allowBlank: false, + blankText: _('role_err_ns_name'), + validationEvent: 'change', + validator: function(value) { + const + grid = Ext.getCmp('modx-grid-role'), + reserved = this.gridEditor.record.json.reserved.name + ; + if (grid.valueIsReserved(reserved, value)) { + const msg = _('role_err_name_reserved', { reservedName: value }); + Ext.Msg.alert(_('error'), msg); + return false; + } + return true; + } }, renderer: { fn: function(value, metaData, record, rowIndex, colIndex, store) { - metaData.css = this.setEditableCellClasses(record); + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); return Ext.util.Format.htmlEncode(value); }, scope: this @@ -49,18 +65,24 @@ MODx.grid.Role = function(config = {}) { header: _('description'), dataIndex: 'description', width: 350, - editor: { xtype: 'textarea' }, + editor: { + xtype: 'textarea' + }, renderer: { fn: function(value, metaData, record, rowIndex, colIndex, store) { - metaData.css = this.setEditableCellClasses(record); + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); return Ext.util.Format.htmlEncode(value); }, scope: this } - }, { + }, + this.getCreatorColumnConfig('role'), + { header: _('authority'), dataIndex: 'authority', width: 60, + align: 'center', sortable: true, editor: { xtype: 'numberfield', @@ -72,7 +94,8 @@ MODx.grid.Role = function(config = {}) { }, renderer: { fn: function(value, metaData, record, rowIndex, colIndex, store) { - metaData.css = this.setEditableCellClasses(record, [record.json.isAssigned]); + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isAssigned, record.json.isProtected], '', false); return value; }, scope: this @@ -84,7 +107,7 @@ MODx.grid.Role = function(config = {}) { selectedRecord = grid.getSelectionModel().getSelected(), roleIsAssigned = selectedRecord.json.isAssigned === 1 ; - if (roleIsAssigned) { + if (!selectedRecord.json.isProtected && roleIsAssigned) { Ext.Msg.show({ title: _('warning'), msg: _('role_warn_authority_locked'), @@ -98,24 +121,34 @@ MODx.grid.Role = function(config = {}) { } } }], - tbar: [{ - text: _('create'), - cls: 'primary-button', - handler: this.createRole, - scope: this - }] + tbar: [this.getCreateButton('role', 'createRole')], + viewConfig: this.getViewConfig(false, false) }); MODx.grid.Role.superclass.constructor.call(this, config); - this.on('beforeedit', this.checkCellIsEditable, this); + + this.gridMenuActions = ['delete']; + + this.setUserCanEdit(['save_role', 'edit_role']); + this.setUserCanCreate(['save_role', 'new_role']); + this.setUserCanDelete(['delete_role']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit || e.record.json.isProtected || (e.field === 'authority' && e.record.json.isAssigned)) { + return false; + } + } + }); }; Ext.extend(MODx.grid.Role, MODx.grid.Grid, { + getMenu: function() { const record = this.getSelectionModel().getSelected(), - permissions = record.data.perm || '', menu = [] ; - if (permissions.indexOf('remove') !== -1) { + if (this.userCanDeleteRecord(record)) { menu.push({ text: _('delete'), handler: this.remove.createDelegate(this, ['role_remove_confirm', 'Security/Role/Remove']) @@ -137,6 +170,7 @@ Ext.extend(MODx.grid.Role, MODx.grid.Grid, { } }); } + }); Ext.reg('modx-grid-role', MODx.grid.Role); @@ -160,17 +194,20 @@ MODx.window.CreateRole = function(config = {}) { fieldLabel: _('name'), xtype: 'textfield' }, { - xtype: MODx.expandHelp ? 'box' : 'hidden', + xtype: 'box', + hidden: !MODx.expandHelp, html: _('role_desc_name'), cls: 'desc-under' }, { name: 'authority', fieldLabel: _('authority'), - xtype: 'textfield', + xtype: 'numberfield', allowNegative: false, - value: 0 + value: 0, + maxValue: 9999 }, { - xtype: MODx.expandHelp ? 'box' : 'hidden', + xtype: 'box', + hidden: !MODx.expandHelp, html: _('role_desc_authority'), cls: 'desc-under' }, { @@ -180,7 +217,8 @@ MODx.window.CreateRole = function(config = {}) { allowBlank: true, grow: true }, { - xtype: MODx.expandHelp ? 'box' : 'hidden', + xtype: 'box', + hidden: !MODx.expandHelp, html: _('role_desc_description'), cls: 'desc-under' }], diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.base.js b/manager/assets/modext/widgets/security/modx.grid.user.group.base.js index 1a025ae3678..ef78bf52cb1 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.base.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.base.js @@ -55,6 +55,14 @@ MODx.grid.UserGroupBase = function UserGroupBase(config = {}) { MODx.grid.UserGroupBase.superclass.constructor.call(this, config); + this.gridMenuActions = ['edit', 'delete']; + + this.setUserCanEdit(['usergroup_edit', 'usergroup_save']); + this.userCanEditAcls = this.userCanEdit; + this.setUserCanCreate(['usergroup_create', 'usergroup_save']); + this.setUserCanDelete(['usergroup_delete']); + this.setShowActionsMenu(); + this.addEvents('createAcl', 'updateAcl'); this.on({ @@ -80,7 +88,9 @@ Ext.extend(MODx.grid.UserGroupBase, MODx.grid.Grid, { tpl: new Ext.XTemplate( `
      - {[ values.permissions.split(',').map(item => '
    • ' + item.trim() + '
    • ').join('') ]} + +
    • {.}
    • +
    ` ), @@ -90,33 +100,23 @@ Ext.extend(MODx.grid.UserGroupBase, MODx.grid.Grid, { return [this.rowExpander, ...columns]; }, getMenu: function() { - const record = this.getSelectionModel().getSelected(), - permissions = record.data.cls, - menu = [] - ; + const menu = []; if (this.getSelectionModel().getCount() > 1) { // Currently not allowing bulk actions for this grid - } else { - if (permissions.indexOf('pedit') !== -1) { - menu.push({ - text: _(`access_${this.aclType}_update`), - handler: this.updateAcl - }); - } - if (permissions.indexOf('premove') !== -1) { - if (menu.length > 0) { - menu.push('-'); - } - menu.push({ - text: _(`access_${this.aclType}_remove`), - handler: this.remove.createDelegate(this, ['confirm_remove', ACL_TYPES_CONFIG[this.aclType].actions.remove]) - }); + } else if (this.userCanEditAcls) { + menu.push({ + text: _(`access_${this.aclType}_update`), + handler: this.updateAcl + }); + if (menu.length > 0) { + menu.push('-'); } + menu.push({ + text: _(`access_${this.aclType}_remove`), + handler: this.remove.createDelegate(this, ['confirm_remove', ACL_TYPES_CONFIG[this.aclType].actions.remove]) + }); } - - if (menu.length > 0) { - this.addContextMenuItem(menu); - } + return menu; }, /** @@ -371,7 +371,7 @@ Ext.extend(MODx.window.UserGroupAclBase, MODx.Window, { */ getPermissionsList: function(window, record = {}) { const - permissions = record?.data?.permissions || window.record.permissions, + permissions = record?.json?.policyPermissions || window.record.policyPermissions, permissionsListContainer = window.fp?.getComponent(`${this.idPrefix}-permissions`), permissionsListCmp = permissionsListContainer?.getComponent(`${this.idPrefix}-permissions-list`), permissionsListLabelCmp = permissionsListContainer?.getComponent(`${this.idPrefix}-permissions-list-label`) 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 189576c9d87..733263cd95d 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 @@ -29,8 +29,7 @@ MODx.grid.UserGroupCategory = function UserGroupCategory(config = {}) { 'policy', 'policy_name', 'context_key', - 'permissions', - 'cls' + 'policyPermissions' ], columns: this.getColumns([ { 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 f977aa85172..5ebd9a811bb 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 @@ -27,8 +27,7 @@ MODx.grid.UserGroupContext = function UserGroupContext(config = {}) { 'role_display', 'policy', 'policy_name', - 'permissions', - 'cls' + 'policyPermissions' ], sortBy: 'target', columns: this.getColumns([ diff --git a/manager/assets/modext/widgets/security/modx.grid.user.group.js b/manager/assets/modext/widgets/security/modx.grid.user.group.js index 26a56481394..8cc52f1a18f 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.group.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.group.js @@ -6,161 +6,243 @@ * @param {Object} config An object of options. * @xtype modx-grid-user-groups */ -MODx.grid.UserGroups = function(config) { - config = config || {}; +MODx.grid.UserGroups = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {user_group_desc}

    ' ) }); - Ext.applyIf(config,{ - title: '' - ,id: 'modx-grid-user-groups' - ,url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + title: '', + id: 'modx-grid-user-groups', + /* + url and baseParams are not utilized by the core when this + grid is used (only in User > Access Permissions). Should remove + if this class is not meant to be somehow used externally (via Extra) + */ + url: MODx.config.connector_url, + baseParams: { action: 'Security/Group/GetList' - } - ,fields: ['usergroup','name','member','role','rolename','primary_group','rank','user_group_desc'] - ,cls: 'modx-grid modx-grid-draggable' - ,columns: [this.exp, - { - header: _('user_group') - ,dataIndex: 'name' - ,width: 175 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/usergroup/update&id=' + record.data.usergroup - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('role') - ,dataIndex: 'rolename' - ,width: 175 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/permission' - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('rank') - ,dataIndex: 'rank' - ,width: 80 - ,editor: { xtype: 'numberfield', allowBlank: false, allowNegative: false } - }] - ,plugins: [new Ext.ux.dd.GridDragDropRowOrder({ - copy: false - ,scrollable: true - ,targetCfg: {} - ,listeners: { - 'afterrowmove': {fn:this.onAfterRowMove,scope:this} - ,'beforerowmove': {fn:this.onBeforeRowMove,scope:this} + }, + fields: [ + 'usergroup', + 'name', + 'member', + 'role', + 'rolename', + 'primary_group', + 'rank', + 'user_group_desc' + ], + cls: 'modx-grid modx-grid-draggable', + columns: [ + this.exp, + { + header: _('user_group'), + dataIndex: 'name', + width: 175, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditGroups + ? this.renderLink(value, { + href: `?a=security/usergroup/update&id=${record.data.usergroup}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, { + header: _('role'), + dataIndex: 'rolename', + width: 175, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditRoles + ? this.renderLink(value, { + href: `?a=security/permission&tab=1&role=${record.data.role}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, { + header: _('rank'), + dataIndex: 'rank', + width: 80, + editor: { + xtype: 'numberfield', + allowBlank: false, + allowNegative: false + } } - }), - this.exp] - ,tbar: [{ - text: _('user_group_user_add') - ,cls:'primary-button' - ,handler: this.addGroup - }] + ], + plugins: [this.exp], + tbar: [ + this.getCreateButton('user_group_user', 'addGroup', 'userCanEditGroupUsers') + ] }); - MODx.grid.UserGroups.superclass.constructor.call(this,config); - this.userRecord = new Ext.data.Record.create(['usergroup','name','member','role','rolename','primary_group']); - this.addEvents('beforeUpdateRole','afterUpdateRole','beforeAddGroup','afterAddGroup','beforeReorderGroup','afterReorderGroup'); + + this.gridMenuActions = ['editGroupUsers']; + this.setUserHasPermissions('editGroups', ['usergroup_edit', 'usergroup_save']); + this.setUserHasPermissions('editGroupUsers', ['usergroup_user_edit']); + this.setUserHasPermissions('editRoles', ['edit_role', 'save_role']); + + if (this.userCanEditGroupUsers) { + config.plugins.push( + new Ext.ux.dd.GridDragDropRowOrder({ + copy: false, + scrollable: true, + targetCfg: {}, + listeners: { + afterrowmove: { + fn: this.onAfterRowMove, + scope: this + }, + /** + * @deprecated In 3.1, appears to be unused + */ + beforerowmove: { + fn: this.onBeforeRowMove, + scope: this + } + } + }) + ); + } + + MODx.grid.UserGroups.superclass.constructor.call(this, config); + + this.userRecord = new Ext.data.Record.create([ + 'usergroup', + 'name', + 'member', + 'role', + 'rolename', + 'primary_group' + ]); + this.addEvents( + 'beforeUpdateRole', + 'afterUpdateRole', + 'beforeAddGroup', + 'afterAddGroup', + 'beforeReorderGroup', + 'afterReorderGroup' + ); + + this.setShowActionsMenu(); }; -Ext.extend(MODx.grid.UserGroups,MODx.grid.LocalGrid,{ - _showMenu: function(g,ri,e) { - e.stopEvent(); - e.preventDefault(); - var m = this.menu; - m.recordIndex = ri; - m.record = this.getStore().getAt(ri).data; - if (!this.getSelectionModel().isSelected(ri)) { - this.getSelectionModel().selectRow(ri); +Ext.extend(MODx.grid.UserGroups, MODx.grid.LocalGrid, { + getMenu: function() { + const menu = []; + if (this.userCanEditGroupUsers) { + menu.push({ + text: _('user_role_update'), + handler: this.updateRole, + scope: this + }); + menu.push('-'); + menu.push({ + text: _('user_group_user_remove'), + handler: this.remove.createDelegate(this, [{ + text: _('user_group_user_remove_confirm') + }]), + scope: this + }); } - m.removeAll(); - m.add({ - text: _('user_role_update') - ,handler: this.updateRole - ,scope: this - },'-',{ - text: _('user_group_user_remove') - ,handler: this.remove.createDelegate(this,[{text: _('user_group_user_remove_confirm')}]) - ,scope: this - }); - m.showAt(e.xy); - } + return menu; + }, - ,onBeforeRowMove: function(dt,sri,ri,sels) { - if (!this.fireEvent('beforeReorderGroup',{dt:dt,sri:sri,ri:ri,sels:sels})) { + /** + * @deprecated In 3.1, appears to be unused (including the beforeReorderGroup event) + */ + onBeforeRowMove: function(dropTarget, fromRowIndex, toRowIndex, selections) { + if (!this.fireEvent('beforeReorderGroup', { + dt: dropTarget, + sri: fromRowIndex, + ri: toRowIndex, + sels: selections + })) { return false; } return true; - } + }, - ,onAfterRowMove: function(dt,sri,ri,sels) { - var s = this.getStore(); - var sourceRec = s.getAt(sri); - var belowRec = s.getAt(ri); - var total = s.getTotalCount(); + onAfterRowMove: function(dropTarget, fromRowIndex, toRowIndex, selections) { + const + store = this.getStore(), + firstDraggedRecord = store.getAt(fromRowIndex), + total = store.getTotalCount() + ; + firstDraggedRecord.set('rank', fromRowIndex); + firstDraggedRecord.commit(); - sourceRec.set('rank',sri); - sourceRec.commit(); - - /* get all rows below ri, and up their rank by 1 */ - var brec; - for (var x=(ri-1);x'+_('user_management_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user' - ,cls:'main-wrapper' - ,preventRender: true + Ext.applyIf(config, { + id: 'modx-panel-users', + cls: 'container', + bodyStyle: '', + defaults: { collapsible: false, autoHeight: true }, + items: [{ + html: _('users'), + id: 'modx-users-header', + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('users'), + layout: 'form', + items: [{ + html: `

    ${_('user_management_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user', + cls: 'main-wrapper', + preventRender: true }] }])] }); - MODx.panel.Users.superclass.constructor.call(this,config); + MODx.panel.Users.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Users,MODx.FormPanel); -Ext.reg('modx-panel-users',MODx.panel.Users); +Ext.extend(MODx.panel.Users, MODx.FormPanel); +Ext.reg('modx-panel-users', MODx.panel.Users); /** * Loads a grid of User. @@ -44,13 +44,13 @@ Ext.reg('modx-panel-users',MODx.panel.Users); */ 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 || null - } - ,fields: [ + Ext.applyIf(config, { + url: MODx.config.connector_url, + baseParams: { + action: 'Security/User/GetList', + usergroup: MODx.request.usergroup || null + }, + fields: [ 'id', 'username', 'fullname', @@ -58,104 +58,134 @@ MODx.grid.User = function(config = {}) { 'gender', 'blocked', 'role', - 'active', - 'cls' - ] - ,paging: true - ,autosave: true - ,save_action: 'Security/User/UpdateFromGrid' - ,autosaveErrorMsg: _('user_err_save') - ,remoteSort: true - ,viewConfig: { - forceFit:true - ,enableRowBody:true - ,scrollOffset: 0 - ,autoFill: true - ,showPreview: true - ,getRowClass : function(rec){ - return rec.data.active ? 'grid-row-active' : 'grid-row-inactive'; + 'active' + ], + paging: true, + autosave: true, + save_action: 'Security/User/UpdateFromGrid', + autosaveErrorMsg: _('user_err_save'), + remoteSort: true, + viewConfig: { + forceFit: true, + enableRowBody: true, + scrollOffset: 0, + autoFill: true, + showPreview: true, + getRowClass: function(record) { + return record.data.active ? 'grid-row-active' : 'grid-row-inactive'; } - } - ,sm: this.sm - ,columns: [this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 50 - ,sortable: true - },{ - header: _('username') - ,dataIndex: 'username' - ,width: 150 - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/user/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('user_full_name') - ,dataIndex: 'fullname' - ,width: 180 - ,sortable: true - ,editor: { xtype: 'textfield' } - ,renderer: Ext.util.Format.htmlEncode - },{ - header: _('email') - ,dataIndex: 'email' - ,width: 180 - ,sortable: true - ,editor: { xtype: 'textfield' } - },{ - header: _('active') - ,dataIndex: 'active' - ,width: 80 - ,sortable: true - ,editor: { xtype: 'combo-boolean', renderer: 'boolean' } - },{ - header: _('user_block') - ,dataIndex: 'blocked' - ,width: 80 - ,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 - ,scope: this - },{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - } - ] + }, + sm: this.sm, + columns: [this.sm, { + header: _('id'), + dataIndex: 'id', + width: 50, + sortable: true + }, { + header: _('username'), + dataIndex: 'username', + width: 150, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=security/user/update&id=${record.data.id}`, + title: _('user_edit_account') + }) + : value + ; + }, + scope: this + } + }, { + header: _('user_full_name'), + dataIndex: 'fullname', + width: 180, + sortable: true, + editor: { xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this + } + }, { + header: _('email'), + dataIndex: 'email', + width: 180, + sortable: true, + editor: { + xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record); + return value; + }, + scope: this + } + }, { + header: _('active'), + dataIndex: 'active', + width: 80, + sortable: true, + editor: { + xtype: 'combo-boolean' + }, + renderer: { + fn: function(value, metaData, record) { + const + displayValue = this.rendYesNo(value, metaData), + classes = `${metaData.css} ${this.setEditableCellClasses(record)}` + ; + // eslint-disable-next-line no-param-reassign + metaData.css = classes; + return displayValue; + }, + scope: this + } + }, { + header: _('user_block'), + dataIndex: 'blocked', + width: 80, + sortable: true, + editor: { + xtype: 'combo-boolean' + }, + renderer: { + fn: function(value, metaData, record) { + const + displayValue = this.rendYesNo(value, metaData), + classes = `${metaData.css} ${this.setEditableCellClasses(record)}` + ; + // eslint-disable-next-line no-param-reassign + metaData.css = classes; + return displayValue; + }, + scope: this + } + }], + tbar: [ + this.getCreateButton('user', 'createUser'), + this.getBulkActionsButton('user', 'Security/User/RemoveMultiple', 'int', 'activate', 'deactivate'), '->', { - 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: { + 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) { + fn: function(cmp, record, selectedIndex) { this.applyGridFilter(cmp, 'usergroup'); }, scope: this @@ -166,165 +196,153 @@ MODx.grid.User = function(config = {}) { this.getClearFiltersButton('filter-usergroup, filter-query') ] }); - MODx.grid.User.superclass.constructor.call(this,config); + MODx.grid.User.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate', 'activate']; + + this.setUserCanEdit(['edit_user', 'save_user']); + this.setUserCanCreate(['new_user', 'save_user']); + this.setUserCanDelete(['delete_user']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit || !this.userCanEditRecord(e.record)) { + return false; + } + } + }); }; -Ext.extend(MODx.grid.User,MODx.grid.Grid,{ +Ext.extend(MODx.grid.User, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; + const menu = []; if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_activate') - ,handler: this.activateSelected - ,scope: this - }); - m.push({ - text: _('selected_deactivate') - ,handler: this.deactivateSelected - ,scope: this - }); - m.push('-'); - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }); + if (this.userCanEdit) { + menu.push({ + text: _('selected_activate'), + handler: this.activateSelected, + scope: this + }); + menu.push({ + text: _('selected_deactivate'), + handler: this.deactivateSelected, + scope: this + }); + } + if (this.userCanDelete) { + menu.push('-'); + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected.bind(this, 'user', 'Security/User/RemoveMultiple') + }); + } } else { - if (p.indexOf('pupdate') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateUser + if (this.userCanEdit) { + menu.push({ + text: _('edit'), + handler: this.updateUser }); } - if (p.indexOf('pcopy') != -1) { - if (m.length > 0) m.push('-'); - m.push({ - text: _('duplicate') - ,handler: this.duplicateUser + if (this.userCanCreate) { + if (menu.length > 0) { menu.push('-'); } + menu.push({ + text: _('duplicate'), + handler: this.duplicateUser }); } - if (p.indexOf('premove') != -1) { - if (m.length > 0) m.push('-'); - m.push({ - text: _('delete') - ,handler: this.removeUser + if (this.userCanDelete) { + if (menu.length > 0) { menu.push('-'); } + menu.push({ + text: _('delete'), + handler: this.removeUser }); } } - if (m.length > 0) { - this.addContextMenuItem(m); - } - } + return menu; + }, - ,createUser: function() { + createUser: function() { MODx.loadPage('security/user/create'); - } + }, - ,updateUser: function() { - MODx.loadPage('security/user/update', 'id='+this.menu.record.id); - } + updateUser: function() { + MODx.loadPage('security/user/update', `id=${this.menu.record.id}`); + }, - ,duplicateUser: function() { + duplicateUser: function() { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/User/Duplicate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + url: this.config.url, + params: { + action: 'Security/User/Duplicate', + id: this.menu.record.id + }, + listeners: { + success: { fn: this.refresh, scope: this } } }); - } + }, - ,removeUser: function() { + removeUser: function() { MODx.msg.confirm({ - title: _('delete') - ,text: _('user_confirm_remove') - ,url: this.config.url - ,params: { - action: 'Security/User/Delete' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + title: _('delete'), + text: _('user_confirm_remove'), + url: this.config.url, + params: { + action: 'Security/User/Delete', + id: this.menu.record.id + }, + listeners: { + success: { fn: this.refresh, scope: this } } }); - } - - ,activateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + activateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/User/ActivateMultiple' - ,users: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/User/ActivateMultiple', + users: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; - } - - ,deactivateSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + }, + deactivateSelected: function() { + const selections = this.getSelectedAsList(); + if (selections === false) { + return false; + } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Security/User/DeactivateMultiple' - ,users: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('user_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Security/User/RemoveMultiple' - ,users: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'Security/User/DeactivateMultiple', + users: selections + }, + listeners: { + success: { + fn: function() { + this.getSelectionModel().clearSelections(true); + this.refresh(); + }, + scope: this + } } }); return true; } - - ,rendGender: function(d,c) { - switch(d.toString()) { - case '0': - return '-'; - case '1': - return _('male'); - case '2': - return _('female'); - } - } }); -Ext.reg('modx-grid-user',MODx.grid.User); +Ext.reg('modx-grid-user', MODx.grid.User); diff --git a/manager/assets/modext/widgets/security/modx.grid.user.recent.resource.js b/manager/assets/modext/widgets/security/modx.grid.user.recent.resource.js index a64f3929cde..ffddb242ac8 100644 --- a/manager/assets/modext/widgets/security/modx.grid.user.recent.resource.js +++ b/manager/assets/modext/widgets/security/modx.grid.user.recent.resource.js @@ -6,72 +6,116 @@ * @param {Object} config An object of options. * @xtype modx-grid-user-recent-resource */ -MODx.grid.RecentlyEditedResourcesByUser = function(config) { - config = config || {}; - var dateFormat = MODx.config.manager_date_format + ' ' + MODx.config.manager_time_format; - Ext.applyIf(config,{ - title: _('recent_docs') - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Security/User/GetRecentlyEditedResources' - ,user: config.user - } - ,autosave: true - ,save_action: 'Resource/UpdateFromGrid' - ,pageSize: 10 - ,fields: ['id','pagetitle','description','editedon','deleted','published','context_key','menu', 'link', 'occurred'] - ,columns: [{ - header: _('id') - ,dataIndex: 'id' - ,width: 75 - ,fixed: true - },{ - header: _('pagetitle') - ,dataIndex: 'pagetitle' - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=resource/update&id=' + record.data.id - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('editedon') - ,dataIndex: 'occurred' - },{ - header: _('published') - ,dataIndex: 'published' - ,width: 120 - ,fixed: true - ,editor: { xtype: 'combo-boolean' ,renderer: 'boolean' } - }] - ,paging: true - ,listeners: { - afteredit: this.refresh - ,afterrender: this.onAfterRender - ,scope: this +MODx.grid.RecentlyEditedResourcesByUser = function(config = {}) { + const dateFormat = `${MODx.config.manager_date_format} ${MODx.config.manager_time_format}`; + Ext.applyIf(config, { + title: _('recent_docs'), + url: MODx.config.connector_url, + baseParams: { + action: 'Security/User/GetRecentlyEditedResources', + user: config.user + }, + autosave: true, + save_action: 'Resource/UpdateFromGrid', + pageSize: 10, + fields: [ + 'id', + 'pagetitle', + 'description', + 'editedon', + 'deleted', + 'published', + 'context_key', + 'menu', + 'link', + 'occurred' + ], + columns: [{ + header: _('id'), + dataIndex: 'id', + width: 75, + fixed: true + }, { + header: _('pagetitle'), + dataIndex: 'pagetitle', + renderer: { + fn: function(value, metaData, record) { + return this.renderLink(value, { + href: `?a=resource/update&id=${record.data.id}`, + target: '_blank' + }); + }, + scope: this + } + }, { + header: _('editedon'), + dataIndex: 'occurred', + renderer: Ext.util.Format.dateRenderer(dateFormat) + }, { + header: _('published'), + dataIndex: 'published', + width: 120, + fixed: true, + editor: { + xtype: 'combo-boolean', + renderer: 'boolean' + } + }], + paging: true, + listeners: { + afteredit: this.refresh, + afterrender: this.onAfterRender, + scope: this } }); - MODx.grid.RecentlyEditedResourcesByUser.superclass.constructor.call(this,config); + MODx.grid.RecentlyEditedResourcesByUser.superclass.constructor.call(this, config); }; -Ext.extend(MODx.grid.RecentlyEditedResourcesByUser,MODx.grid.Grid,{ +Ext.extend(MODx.grid.RecentlyEditedResourcesByUser, MODx.grid.Grid, { + getMenu: function() { + const menu = []; + menu.push({ + text: _('resource_overview'), + params: { + a: 'resource/data', + type: 'view' + } + }); + if (MODx.perm.edit_document) { + menu.push({ + text: _('resource_edit'), + params: { + a: 'resource/update', + type: 'edit' + } + }); + } + menu.push('-'); + menu.push({ + text: _('resource_view'), + handler: this.preview + }); + + return menu; + }, + preview: function() { window.open(this.menu.record.link); - } - ,refresh: function() { - var tree = Ext.getCmp('modx-resource-tree'); + }, + refresh: function() { + const tree = Ext.getCmp('modx-resource-tree'); if (tree && tree.rendered) { tree.refresh(); } - } + }, // Workaround to resize the grid when in a dashboard widget - ,onAfterRender: function() { - var cnt = Ext.getCmp('modx-content') - // Dashboard widget "parent" (renderTo) - ,parent = Ext.get('modx-grid-user-recent-resource'); - - if (cnt && parent) { - cnt.on('afterlayout', function(elem, layout) { - var width = parent.getWidth(); + onAfterRender: function() { + const + contentCmp = Ext.getCmp('modx-content'), + grid = Ext.get('modx-grid-user-recent-resource') + ; + if (contentCmp && grid) { + contentCmp.on('afterlayout', function(elem, layout) { + const width = grid.getWidth(); // Only resize when more than 500px (else let's use/enable the horizontal scrolling) if (width > 500) { this.setWidth(width); @@ -80,4 +124,4 @@ Ext.extend(MODx.grid.RecentlyEditedResourcesByUser,MODx.grid.Grid,{ } } }); -Ext.reg('modx-grid-user-recent-resource',MODx.grid.RecentlyEditedResourcesByUser); +Ext.reg('modx-grid-user-recent-resource', MODx.grid.RecentlyEditedResourcesByUser); 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 d1bb58f287a..da842026514 100644 --- a/manager/assets/modext/widgets/security/modx.panel.user.group.js +++ b/manager/assets/modext/widgets/security/modx.panel.user.group.js @@ -4,240 +4,242 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-user-group */ -MODx.panel.UserGroup = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-user-group' - ,cls: 'container form-with-labels' - ,url: MODx.config.connector_url - ,baseParams: { +MODx.panel.UserGroup = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-user-group', + cls: 'container form-with-labels', + url: MODx.config.connector_url, + baseParams: { action: 'Security/Group/Update' - } - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [this.getPageHeader(config),{ - xtype: 'modx-tabs' - ,defaults: { - autoHeight: true - ,border: true - ,bodyCssClass: 'tab-panel-wrapper' - ,hideMode: 'offsets' - } - ,id: 'modx-usergroup-tabs' - ,forceLayout: true - ,deferredRender: false - ,items: [{ - title: _('general_information') - ,defaults: { - border: false - ,msgTarget: 'side' - } - ,layout: 'form' - ,itemId: 'modx-usergroup-general-panel' - ,labelAlign: 'top' - ,labelSeparator: '' - ,items: [{ - xtype: 'panel' - ,border: false - ,cls: 'main-wrapper' - ,layout: 'form' - ,items: [{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,labelSeparator: '' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .6 - ,items: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-usergroup-id' - ,value: config.record.id - },{ - name: 'name' - ,id: 'modx-usergroup-name' - ,xtype: config.record && (config.record.name === 'Administrator' || config.record.id === 0) ? 'statictextfield' : 'textfield' - ,fieldLabel: _('name') - ,allowBlank: false - ,enableKeyEvents: true - ,disabled: config.record.id === 0 - ,anchor: '100%' - ,listeners: { - 'keyup': {scope:this,fn:function(f,e) { - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); - }} + }, + defaults: { + collapsible: false, + autoHeight: true + }, + items: [this.getPageHeader(config), { + xtype: 'modx-tabs', + defaults: { + autoHeight: true, + border: true, + bodyCssClass: 'tab-panel-wrapper', + hideMode: 'offsets' + }, + id: 'modx-usergroup-tabs', + forceLayout: true, + deferredRender: false, + items: [{ + title: _('general_information'), + defaults: { + border: false, + msgTarget: 'side' + }, + layout: 'form', + itemId: 'modx-usergroup-general-panel', + labelAlign: 'top', + labelSeparator: '', + items: [{ + xtype: 'panel', + border: false, + cls: 'main-wrapper', + layout: 'form', + items: [{ + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelAlign: 'top', + labelSeparator: '', + anchor: '100%', + border: false + }, + items: [{ + columnWidth: 0.6, + items: [{ + xtype: 'hidden', + name: 'id', + id: 'modx-usergroup-id', + value: config.record.id + }, { + name: 'name', + xtype: config.record && (config.record.name === 'Administrator' || config.record.id === 0) ? 'statictextfield' : 'textfield', + fieldLabel: _('name'), + allowBlank: false, + enableKeyEvents: true, + disabled: config.record.id === 0, + anchor: '100%', + listeners: { + keyup: { + fn: function(f, e) { + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); + }, + scope: this + } } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-usergroup-name' - ,html: _('user_group_desc_name') - ,cls: 'desc-under' - },{ - name: 'description' - ,id: 'modx-usergroup-description' - ,xtype: 'textarea' - ,fieldLabel: _('description') - ,anchor: '100%' - ,grow: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-usergroup-description' - ,html: _('user_group_desc_description') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_desc_name'), + cls: 'desc-under' + }, { + name: 'description', + xtype: 'textarea', + fieldLabel: _('description'), + anchor: '100%', + grow: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_desc_description'), + cls: 'desc-under' }] - },{ - columnWidth: .4 - ,items: [{ - name: 'parent' - ,hiddenName: 'parent' - ,id: 'modx-usergroup-parent' - ,xtype: 'modx-combo-usergroup' - ,fieldLabel: _('user_group_parent') - ,editable: false - ,anchor: '100%' - ,disabled: config.record.id === 0 || config.record.name === 'Administrator' - ,baseParams: { - action: 'Security/Group/GetList' - ,addNone: true - ,exclude: config.record.id + }, { + columnWidth: 0.4, + items: [{ + name: 'parent', + hiddenName: 'parent', + xtype: 'modx-combo-usergroup', + fieldLabel: _('user_group_parent'), + editable: false, + anchor: '100%', + disabled: config.record.id === 0 || config.record.name === 'Administrator', + baseParams: { + action: 'Security/Group/GetList', + addNone: true, + exclude: config.record.id } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-usergroup-parent' - ,html: _('user_group_desc_parent') - ,cls: 'desc-under' - },{ - name: 'dashboard' - ,id: 'modx-usergroup-dashboard' - ,xtype: 'modx-combo-dashboard' - ,fieldLabel: _('dashboard') - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-usergroup-dashboard' - ,html: _('user_group_desc_dashboard') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_desc_parent'), + cls: 'desc-under' + }, { + name: 'dashboard', + xtype: 'modx-combo-dashboard', + fieldLabel: _('dashboard'), + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_desc_dashboard'), + cls: 'desc-under' }] }] }] }] - },{ - title: _('access_permissions') - ,itemId: 'modx-usergroup-permissions-panel' - ,items: [{ - xtype: 'modx-vtabs' - ,items: [{ - title: _('user_group_context_access') - ,itemId: 'user-group-context-access' - ,hideMode: 'offsets' - ,layout: 'form' - ,autoWidth: false - ,items: [{ - html: '

    '+_('user_group_context_access_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-context' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true - ,cls: 'main-wrapper' - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'afteredit': {fn:this.markDirty,scope:this} - ,'updateAcl': {fn:this.markDirty,scope:this} - ,'createAcl': {fn:this.markDirty,scope:this} + }, { + title: _('access_permissions'), + itemId: 'modx-usergroup-permissions-panel', + items: [{ + xtype: 'modx-vtabs', + items: [{ + title: _('user_group_context_access'), + itemId: 'user-group-context-access', + hideMode: 'offsets', + layout: 'form', + autoWidth: false, + items: [{ + html: `

    ${_('user_group_context_access_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-context', + preventRender: true, + usergroup: config.record.id, + autoHeight: true, + cls: 'main-wrapper', + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + afteredit: { fn: this.markDirty, scope: this }, + updateAcl: { fn: this.markDirty, scope: this }, + createAcl: { fn: this.markDirty, scope: this } } }] - },{ - title: _('user_group_resourcegroup_access') - ,itemId: 'user-group-resourcegroup-access' - ,hideMode: 'offsets' - ,layout: 'form' - ,items: [{ - html: '

    '+_('user_group_resourcegroup_access_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-resource-group' - ,cls: 'main-wrapper' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'afteredit': {fn:this.markDirty,scope:this} - ,'updateAcl': {fn:this.markDirty,scope:this} - ,'createAcl': {fn:this.markDirty,scope:this} + }, { + title: _('user_group_resourcegroup_access'), + itemId: 'user-group-resourcegroup-access', + hideMode: 'offsets', + layout: 'form', + items: [{ + html: `

    ${_('user_group_resourcegroup_access_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-resource-group', + cls: 'main-wrapper', + preventRender: true, + usergroup: config.record.id, + autoHeight: true, + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + afteredit: { fn: this.markDirty, scope: this }, + updateAcl: { fn: this.markDirty, scope: this }, + createAcl: { fn: this.markDirty, scope: this } } }] - },{ - title: _('user_group_category_access') - ,itemId: 'user-group-category-access' - ,hideMode: 'offsets' - ,layout: 'form' - ,items: [{ - html: '

    '+_('user_group_category_access_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-category' - ,cls: 'main-wrapper' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'afteredit': {fn:this.markDirty,scope:this} - ,'updateAcl': {fn:this.markDirty,scope:this} - ,'createAcl': {fn:this.markDirty,scope:this} + }, { + title: _('user_group_category_access'), + itemId: 'user-group-category-access', + hideMode: 'offsets', + layout: 'form', + items: [{ + html: `

    ${_('user_group_category_access_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-category', + cls: 'main-wrapper', + preventRender: true, + usergroup: config.record.id, + autoHeight: true, + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + afteredit: { fn: this.markDirty, scope: this }, + updateAcl: { fn: this.markDirty, scope: this }, + createAcl: { fn: this.markDirty, scope: this } } }] - },{ - title: _('user_group_source_access') - ,itemId: 'user-group-source-access' - ,hideMode: 'offsets' - ,layout: 'form' - ,items: [{ - html: '

    '+_('user_group_source_access_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-source' - ,cls: 'main-wrapper' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'afteredit': {fn:this.markDirty,scope:this} - ,'updateAcl': {fn:this.markDirty,scope:this} - ,'createAcl': {fn:this.markDirty,scope:this} + }, { + title: _('user_group_source_access'), + itemId: 'user-group-source-access', + hideMode: 'offsets', + layout: 'form', + items: [{ + html: `

    ${_('user_group_source_access_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-source', + cls: 'main-wrapper', + preventRender: true, + usergroup: config.record.id, + autoHeight: true, + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + afteredit: { fn: this.markDirty, scope: this }, + updateAcl: { fn: this.markDirty, scope: this }, + createAcl: { fn: this.markDirty, scope: this } } }] - },{ - title: _('user_group_namespace_access') - ,itemId: 'user-group-namespace-access' - ,hideMode: 'offsets' - ,layout: 'form' - ,items: [{ - html: '

    ' + _('user_group_namespace_access_desc') + '

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-namespace' - ,cls: 'main-wrapper' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true + }, { + title: _('user_group_namespace_access'), + itemId: 'user-group-namespace-access', + hideMode: 'offsets', + layout: 'form', + items: [{ + html: `

    ${_('user_group_namespace_access_desc')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-namespace', + cls: 'main-wrapper', + preventRender: true, + usergroup: config.record.id, + autoHeight: true }] - }] - ,listeners: { + }], + listeners: { render: function(vtabPanel) { - var elCatsPanelKey = vtabPanel.items.keys.indexOf('user-group-category-access'), + const + elCatsPanelKey = vtabPanel.items.keys.indexOf('user-group-category-access'), mediaSrcPanelKey = vtabPanel.items.keys.indexOf('user-group-source-access'), - namespacePanelKey = vtabPanel.items.keys.indexOf('user-group-namespace-access') + namespacePanelKey = vtabPanel.items.keys.indexOf('user-group-namespace-access'), form = Ext.getCmp('modx-panel-user-group').getForm() - ; + ; if (form.record.id === 0) { vtabPanel.hideTabStripItem(elCatsPanelKey); vtabPanel.hideTabStripItem(mediaSrcPanelKey); @@ -246,45 +248,46 @@ MODx.panel.UserGroup = function(config) { } } }] - },{ - title: _('users') - ,itemId: 'modx-usergroup-users-panel' - ,items: [{ - html: '

    '+_('user_group_user_access_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-user-group-users' - ,cls: 'main-wrapper' - ,preventRender: true - ,usergroup: config.record.id - ,autoHeight: true - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'updateRole': {fn:this.markDirty,scope:this} - ,'addUser': {fn:this.markDirty,scope:this} + }, { + title: _('users'), + itemId: 'modx-usergroup-users-panel', + items: [{ + html: `

    ${_('user_group_user_access_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-user-group-users', + cls: 'main-wrapper', + preventRender: true, + usergroup: config.record.id, + autoHeight: true, + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + updateRole: { fn: this.markDirty, scope: this }, + addUser: { fn: this.markDirty, scope: this } } }] - },{ - title: _('settings') - ,itemId: 'modx-usergroup-settings-panel' - ,layout: 'form' - ,items: [{ - html: '

    '+_('user_group_settings_desc')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-group-settings' - ,cls: 'main-wrapper' - ,preventRender: true - ,group: config.record.id - ,autoHeight: true + }, { + title: _('settings'), + itemId: 'modx-usergroup-settings-panel', + layout: 'form', + items: [{ + html: `

    ${_('user_group_settings_desc')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-group-settings', + cls: 'main-wrapper', + preventRender: true, + group: config.record.id, + autoHeight: true }] - }] - ,listeners: { + }], + listeners: { render: function(tabPanel) { - var usersPanelKey = tabPanel.items.keys.indexOf('modx-usergroup-users-panel'), + const + usersPanelKey = tabPanel.items.keys.indexOf('modx-usergroup-users-panel'), settingsPanelKey = tabPanel.items.keys.indexOf('modx-usergroup-settings-panel'), form = Ext.getCmp('modx-panel-user-group').getForm() - ; + ; if (form.record.id === 0) { tabPanel.hideTabStripItem(usersPanelKey); tabPanel.hideTabStripItem(settingsPanelKey); @@ -294,41 +297,41 @@ MODx.panel.UserGroup = function(config) { } } } - }] - ,useLoadingMask: false - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + }], + useLoadingMask: false, + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.UserGroup.superclass.constructor.call(this,config); + MODx.panel.UserGroup.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.UserGroup,MODx.FormPanel,{ - initialized: false - ,setup: function() { - if (this.initialized || this.config.usergroup === '' || this.config.usergroup == undefined) { +Ext.extend(MODx.panel.UserGroup, MODx.FormPanel, { + initialized: false, + setup: function() { + if (this.initialized || this.config.usergroup === '' || this.config.usergroup === undefined) { this.fireEvent('ready'); return false; } - var r = this.config.record; - this.getForm().setValues(r); - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(r.name)); + const { record } = this.config; + this.getForm().setValues(record); + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(record.name)); - this.fireEvent('ready',r); + this.fireEvent('ready', record); MODx.fireEvent('ready'); this.initialized = true; - } - ,beforeSubmit: function(o) {} - ,success: function(o) {} - ,getPageHeader: function(config) { + }, + beforeSubmit: function(o) {}, + success: function(o) {}, + getPageHeader: function(config) { return MODx.util.getHeaderBreadCrumbs('modx-user-group-header', [{ text: _('user_group_management'), href: MODx.getPage('security/permission') }]); } }); -Ext.reg('modx-panel-user-group',MODx.panel.UserGroup); +Ext.reg('modx-panel-user-group', MODx.panel.UserGroup); /** * @class MODx.grid.FCProfileUserGroups @@ -339,153 +342,197 @@ Ext.reg('modx-panel-user-group',MODx.panel.UserGroup); 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 , + 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' - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Security/Group/User/GetList' - ,usergroup: config.usergroup - } - ,paging: true - ,grouping: true - ,remoteSort: true - ,groupBy: 'role_name' - ,singleText: _('user') - ,pluralText: _('users') - ,sortBy: 'authority' - ,sortDir: 'ASC' - ,fields: [ + Ext.applyIf(config, { + title: '', + id: 'modx-grid-user-group-users', + url: MODx.config.connector_url, + baseParams: { + action: 'Security/Group/User/GetList', + usergroup: config.usergroup + }, + paging: true, + grouping: true, + remoteSort: true, + groupBy: 'role_name', + singleText: _('user'), + pluralText: _('users'), + sortBy: 'authority', + sortDir: 'ASC', + fields: [ 'id', 'username', 'role', 'role_name', 'authority' - ] - ,columns: [{ - header: _('username') - ,dataIndex: 'username' - ,width: 175 - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/user/update&id=' + record.data.id - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('role') - ,dataIndex: 'role_name' - ,width: 175 - ,sortable: true - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=security/permission' - ,target: '_blank' - }); - }, scope: this } - }] - ,tbar: [ + ], + columns: [{ + header: _('username'), + dataIndex: 'username', + width: 175, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditUsers + ? this.renderLink(value, { + href: `?a=security/user/update&id=${record.id}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }, { + header: _('role'), + dataIndex: 'role_name', + width: 175, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + return this.userCanEditRoles + ? this.renderLink(value, { + href: `?a=security/permission&tab=1&role=${record.json.role}`, + target: '_blank' + }) + : value + ; + }, + scope: this + } + }], + tbar: [ + /* + Because visibility of these buttons is determined by non-standard + and differing create permissions, not using base method getCreateButton() + here; controlled by render listener below + */ { - 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 + text: _('user_group_update'), + id: 'modx-btn-user-group-edit', + cls: 'primary-button', + handler: this.updateUserGroup + }, { + text: _('user_group_user_add'), + id: 'modx-btn-user-group-add-user', + cls: 'primary-button', + handler: this.addUser }, '->', this.getQueryFilterField(`filter-query-users:${queryValue}`, 'user-group-users'), this.getClearFiltersButton('filter-query-users') ] }); - MODx.grid.UserGroupUsers.superclass.constructor.call(this,config); - this.addEvents('updateRole','addUser'); + MODx.grid.UserGroupUsers.superclass.constructor.call(this, config); + this.addEvents('updateRole', 'addUser'); + + this.gridMenuActions = ['editGroupUsers']; + this.setUserHasPermissions('editGroups', ['usergroup_edit', 'usergroup_save']); + this.setUserHasPermissions('editGroupUsers', ['usergroup_user_edit']); + this.setUserHasPermissions('editRoles', ['edit_role', 'save_role']); + this.setUserHasPermissions('editUsers', ['edit_user', 'save_user']); + this.setShowActionsMenu(); + + this.on({ + render: grid => { + const buttonsToHide = []; + if (!this.userCanEditGroups || grid.ownerCt.id !== 'modx-tree-panel-usergroup') { + buttonsToHide.push('modx-btn-user-group-edit'); + } + if (!this.userCanEditGroupUsers) { + buttonsToHide.push('modx-btn-user-group-add-user'); + } + if (buttonsToHide.length > 0) { + buttonsToHide.forEach(btnId => Ext.getCmp(btnId)?.hide()); + } + } + }); }; -Ext.extend(MODx.grid.UserGroupUsers,MODx.grid.Grid,{ +Ext.extend(MODx.grid.UserGroupUsers, MODx.grid.Grid, { getMenu: function() { - var m = []; - if (MODx.perm.usergroup_user_edit) { - m.push({ - text: _('user_role_update') - ,handler: this.updateRole + const menu = []; + if (this.userCanEditGroupUsers) { + menu.push({ + text: _('user_role_update'), + handler: this.updateRole }); - m.push('-'); - m.push({ - text: _('user_group_user_remove') - ,handler: this.removeUser + menu.push('-'); + menu.push({ + text: _('user_group_user_remove'), + handler: this.removeUser }); } - return m; - } + return menu; + }, - ,updateUserGroup: function() { - var id = this.config.usergroup; - MODx.loadPage('security/usergroup/update', 'id=' + id); - } + updateUserGroup: function() { + const id = this.config.usergroup; + MODx.loadPage('security/usergroup/update', `id=${id}`); + }, - ,updateRole: function(btn,e) { - var r = this.menu.record; - r.usergroup = this.config.usergroup; - r.user = r.id; + updateRole: function(btn, e) { + const { record } = this.menu; + record.usergroup = this.config.usergroup; + record.user = record.id; - this.loadWindow(btn,e,{ - xtype: 'modx-window-user-group-role-update' - ,record: r - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - this.fireEvent('updateRole',r); - },scope:this} + this.loadWindow(btn, e, { + xtype: 'modx-window-user-group-role-update', + record: record, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('updateRole', response); + }, + scope: this + } } }); - } + }, - ,addUser: function(btn,e) { - var r = {usergroup:this.config.usergroup}; + addUser: function(btn, e) { + const record = { usergroup: this.config.usergroup }; if (!this.windows['modx-window-user-group-adduser']) { this.windows['modx-window-user-group-adduser'] = Ext.ComponentMgr.create({ - xtype: 'modx-window-user-group-adduser' - ,record: r - ,grid: this - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - this.fireEvent('addUser',r); - },scope:this} + xtype: 'modx-window-user-group-adduser', + record: record, + grid: this, + listeners: { + success: { + fn: function(response) { + this.refresh(); + this.fireEvent('addUser', response); + }, + scope: this + } } }); } - - this.windows['modx-window-user-group-adduser'].setValues(r); + this.windows['modx-window-user-group-adduser'].setValues(record); this.windows['modx-window-user-group-adduser'].show(e.target); - } + }, - ,removeUser: function(btn,e) { - var r = this.menu.record; + removeUser: function(btn, e) { + const { record } = this.menu; MODx.msg.confirm({ - title: _('warning') - ,text: _('user_group_user_remove_confirm') || _('confirm_remove') - ,url: this.config.url - ,params: { - action: 'Security/Group/User/Remove' - ,user: r.id - ,usergroup: this.config.usergroup - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + title: _('warning'), + text: _('user_group_user_remove_confirm') || _('confirm_remove'), + url: this.config.url, + params: { + action: 'Security/Group/User/Remove', + user: record.id, + usergroup: this.config.usergroup + }, + listeners: { + success: { fn: this.refresh, scope: this } } }); } }); -Ext.reg('modx-grid-user-group-users',MODx.grid.UserGroupUsers); +Ext.reg('modx-grid-user-group-users', MODx.grid.UserGroupUsers); /** * @class MODx.window.UpdateUserGroupRole @@ -493,32 +540,31 @@ Ext.reg('modx-grid-user-group-users',MODx.grid.UserGroupUsers); * @param {Object} config An object of options. * @xtype modx-window-user-group-role-update */ -MODx.window.UpdateUserGroupRole = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-window-user-group-role-update' - ,title: _('user_group_user_update_role') - ,url: MODx.config.connector_url - ,action: 'Security/Group/User/Update' - ,fields: [{ - xtype: 'hidden' - ,name: 'usergroup' - ,value: config.usergroup - },{ - xtype: 'hidden' - ,name: 'user' - ,value: config.user - },{ - xtype: 'modx-combo-usergrouprole' - ,id: 'modx-uugr-role' - ,name: 'role' - ,fieldLabel: _('role') +MODx.window.UpdateUserGroupRole = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-window-user-group-role-update', + title: _('user_group_user_update_role'), + url: MODx.config.connector_url, + action: 'Security/Group/User/Update', + fields: [{ + xtype: 'hidden', + name: 'usergroup', + value: config.usergroup + }, { + xtype: 'hidden', + name: 'user', + value: config.user + }, { + xtype: 'modx-combo-usergrouprole', + id: 'modx-uugr-role', + name: 'role', + fieldLabel: _('role') }] }); - MODx.window.UpdateUserGroupRole.superclass.constructor.call(this,config); + MODx.window.UpdateUserGroupRole.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.UpdateUserGroupRole,MODx.Window); -Ext.reg('modx-window-user-group-role-update',MODx.window.UpdateUserGroupRole); +Ext.extend(MODx.window.UpdateUserGroupRole, MODx.Window); +Ext.reg('modx-window-user-group-role-update', MODx.window.UpdateUserGroupRole); /** * @class MODx.window.AddUserToUserGroup @@ -526,49 +572,46 @@ Ext.reg('modx-window-user-group-role-update',MODx.window.UpdateUserGroupRole); * @param {Object} config An object of options. * @xtype modx-window-user-group-adduser */ -MODx.window.AddUserToUserGroup = function(config) { - config = config || {}; - this.ident = config.ident || 'auug'+Ext.id(); - Ext.applyIf(config,{ - title: _('user_group_user_add') - ,url: MODx.config.connector_url - ,action: 'Security/Group/User/Create' - ,fields: [{ - fieldLabel: _('user') - ,description: MODx.expandHelp ? '' : _('user_group_user_add_user_desc') - ,name: 'user' - ,hiddenName: 'user' - ,id: 'modx-auug-user' - ,xtype: 'modx-combo-user' - ,editable: true - ,typeAhead: true - ,allowBlank: false - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-user' - ,html: _('user_group_user_add_user_desc') - ,cls: 'desc-under' - },{ - fieldLabel: _('role') - ,description: MODx.expandHelp ? '' : _('user_group_user_add_role_desc') - ,name: 'role' - ,hiddenName: 'role' - ,id: 'modx-auug-role' - ,xtype: 'modx-combo-role' - ,allowBlank: false - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-'+this.ident+'-role' - ,html: _('user_group_user_add_role_desc') - ,cls: 'desc-under' - },{ - name: 'usergroup' - ,xtype: 'hidden' +MODx.window.AddUserToUserGroup = function(config = {}) { + this.ident = config.ident || `auug${Ext.id()}`; + Ext.applyIf(config, { + title: _('user_group_user_add'), + url: MODx.config.connector_url, + action: 'Security/Group/User/Create', + fields: [{ + fieldLabel: _('user'), + description: MODx.expandHelp ? '' : _('user_group_user_add_user_desc'), + name: 'user', + hiddenName: 'user', + xtype: 'modx-combo-user', + editable: true, + typeAhead: true, + allowBlank: false, + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_user_add_user_desc'), + cls: 'desc-under' + }, { + fieldLabel: _('role'), + description: MODx.expandHelp ? '' : _('user_group_user_add_role_desc'), + name: 'role', + hiddenName: 'role', + xtype: 'modx-combo-role', + allowBlank: false, + anchor: '100%' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('user_group_user_add_role_desc'), + cls: 'desc-under' + }, { + name: 'usergroup', + xtype: 'hidden' }] }); - MODx.window.AddUserToUserGroup.superclass.constructor.call(this,config); + MODx.window.AddUserToUserGroup.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.AddUserToUserGroup,MODx.Window); -Ext.reg('modx-window-user-group-adduser',MODx.window.AddUserToUserGroup); +Ext.extend(MODx.window.AddUserToUserGroup, MODx.Window); +Ext.reg('modx-window-user-group-adduser', MODx.window.AddUserToUserGroup); diff --git a/manager/assets/modext/widgets/source/modx.panel.source.js b/manager/assets/modext/widgets/source/modx.panel.source.js index cec222890e8..396151653e1 100644 --- a/manager/assets/modext/widgets/source/modx.panel.source.js +++ b/manager/assets/modext/widgets/source/modx.panel.source.js @@ -4,223 +4,252 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-source */ -MODx.panel.Source = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-source' - ,url: MODx.config.connector_url - ,baseParams: { +MODx.panel.Source = function(config = {}) { + let generalIntro = {}; + if (config.record.reserved) { + generalIntro = { + xtype: 'box', + cls: 'panel-desc', + html: _('source_reserved_general_desc') + }; + } + Ext.applyIf(config, { + id: 'modx-panel-source', + url: MODx.config.connector_url, + baseParams: { action: 'Source/Update' - } - ,defaults: { collapsible: false ,autoHeight: true } - ,cls: 'container form-with-labels' - ,items: [this.getPageHeader(config),{ - xtype: 'modx-tabs' - ,defaults: { - autoHeight: true - ,border: true - ,bodyCssClass: 'tab-panel-wrapper' - } - ,id: 'modx-source-tabs' - ,forceLayout: true - ,deferredRender: false - ,stateful: true - ,stateId: 'modx-source-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } - ,items: [{ - title: _('general_information') - ,defaults: { border: false, msgTarget: 'side' } - ,layout: 'form' - ,id: 'modx-source-form' - ,labelWidth: 150 - ,items: [{ - xtype: 'panel' - ,border: false - ,cls: 'main-wrapper' - ,layout: 'form' - ,labelAlign: 'top' - ,items: [{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .65 - ,cls: 'main-content' - ,items: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-source-id' - ,value: config.record.id - },{ - name: 'name' - ,id: 'modx-source-name' - ,xtype: 'textfield' - ,fieldLabel: _('name') - ,description: MODx.expandHelp ? '' : _('source_name_desc') - ,allowBlank: false - ,enableKeyEvents: true - ,anchor: '100%' - ,listeners: { - 'keyup': {scope:this,fn:function(f,e) { - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); - }} + }, + defaults: { + collapsible: false, + autoHeight: true + }, + cls: 'container form-with-labels', + items: [this.getPageHeader(config), { + xtype: 'modx-tabs', + defaults: { + autoHeight: true, + border: true, + bodyCssClass: 'tab-panel-wrapper' + }, + id: 'modx-source-tabs', + forceLayout: true, + deferredRender: false, + stateful: true, + stateId: 'modx-source-tabpanel', + stateEvents: ['tabchange'], + getState: function() { + return { + activeTab: this.items.indexOf(this.getActiveTab()) + }; + }, + items: [{ + title: _('general_information'), + layout: 'form', + id: 'modx-source-form', + items: [generalIntro, { + xtype: 'panel', + border: false, + cls: 'main-wrapper', + layout: 'form', + labelAlign: 'top', + items: [{ + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelAlign: 'top', + labelSeparator: '' + }, + items: [{ + columnWidth: 0.65, + defaults: { + anchor: '100%', + msgTarget: 'under' + }, + cls: 'main-content', + items: [{ + xtype: 'hidden', + name: 'id', + id: 'modx-source-id', + value: config.record.id + }, { + xtype: config.record.reserved ? 'statictextfield' : 'textfield', + name: 'name', + id: 'modx-source-name', + fieldLabel: _('name'), + description: MODx.expandHelp ? '' : _('source_name_desc'), + allowBlank: false, + enableKeyEvents: true, + listeners: { + keyup: { + scope: this, + fn: function(field, e) { + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(field.getValue())); + } + } } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-source-name' - ,html: _('source_name_desc') - ,cls: 'desc-under' - },{ - name: 'description' - ,id: 'modx-source-description' - ,xtype: 'textarea' - ,fieldLabel: _('description') - ,description: MODx.expandHelp ? '' : _('source_description_desc') - ,anchor: '100%' - ,grow: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-source-description' - ,html: _('source_description_desc') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('source_name_desc'), + cls: 'desc-under' + }, { + xtype: config.record.reserved ? 'statictextarea' : 'textarea', + name: 'description', + id: 'modx-source-description', + fieldLabel: _('description'), + description: MODx.expandHelp ? '' : _('source_description_desc'), + grow: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('source_description_desc'), + cls: 'desc-under' }] - },{ - columnWidth: .35 - ,cls: 'main-content' - ,items: [{ - name: 'class_key' - ,hiddenName: 'class_key' - ,id: 'modx-source-type' - ,xtype: 'modx-combo-source-type' - ,fieldLabel: _('source_type') - ,description: MODx.expandHelp ? '' : _('source_type_desc') - ,anchor: '100%' - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-source-type' - ,html: _('source_type_desc') - ,cls: 'desc-under' + }, { + columnWidth: 0.35, + defaults: { + anchor: '100%', + msgTarget: 'under' + }, + cls: 'main-content', + items: [{ + disabled: config.record.reserved, + xtype: 'modx-combo-source-type', + name: 'class_key', + hiddenName: 'class_key', + id: 'modx-source-type', + fieldLabel: _('source_type'), + description: MODx.expandHelp ? '' : _('source_type_desc') + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('source_type_desc'), + cls: 'desc-under' }] }] }] - },{ - html: '

    '+_('source_properties.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-source-properties' - ,preventRender: true - ,source: config.record.id - ,defaultProperties: config.defaultProperties - ,autoHeight: true - ,cls: 'main-wrapper' - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} + }, { + html: `

    ${_('source_properties.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-source-properties', + preventRender: true, + source: config.record.id, + defaultProperties: config.defaultProperties, + autoHeight: true, + cls: 'main-wrapper', + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this } } }] - },{ - title: _('access') - ,hideMode: 'offsets' - ,items: [{ - html: '

    '+_('source.access.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-source-access' - ,preventRender: true - ,source: config.record.id - ,autoHeight: true - ,cls: 'main-wrapper' - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'updateRole': {fn:this.markDirty,scope:this} - ,'addMember': {fn:this.markDirty,scope:this} + }, { + title: _('access'), + hideMode: 'offsets', + items: [{ + html: `

    ${_('source.access.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-source-access', + preventRender: true, + source: config.record.id, + autoHeight: true, + cls: 'main-wrapper', + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + updateRole: { fn: this.markDirty, scope: this }, + addMember: { fn: this.markDirty, scope: this } } }] }] - }] - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + }], + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.Source.superclass.constructor.call(this,config); + MODx.panel.Source.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Source,MODx.FormPanel,{ - initialized: false +Ext.extend(MODx.panel.Source, MODx.FormPanel, { + initialized: false, - ,setup: function() { - if (this.initialized) { return false; } + setup: function() { + if (this.initialized) { + return false; + } if (Ext.isEmpty(this.config.record.id)) { this.fireEvent('ready'); return false; } + this.getForm().setValues(this.config.record); + /* The component rendering is deferred since we are not using renderTo */ Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(this.config.record.name)); - var g,d; if (!Ext.isEmpty(this.config.record.properties)) { - g = Ext.getCmp('modx-grid-source-properties'); - if (g) { - g.defaultProperties = this.config.defaultProperties; - g.getStore().loadData(this.config.record.properties); + const propsGrid = Ext.getCmp('modx-grid-source-properties'); + if (propsGrid) { + propsGrid.defaultProperties = this.config.defaultProperties; + propsGrid.getStore().loadData(this.config.record.properties); } } if (!Ext.isEmpty(this.config.record.access)) { - d = this.config.record.access; - g = Ext.getCmp('modx-grid-source-access'); - if (g) { - d = Ext.decode(d); - if (!Ext.isEmpty(d)) { - g.defaultProperties = d; - g.getStore().loadData(d); + let { access } = this.config.record; + const accessGrid = Ext.getCmp('modx-grid-source-access'); + if (accessGrid) { + access = Ext.decode(access); + if (!Ext.isEmpty(access)) { + accessGrid.defaultProperties = access; + accessGrid.getStore().loadData(access); } } } - this.fireEvent('ready',this.config.record); + this.fireEvent('ready', this.config.record); MODx.fireEvent('ready'); this.initialized = true; - } + }, - ,beforeSubmit: function(o) { - var bp = {}; - var sp = Ext.getCmp('modx-grid-source-properties'); - if (sp) { - bp.properties = sp.encode(); + beforeSubmit: function(o) { + const + sourceData = {}, + propsGrid = Ext.getCmp('modx-grid-source-properties'), + accessGrid = Ext.getCmp('modx-grid-source-access') + ; + if (propsGrid) { + sourceData.properties = propsGrid.encode(); } - var ap = Ext.getCmp('modx-grid-source-access'); - if (ap) { - bp.access = ap.encode(); + if (accessGrid) { + sourceData.access = accessGrid.encode(); } - Ext.apply(o.form.baseParams,bp); - } + Ext.apply(o.form.baseParams, sourceData); + }, - ,success: function(o) { + success: function(o) { if (Ext.isEmpty(this.config.record) || Ext.isEmpty(this.config.record.id)) { - MODx.loadPage('source/update', 'id='+o.result.object.id); + MODx.loadPage('source/update', `id=${o.result.object.id}`); } else { + const + propsGrid = Ext.getCmp('modx-grid-source-properties'), + accessGrid = Ext.getCmp('modx-grid-source-access') + ; Ext.getCmp('modx-abtn-save').setDisabled(false); - var wg = Ext.getCmp('modx-grid-source-properties'); - if (wg) { wg.getStore().commitChanges(); } - var ag = Ext.getCmp('modx-grid-source-access'); - if (ag) { ag.getStore().commitChanges(); } + if (propsGrid) { + propsGrid.getStore().commitChanges(); + } + if (accessGrid) { + accessGrid.getStore().commitChanges(); + } } - } + }, - ,getPageHeader: function(config) { + getPageHeader: function(config) { return MODx.util.getHeaderBreadCrumbs('modx-source-header', [{ text: _('sources'), href: MODx.getPage('source') }]); } }); -Ext.reg('modx-panel-source',MODx.panel.Source); +Ext.reg('modx-panel-source', MODx.panel.Source); diff --git a/manager/assets/modext/widgets/source/modx.panel.sources.js b/manager/assets/modext/widgets/source/modx.panel.sources.js index 7cce9d4e9a3..11ccc6c4818 100644 --- a/manager/assets/modext/widgets/source/modx.panel.sources.js +++ b/manager/assets/modext/widgets/source/modx.panel.sources.js @@ -6,45 +6,47 @@ * @param {Object} config An object of configuration options * @xtype modx-panel-sources */ -MODx.panel.Sources = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-sources' - ,cls: 'container' - ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('sources') - ,id: 'modx-sources-header' - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('sources') - ,layout: 'form' - ,items: [{ - html: '

    '+_('sources.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-sources' - ,cls: 'main-wrapper' - ,preventRender: true +MODx.panel.Sources = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-sources', + cls: 'container', + bodyStyle: '', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [{ + html: _('sources'), + id: 'modx-sources-header', + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('sources'), + layout: 'form', + items: [{ + html: `

    ${_('sources.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-sources', + cls: 'main-wrapper', + preventRender: true }] - },{ - layout: 'form' - ,title: _('source_types') - ,items: [{ - html: '

    '+_('source_types.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-source-types' - ,cls: 'main-wrapper' - ,preventRender: true + }, { + layout: 'form', + title: _('source_types'), + items: [{ + html: `

    ${_('source_types.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-source-types', + cls: 'main-wrapper', + preventRender: true }] }])] }); - MODx.panel.Sources.superclass.constructor.call(this,config); + MODx.panel.Sources.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Sources,MODx.FormPanel); -Ext.reg('modx-panel-sources',MODx.panel.Sources); +Ext.extend(MODx.panel.Sources, MODx.FormPanel); +Ext.reg('modx-panel-sources', MODx.panel.Sources); /** * Loads a grid of Sources. @@ -56,168 +58,167 @@ Ext.reg('modx-panel-sources',MODx.panel.Sources); */ MODx.grid.Sources = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { + + Ext.applyIf(config, { + id: 'modx-grid-sources', + url: MODx.config.connector_url, + baseParams: { action: 'Source/GetList' - } - ,fields: [ + }, + fields: [ 'id', 'name', 'description', 'class_key', - 'cls' - ] - ,paging: true - ,autosave: true - ,save_action: 'Source/UpdateFromGrid' - ,remoteSort: true - ,sm: this.sm - ,columns: [this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 50 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name' - ,width: 150 - ,sortable: true - ,editor: { xtype: 'textfield' ,allowBlank: false } - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=source/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 300 - ,sortable: false - ,editor: { xtype: 'textarea' } - ,renderer: Ext.util.Format.htmlEncode - }] - ,tbar: [ - { - text: _('create') - ,handler: { - xtype: 'modx-window-source-create', - blankValues: true + 'creator' + ], + paging: true, + autosave: true, + save_action: 'Source/UpdateFromGrid', + remoteSort: true, + sm: this.sm, + stateful: true, + stateId: 'modx-grid-sources-state', + columns: [this.sm, { + header: _('id'), + dataIndex: 'id', + width: 50, + sortable: true + }, { + header: _('name'), + dataIndex: 'name', + width: 150, + sortable: true, + editor: { + xtype: 'textfield', + allowBlank: false, + blankText: _('source_err_ns_name'), + validationEvent: 'change', + validator: function(value) { + const grid = Ext.getCmp('modx-grid-sources'), + reserved = this.gridEditor.record.json.reserved.name + ; + if (grid.valueIsReserved(reserved, value)) { + const msg = _('source_err_name_reserved', { reservedName: value }); + Ext.Msg.alert(_('error'), msg); + return false; + } + return true; } - ,cls:'primary-button' - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=source/update&id=${record.data.id}`, + title: _('source_edit') + }) + : value + ; + }, + scope: this + } + }, { + header: _('description'), + dataIndex: 'description', + width: 300, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } + }, + this.getCreatorColumnConfig('source') + ], + tbar: [ + this.getCreateButton('source', { + xtype: 'modx-window-source-create', + blankValues: true + }), + this.getBulkActionsButton('source', 'Source/RemoveMultiple'), '->', this.getQueryFilterField(), this.getClearFiltersButton() - ] + ], + viewConfig: this.getViewConfig() }); - MODx.grid.Sources.superclass.constructor.call(this,config); -}; -Ext.extend(MODx.grid.Sources,MODx.grid.Grid,{ - getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; + MODx.grid.Sources.superclass.constructor.call(this, config); - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }); - } else { - if (p.indexOf('pupdate') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateSource - }); - } - if (p.indexOf('pduplicate') != -1) { - m.push({ - text: _('duplicate') - ,handler: this.duplicateSource - }); - } - if (p.indexOf('premove') != -1 && r.data.id != 1 && r.data.name != 'Filesystem') { - if (m.length > 0) m.push('-'); - m.push({ - text: _('delete') - ,handler: this.removeSource - }); - } - } - if (m.length > 0) { - this.addContextMenuItem(m); - } - } - - ,createSource: function() { - MODx.loadPage('system/source/create'); - } + this.gridMenuActions = ['edit', 'delete', 'duplicate']; - ,updateSource: function() { - MODx.loadPage('source/update', 'id='+this.menu.record.id); - } + this.setUserCanEdit(['source_save', 'source_edit']); + this.setUserCanCreate(['source_save']); + this.setUserCanDelete(['source_delete']); + this.setShowActionsMenu(); - ,duplicateSource: function(btn,e) { - MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Source/Duplicate' - ,id: this.menu.record.id + this.on({ + beforeedit: function(e) { + if (e.record.json.isProtected || !this.userCanEditRecord(e.record)) { + return false; } - ,listeners: { - 'success': {fn:this.refresh,scope:this} - } - }); - } + } + }); +}; +Ext.extend(MODx.grid.Sources, MODx.grid.Grid, { - ,removeSource: function() { - MODx.msg.confirm({ - title: _('delete') - ,text: _('source_remove_confirm') - ,url: this.config.url - ,params: { - action: 'Source/Remove' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} + getMenu: function() { + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateSource + }); + } + if (this.userCanCreate && this.userCanDuplicateRecord(record)) { + menu.push({ + text: _('duplicate'), + handler: this.duplicateSource + }); + } + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); } - }); - } + menu.push({ + text: _('delete'), + handler: this.confirm.createDelegate(this, ['Source/Remove', 'source_remove_confirm']) + }); + } + return menu; + }, - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; + updateSource: function() { + MODx.loadPage('source/update', `id=${this.menu.record.id}`); + }, - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('source_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Source/RemoveMultiple' - ,sources: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + duplicateSource: function(btn, e) { + MODx.Ajax.request({ + url: this.config.url, + params: { + action: 'Source/Duplicate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - return true; } - }); -Ext.reg('modx-grid-sources',MODx.grid.Sources); +Ext.reg('modx-grid-sources', MODx.grid.Sources); /** * Generates the create Source window. @@ -228,68 +229,69 @@ Ext.reg('modx-grid-sources',MODx.grid.Sources); * @xtype modx-window-source-create */ MODx.window.CreateSource = function(config = {}) { - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,autoHeight: true - ,action: 'Source/Create' - ,fields: [{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,anchor: '100%' - ,allowBlank: false - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,anchor: '100%' - ,grow: true - },{ - name: 'class_key' - ,hiddenName: 'class_key' - ,xtype: 'modx-combo-source-type' - ,fieldLabel: _('source_type') - ,anchor: '100%' - ,allowBlank: false - ,value: MODx.config.default_media_source_type - }] - ,keys: [] + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + autoHeight: true, + action: 'Source/Create', + formDefaults: { + anchor: '100%', + validationEvent: 'change', + validateOnBlur: false + }, + fields: [{ + xtype: 'textfield', + fieldLabel: _('name'), + name: 'name', + allowBlank: false + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + grow: true + }, { + name: 'class_key', + xtype: 'modx-combo-source-type', + fieldLabel: _('source_type'), + allowBlank: false, + value: MODx.config.default_media_source_type + }], + keys: [] }); - MODx.window.CreateSource.superclass.constructor.call(this,config); + MODx.window.CreateSource.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateSource,MODx.Window); -Ext.reg('modx-window-source-create',MODx.window.CreateSource); +Ext.extend(MODx.window.CreateSource, MODx.Window); +Ext.reg('modx-window-source-create', MODx.window.CreateSource); MODx.grid.SourceTypes = function(config = {}) { - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + url: MODx.config.connector_url, + baseParams: { action: 'Source/Type/GetList' - } - ,fields: [ + }, + fields: [ 'class', 'name', 'description' - ] - ,showActionsColumn: false - ,paging: true - ,remoteSort: true - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,width: 150 - ,sortable: true - ,renderer: Ext.util.Format.htmlEncode - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 300 - ,sortable: false - ,renderer: Ext.util.Format.htmlEncode + ], + showActionsColumn: false, + paging: true, + remoteSort: true, + columns: [{ + header: _('name'), + dataIndex: 'name', + width: 150, + sortable: true, + renderer: Ext.util.Format.htmlEncode + }, { + header: _('description'), + dataIndex: 'description', + width: 300, + sortable: false, + renderer: Ext.util.Format.htmlEncode }] }); - MODx.grid.SourceTypes.superclass.constructor.call(this,config); + MODx.grid.SourceTypes.superclass.constructor.call(this, config); }; -Ext.extend(MODx.grid.SourceTypes,MODx.grid.Grid); -Ext.reg('modx-grid-source-types',MODx.grid.SourceTypes); +Ext.extend(MODx.grid.SourceTypes, MODx.grid.Grid); +Ext.reg('modx-grid-source-types', MODx.grid.SourceTypes); diff --git a/manager/assets/modext/widgets/system/modx.grid.content.type.js b/manager/assets/modext/widgets/system/modx.grid.content.type.js index 16bda8b568a..289f1672ca9 100644 --- a/manager/assets/modext/widgets/system/modx.grid.content.type.js +++ b/manager/assets/modext/widgets/system/modx.grid.content.type.js @@ -4,35 +4,37 @@ * @param {Object} config An object of options. * @xtype modx-panel-content-type */ -MODx.panel.ContentType = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-content-type' - ,cls: 'container' - ,url: MODx.config.connector_url - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('content_types') - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('content_types') - ,layout: 'form' - ,itemId: 'form' - ,items: [{ - html: '

    '+_('content_type_desc')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-content-type' - ,itemId: 'grid' - ,cls:'main-wrapper' - ,preventRender: true +MODx.panel.ContentType = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-content-type', + cls: 'container', + url: MODx.config.connector_url, + defaults: { + collapsible: false, + autoHeight: true + }, + items: [{ + html: _('content_types'), + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('content_types'), + layout: 'form', + itemId: 'form', + items: [{ + html: `

    ${_('content_type_desc')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-content-type', + itemId: 'grid', + cls: 'main-wrapper', + preventRender: true }] }])] }); - MODx.panel.ContentType.superclass.constructor.call(this,config); + MODx.panel.ContentType.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.ContentType,MODx.FormPanel,{}); -Ext.reg('modx-panel-content-type',MODx.panel.ContentType); +Ext.extend(MODx.panel.ContentType, MODx.FormPanel, {}); +Ext.reg('modx-panel-content-type', MODx.panel.ContentType); /** * Loads a grid of content types @@ -42,115 +44,158 @@ Ext.reg('modx-panel-content-type',MODx.panel.ContentType); * @param {Object} config An object of options. * @xtype modx-grid-contenttype */ -MODx.grid.ContentType = function(config) { - config = config || {}; - var binaryColumn = new Ext.ux.grid.CheckColumn({ - header: _('binary') - ,dataIndex: 'binary' - ,width: 40 - ,sortable: true +MODx.grid.ContentType = function(config = {}) { + const binaryColumn = new Ext.ux.grid.CheckColumn({ + header: _('binary'), + dataIndex: 'binary', + width: 40, + sortable: true }); - - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + url: MODx.config.connector_url, + baseParams: { action: 'System/ContentType/GetList' + }, + autosave: true, + save_action: 'System/ContentType/UpdateFromGrid', + fields: [ + 'id', + 'name', + 'mime_type', + 'file_extensions', + 'icon', + 'headers', + 'binary', + 'description', + 'creator' + ], + paging: true, + remoteSort: true, + plugins: binaryColumn, + columns: [ + { + header: _('id'), + dataIndex: 'id', + width: 50, + sortable: true + }, { + header: _('name'), + dataIndex: 'name', + sortable: true, + editor: { xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } + }, { + header: _('description'), + dataIndex: 'description', + width: 200, + editor: { xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } + }, { + header: _('mime_type'), + dataIndex: 'mime_type', + width: 80, + sortable: true, + editor: { xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } + }, { + header: _('file_extensions'), + dataIndex: 'file_extensions', + sortable: true, + editor: { xtype: 'textfield' } + }, { + header: _('icon'), + dataIndex: 'icon', + sortable: false, + editor: { xtype: 'textfield' }, + renderer: this.renderIconField.createDelegate(this, [this], true) + }, + binaryColumn, + { + dataIndex: 'headers', + hidden: true + }, + this.getCreatorColumnConfig('content_types') + ], + tbar: [ + this.getCreateButton('content_types', 'newContentType') + ] + }); + MODx.grid.ContentType.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete']; + + // Note there are currently no action-specific permissions for Content Types + this.setUserCanEdit(['content_types']); + this.setUserCanCreate(['content_types']); + this.setUserCanDelete(['content_types']); + this.setShowActionsMenu(); + + this.on({ + beforerender: function(grid) { + grid.view = new Ext.grid.GridView(grid.getViewConfig(false)); + }, + beforeedit: function(e) { + const skipProtectionFieldList = ['file_extensions', 'icon']; + if ((e.record.json.isProtected && !skipProtectionFieldList.includes(e.field)) || !this.userCanEditRecord(e.record)) { + return false; + } } - ,autosave: true - ,save_action: 'System/ContentType/UpdateFromGrid' - ,fields: ['id','name','mime_type','file_extensions','icon','headers','binary','description'] - ,paging: true - ,remoteSort: true - ,plugins: binaryColumn - ,columns: [{ - header: _('id') - ,dataIndex: 'id' - ,width: 50 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name' - ,sortable: true - ,editor: { xtype: 'textfield' } - },{ - header: _('description') - ,dataIndex: 'description' - ,editor: { xtype: 'textfield' } - ,width: 200 - },{ - header: _('mime_type') - ,dataIndex: 'mime_type' - ,sortable: true - ,editor: { xtype: 'textfield' } - ,width: 80 - },{ - header: _('file_extensions') - ,dataIndex: 'file_extensions' - ,sortable: true - ,editor: { xtype: 'textfield' } - },{ - header: _('icon') - ,dataIndex: 'icon' - ,sortable: false - ,editor: { xtype: 'textfield' } - ,renderer: this.renderIconField.createDelegate(this,[this],true) - }, binaryColumn, { - dataIndex: 'headers' - ,hidden: true - }] - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,handler: this.newContentType - ,scope: this - }] }); - MODx.grid.ContentType.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.ContentType,MODx.grid.Grid,{ +Ext.extend(MODx.grid.ContentType, MODx.grid.Grid, { getMenu: function() { - var m = []; - m.push({ - text: _('edit') - ,handler: function(btn, e) { - var window = new MODx.window.CreateContentType({ - record: this.menu.record - ,title: _('edit') - ,action: 'System/ContentType/Update' - ,listeners: { - success: { - fn: this.refresh - ,scope: this - } - } - }); - window.setRecord(this.menu.record); - window.show(e.target); - } - ,scope: this - }); - m.push({ - text: _('delete') - ,handler: this.confirm.createDelegate(this,['System/ContentType/Remove',_('content_type_remove_confirm')]) - }); + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateContentType.createDelegate(this, [record], true) + }); + } + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + menu.push({ + text: _('delete'), + handler: this.confirm.createDelegate(this, ['System/ContentType/Remove', _('content_type_remove_confirm')]) + }); + } + return menu; + }, - return m; - } + newContentType: function(btn, e) { + const window = new MODx.window.CreateContentType({ grid: this }); + window.show(e.target); + }, - ,newContentType: function(btn, e) { - var window = new MODx.window.CreateContentType({ - listeners: { - success: { - fn: this.refresh - ,scope: this - } - } - }); + updateContentType: function(btn, e, record) { + const window = new MODx.window.UpdateContentType({ record: record, grid: this }); window.show(e.target); - } + }, - ,renderIconField: function (v, md, rec) { - return new Ext.XTemplate('   {icon:htmlEncode}').apply(rec.data); + renderIconField: function(value, metaData, record) { + return new Ext.XTemplate('   {icon:htmlEncode}').apply(record.data); } }); Ext.reg('modx-grid-content-type', MODx.grid.ContentType); @@ -163,134 +208,139 @@ Ext.reg('modx-grid-content-type', MODx.grid.ContentType); * @param {Object} config An object of options. * @xtype modx-window-content-type-create */ -MODx.window.CreateContentType = function(config) { - config = config || {}; - this.ident = config.ident || 'modx-cct'+Ext.id(); - Ext.applyIf(config,{ - title: _('create') - ,width: 600 - ,url: MODx.config.connector_url - ,action: 'System/ContentType/Create' - ,bwrapCssClass: 'x-window-with-tabs' - ,fields: [{ - xtype: 'modx-tabs' - ,items: [{ - title: _('content_type_main_tab') - ,layout: 'form' - ,items: [{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .6 - ,defaults: { - msgTarget: 'under' - } - ,items: [{ - xtype: 'hidden' - ,name: 'id' - },{ - fieldLabel: _('name') - ,name: 'name' - ,id: this.ident+'-name' - ,xtype: 'textfield' - ,anchor: '100%' - ,allowBlank: false - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: this.ident+'-name' - ,html: _('name_desc') - ,cls: 'desc-under' - },{ - fieldLabel: _('mime_type') - ,description: MODx.expandHelp ? '' : _('mime_type_desc') - ,name: 'mime_type' - ,id: this.ident+'-mime-type' - ,xtype: 'textfield' - ,anchor: '100%' - ,allowBlank: false - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: this.ident+'-mime-type' - ,html: _('mime_type_desc') - ,cls: 'desc-under' +MODx.window.CreateContentType = function(config = {}) { + Ext.applyIf(config, { + title: _('create'), + width: 600, + url: MODx.config.connector_url, + action: 'System/ContentType/Create', + bwrapCssClass: 'x-window-with-tabs', + fields: [{ + xtype: 'modx-tabs', + items: [{ + title: _('content_type_main_tab'), + layout: 'form', + items: [{ + xtype: 'modx-description', + id: 'modx-content-type-general-desc', + hidden: !config.isUpdate || !config.record?.json?.isProtected, + html: _('content_type_reserved_general_desc') + }, { + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelSeparator: '' + }, + items: [{ + columnWidth: 0.6, + defaults: { + msgTarget: 'under', + anchor: '100%', + validationEvent: 'change', + validateOnBlur: false + }, + items: [{ + xtype: 'hidden', + name: 'id' + }, { + fieldLabel: _('name'), + name: 'name', + xtype: 'textfield', + allowBlank: false, + readOnly: (config.isUpdate && config.record.json?.isProtected) || false + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('name_desc'), + cls: 'desc-under' + }, { + fieldLabel: _('mime_type'), + description: MODx.expandHelp ? '' : _('mime_type_desc'), + name: 'mime_type', + xtype: 'textfield', + allowBlank: false, + readOnly: (config.isUpdate && config.record.json?.isProtected) || false + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('mime_type_desc'), + cls: 'desc-under' }] - },{ - columnWidth: .4 - ,defaults: { - msgTarget: 'under' - } - ,items: [{ - fieldLabel: _('icon') - ,description: MODx.expandHelp ? '' : _('icon_desc') - ,name: 'icon' - ,id: this.ident+'-icon' - ,xtype: 'textfield' - ,anchor: '100%' - ,allowBlank: true - },{ - fieldLabel: _('file_extensions') - ,description: MODx.expandHelp ? '' : _('file_extensions_desc') - ,name: 'file_extensions' - ,id: this.ident+'-file-extensions' - ,xtype: 'textfield' - ,anchor: '100%' - ,allowBlank: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: this.ident+'-file-extensions' - ,html: _('file_extensions_desc') - ,cls: 'desc-under' + }, { + columnWidth: 0.4, + defaults: { + msgTarget: 'under', + anchor: '100%', + validationEvent: 'change', + validateOnBlur: false + }, + items: [{ + fieldLabel: _('icon'), + description: MODx.expandHelp ? '' : _('icon_desc'), + name: 'icon', + xtype: 'textfield' + }, { + fieldLabel: _('file_extensions'), + description: MODx.expandHelp ? '' : _('file_extensions_desc'), + name: 'file_extensions', + xtype: 'textfield' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('file_extensions_desc'), + cls: 'desc-under' }] }] - },{ - xtype: 'xcheckbox' - ,hideLabel: true - ,boxLabel: _('binary_desc') - ,name: 'binary' - ,hiddenName: 'binary' - ,id: this.ident+'-binary' - ,anchor: '100%' - },{ - fieldLabel: _('description') - ,name: 'description' - ,id: 'modx-'+this.ident+'-description' - ,xtype: 'textarea' - ,anchor: '100%' - ,grow: true - },{ - xtype: 'hidden' - ,name: 'headers' + }, { + xtype: 'xcheckbox', + hideLabel: true, + boxLabel: _('binary_desc'), + name: 'binary', + hiddenName: 'binary' + }, { + fieldLabel: _('description'), + labelSeparator: '', + name: 'description', + xtype: 'textarea', + anchor: '100%', + grow: true, + readOnly: (config.isUpdate && config.record.json?.isProtected) || false + }, { + xtype: 'hidden', + name: 'headers' }] - },{ - title: _('content_type_header_tab') - ,layout: 'anchor' - ,anchor: '100%' - ,items: [{ - xtype: 'modx-content-type-headers-grid' - ,id: 'headers' + }, { + title: _('content_type_header_tab'), + layout: 'anchor', + anchor: '100%', + items: [{ + xtype: 'modx-content-type-headers-grid', + id: 'headers' }] }] - }] - ,keys: [] + }], + keys: [] }); MODx.window.CreateContentType.superclass.constructor.call(this, config); - this.on('beforeSubmit', this.beforeSubmit, this); + this.on({ + beforeSubmit: this.beforeSubmit, + success: { + fn: function() { + this.grid.refresh(); + } + } + }); }; -Ext.extend(MODx.window.CreateContentType,MODx.Window, { +Ext.extend(MODx.window.CreateContentType, MODx.Window, { setRecord: function(record) { this.setValues(record); - - var grid = Ext.getCmp('headers') - ,store = grid.getStore(); - + const + grid = Ext.getCmp('headers'), + store = grid.getStore() + ; store.removeAll(); if (record.headers && record.headers.length > 0) { Ext.each(record.headers, function(header) { @@ -299,15 +349,16 @@ Ext.extend(MODx.window.CreateContentType,MODx.Window, { })); }, this); } - } + }, - ,beforeSubmit: function(o) { - var grid = Ext.getCmp('headers'), - store = grid.getStore() - ,records = store.getRange() - ,form = this.fp.getForm(); - - var results = []; + beforeSubmit: function(o) { + const + grid = Ext.getCmp('headers'), + store = grid.getStore(), + records = store.getRange(), + form = this.fp.getForm(), + results = [] + ; Ext.each(records, function(rec) { results.push(rec.get('header')); }, this); @@ -317,78 +368,84 @@ Ext.extend(MODx.window.CreateContentType,MODx.Window, { return true; } }); -Ext.reg('modx-window-content-type-create',MODx.window.CreateContentType); +Ext.reg('modx-window-content-type-create', MODx.window.CreateContentType); + +MODx.window.UpdateContentType = function(config = {}) { + Ext.applyIf(config, { + title: _('edit'), + action: 'System/ContentType/Update', + isUpdate: true + }); + MODx.window.UpdateContentType.superclass.constructor.call(this, config); + this.setRecord(config.record.data); +}; +Ext.extend(MODx.window.UpdateContentType, MODx.window.CreateContentType, {}); /** * * @param config * @constructor */ -MODx.ContentTypeHeaderGrid = function(config) { - config = config || {}; - +MODx.ContentTypeHeaderGrid = function(config = {}) { Ext.apply(config, { - fields: ['id', 'header'] - ,columns: [{ - header: _('content_type_header') - ,dataIndex: 'header' - }] - ,deferredRender: true - ,autoHeight: true - ,tbar: [{ - text: _('create') - ,cls: 'primary-button' - ,handler: this.add - ,scope: this - }] + fields: ['id', 'header'], + columns: [{ + header: _('content_type_header'), + dataIndex: 'header' + }], + deferredRender: true, + autoHeight: true, + tbar: [this.getCreateButton('content_types', 'add', true)] }); MODx.ContentTypeHeaderGrid.superclass.constructor.call(this, config); }; Ext.extend(MODx.ContentTypeHeaderGrid, MODx.grid.LocalGrid, { - add: function(btn,e) { - var window = this.loadWindow(); + add: function(btn, e) { + const window = this.loadWindow(); window.show(e.target); - } - - ,edit: function(btn, e) { - var record = this.menu.record - ,window = this.loadWindow(record); + }, + edit: function(btn, e) { + const + { record } = this.menu, + window = this.loadWindow(record) + ; window.setValues(record); window.show(e.target); - } - - ,remove: function() { - var record = this.menu.record - ,store = this.getStore() - ,idx = store.find('header', record['header']); + }, + remove: function() { + const + { record } = this.menu, + store = this.getStore(), + idx = store.find('header', record.header) + ; store.removeAt(idx); - } + }, - ,loadWindow: function(record) { + loadWindow: function(record) { return MODx.load({ - xtype: 'modx-window-content-header' - ,grid: this - ,record: record + xtype: 'modx-window-content-header', + grid: this, + record: record }); - } + }, - ,getMenu: function() { - var m = []; - m.push({ - text: _('edit') - ,handler: this.edit - ,scope: this + getMenu: function() { + const menu = []; + menu.push({ + text: _('edit'), + handler: this.edit, + scope: this }); - m.push({ - text: _('delete') - ,handler: this.remove - ,scope: this + menu.push({ + text: _('delete'), + handler: this.remove, + scope: this }); - return m; + return menu; } }); Ext.reg('modx-content-type-headers-grid', MODx.ContentTypeHeaderGrid); @@ -398,39 +455,38 @@ Ext.reg('modx-content-type-headers-grid', MODx.ContentTypeHeaderGrid); * @param config * @constructor */ -MODx.window.ContentHeader = function(config) { - config = config || {}; - +MODx.window.ContentHeader = function(config = {}) { Ext.apply(config, { - title: _('content_type_header_title') - ,fields: [{ - xtype: 'textfield' - ,name: 'header' - ,fieldLabel: _('content_type_header') - ,anchor: '100%' - ,allowBlank: false - }] - ,closeAction: 'close' + title: _('content_type_header_title'), + fields: [{ + xtype: 'textfield', + name: 'header', + fieldLabel: _('content_type_header'), + anchor: '100%', + allowBlank: false + }], + closeAction: 'close' }); MODx.window.ContentHeader.superclass.constructor.call(this, config); }; Ext.extend(MODx.window.ContentHeader, MODx.Window, { submit: function(close) { - var values = this.fp.getForm().getValues() - ,store = this.grid.getStore(); - + const + values = this.fp.getForm().getValues(), + store = this.grid.getStore() + ; if (this.config.record && this.config.record.header) { // Existing record, let's update it - var idx = store.find('header', this.config.record.header); + const idx = store.find('header', this.config.record.header); store.removeAt(idx); store.insert(idx, new Ext.data.Record({ - header: values['header'] + header: values.header })); } else { // New record let's add it to the store store.add(new Ext.data.Record({ - header: values['header'] + header: values.header })); } diff --git a/manager/assets/modext/widgets/system/modx.grid.context.js b/manager/assets/modext/widgets/system/modx.grid.context.js index 2532e5bfd78..eaf658e8315 100644 --- a/manager/assets/modext/widgets/system/modx.grid.context.js +++ b/manager/assets/modext/widgets/system/modx.grid.context.js @@ -6,34 +6,37 @@ * @param {Object} config An object of configuration options * @xtype modx-panel-contexts */ -MODx.panel.Contexts = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-contexts' - ,cls: 'container' - ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('contexts') - ,id: 'modx-contexts-header' - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('contexts') - ,layout: 'form' - ,items: [{ - html: '

    '+_('context_management_message')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-contexts' - ,cls:'main-wrapper' - ,preventRender: true +MODx.panel.Contexts = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-contexts', + cls: 'container', + bodyStyle: '', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [{ + html: _('contexts'), + id: 'modx-contexts-header', + xtype: 'modx-header' + }, MODx.getPageStructure([{ + title: _('contexts'), + layout: 'form', + items: [{ + html: `

    ${_('context_management_message')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-contexts', + urlFilters: ['search'], + cls: 'main-wrapper', + preventRender: true }] }])] }); - MODx.panel.Contexts.superclass.constructor.call(this,config); + MODx.panel.Contexts.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Contexts,MODx.FormPanel); -Ext.reg('modx-panel-contexts',MODx.panel.Contexts); +Ext.extend(MODx.panel.Contexts, MODx.FormPanel); +Ext.reg('modx-panel-contexts', MODx.panel.Contexts); /** * Loads a grid of modContexts. @@ -43,110 +46,175 @@ Ext.reg('modx-panel-contexts',MODx.panel.Contexts); * @param {Object} config An object of configuration properties * @xtype modx-grid-contexts */ -MODx.grid.Context = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('contexts') - ,id: 'modx-grid-context' - ,url: MODx.config.connector_url - ,baseParams: { +MODx.grid.Context = function(config = {}) { + Ext.applyIf(config, { + title: _('contexts'), + id: 'modx-grid-context', + url: MODx.config.connector_url, + baseParams: { action: 'Context/GetList' - } - ,fields: [ + }, + fields: [ 'key', 'name', 'description', - 'perm', - 'rank' - ] - ,paging: true - ,autosave: true - ,save_action: 'Context/UpdateFromGrid' - ,remoteSort: true - ,primaryKey: 'key' - ,columns: [{ - header: _('key') - ,dataIndex: 'key' - ,width: 100 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name' - ,width: 150 - ,sortable: true - ,editor: { xtype: 'textfield' } - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=context/update&key=' + record.data.key - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 575 - ,sortable: false - ,editor: { xtype: 'textarea' } - },{ - header: _('rank') - ,dataIndex: 'rank' - ,width: 100 - ,sortable: true - ,editor: { xtype: 'numberfield' } - }] - ,tbar: [ - { - text: _('create') - ,cls:'primary-button' - ,handler: this.create - ,scope: this + 'rank', + 'creator' + ], + paging: true, + autosave: true, + save_action: 'Context/UpdateFromGrid', + remoteSort: true, + primaryKey: 'key', + stateful: true, + stateId: 'modx-grid-context-state', + columns: [{ + header: _('key'), + dataIndex: 'key', + width: 100, + sortable: true + }, { + header: _('name'), + dataIndex: 'name', + width: 150, + sortable: true, + editor: { + xtype: 'textfield', + allowBlank: false, + blankText: _('context_err_ns_name'), + validationEvent: 'change', + validator: function(value) { + const + grid = Ext.getCmp('modx-grid-context'), + reserved = this.gridEditor.record.json.reserved.name + ; + if (grid.valueIsReserved(reserved, value)) { + const msg = _('context_err_name_reserved', { + reservedName: value + }); + Ext.Msg.alert(_('error'), msg); + return false; + } + return true; + } + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected, !(record.json.key === 'web')]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=context/update&key=${record.data.key}`, + title: _('context_edit') + }) + : value + ; + }, + scope: this + } + }, { + header: _('description'), + dataIndex: 'description', + width: 575, + sortable: false, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected, !(record.json.key === 'web')]); + return value; + }, + scope: this + } + }, + this.getCreatorColumnConfig('context'), + { + header: _('rank'), + dataIndex: 'rank', + width: 100, + align: 'center', + sortable: true, + editor: { + xtype: 'numberfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected, !(record.json.key === 'web')]); + return value; + }, + scope: this + } + }], + tbar: [ + this.getCreateButton('context'), '->', this.getQueryFilterField(), this.getClearFiltersButton() - ] + ], + viewConfig: this.getViewConfig(false) + }); + MODx.grid.Context.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate']; + + this.setUserCanEdit(['save_context', 'edit_context']); + this.setUserCanCreate(['save_context', 'new_context']); + this.setUserCanDelete(['delete_context']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEdit || e.record.json.key === 'mgr' || !this.userCanEditRecord(e.record)) { + return false; + } + } }); - MODx.grid.Context.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ +Ext.extend(MODx.grid.Context, MODx.grid.Grid, { + getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.perm; - var m = []; - if (p.indexOf('pnew') != -1) { - m.push({ - text: _('duplicate') - ,handler: this.duplicateContext - ,scope: this + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.userCanCreate && this.userCanDuplicateRecord(record)) { + menu.push({ + text: _('duplicate'), + handler: this.duplicateContext, + scope: this }); } - - if (p.indexOf('pedit') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateContext + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateContext }); } - - if (p.indexOf('premove') != -1) { - m.push('-'); - m.push({ - text: _('delete') - ,handler: this.remove - ,scope: this + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('delete'), + handler: this.remove, + scope: this }); } - return m; - } + return menu; + }, - ,create: function(btn, e) { + create: function(btn, e) { if (this.createWindow) { this.createWindow.destroy(); } this.createWindow = MODx.load({ xtype: 'modx-window-context-create', - closeAction:'close', + closeAction: 'close', listeners: { - 'success': { + success: { fn: function() { this.afterAction(); }, @@ -155,34 +223,39 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ } }); this.createWindow.show(e.target); - } + }, - ,updateContext: function(itm,e) { - MODx.loadPage('context/update', 'key='+this.menu.record.key); - } + updateContext: function(itm, e) { + MODx.loadPage('context/update', `key=${this.menu.record.key}`); + }, - ,duplicateContext: function() { - var r = { - key: this.menu.record.key - ,newkey: '' - }; - var w = MODx.load({ - xtype: 'modx-window-context-duplicate' - ,record: r - ,listeners: { - 'success': {fn:function() { - this.refresh(); - var tree = Ext.getCmp('modx-resource-tree'); - if (tree) { - tree.refresh(); + duplicateContext: function() { + const + record = { + key: this.menu.record.key, + newkey: '' + }, + window = MODx.load({ + xtype: 'modx-window-context-duplicate', + record: record, + listeners: { + success: { + fn: function() { + this.refresh(); + const tree = Ext.getCmp('modx-resource-tree'); + if (tree) { + tree.refresh(); + } + }, + scope: this } - },scope:this} - } - }); - w.show(); - } + } + }) + ; + window.show(); + }, - ,remove: function(btn, e) { + remove: function(btn, e) { MODx.msg.confirm({ title: _('warning'), text: _('context_remove_confirm'), @@ -192,7 +265,7 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ key: this.menu.record.key }, listeners: { - 'success': { + success: { fn: function() { this.afterAction(); }, @@ -200,10 +273,10 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ } } }); - } + }, - ,afterAction: function() { - var cmp = Ext.getCmp('modx-resource-tree'); + afterAction: function() { + const cmp = Ext.getCmp('modx-resource-tree'); if (cmp) { cmp.refresh(); } @@ -211,7 +284,7 @@ Ext.extend(MODx.grid.Context,MODx.grid.Grid,{ this.refresh(); } }); -Ext.reg('modx-grid-contexts',MODx.grid.Context); +Ext.reg('modx-grid-contexts', MODx.grid.Context); /** * Generates the create context window. @@ -221,40 +294,41 @@ Ext.reg('modx-grid-contexts',MODx.grid.Context); * @param {Object} config An object of options. * @xtype modx-window-context-create */ -MODx.window.CreateContext = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Context/Create' - ,fields: [{ - xtype: 'textfield' - ,fieldLabel: _('context_key') - ,name: 'key' - ,anchor: '100%' - ,maxLength: 100 - },{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,anchor: '100%' - ,maxLength: 100 - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,anchor: '100%' - ,grow: true - },{ - xtype: 'numberfield' - ,fieldLabel: _('rank') - ,name: 'rank' - ,allowBlank: true - ,anchor: '100%' - }] - ,keys: [] +MODx.window.CreateContext = function(config = {}) { + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + action: 'Context/Create', + formDefaults: { + anchor: '100%' + }, + fields: [{ + xtype: 'textfield', + fieldLabel: _('context_key'), + name: 'key', + maxLength: 100, + allowBlank: false, + blankText: _('context_err_ns_key') + }, { + xtype: 'textfield', + fieldLabel: _('name'), + name: 'name', + maxLength: 100, + allowBlank: false, + blankText: _('context_err_ns_name') + }, { + xtype: 'textarea', + fieldLabel: _('description'), + name: 'description', + grow: true + }, { + xtype: 'numberfield', + fieldLabel: _('rank'), + name: 'rank' + }], + keys: [] }); - MODx.window.CreateContext.superclass.constructor.call(this,config); + MODx.window.CreateContext.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.CreateContext,MODx.Window); -Ext.reg('modx-window-context-create',MODx.window.CreateContext); +Ext.extend(MODx.window.CreateContext, MODx.Window); +Ext.reg('modx-window-context-create', MODx.window.CreateContext); 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 aaea2ab9710..eec0924a04f 100644 --- a/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js +++ b/manager/assets/modext/widgets/system/modx.grid.dashboard.widgets.js @@ -11,14 +11,14 @@ MODx.grid.DashboardWidgets = function(config = {}) { '

    {description_trans}

    ' ) }); - this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-dashboard-widgets', + url: MODx.config.connector_url, + baseParams: { action: 'System/Dashboard/Widget/GetList' - } - ,fields: [ + }, + fields: [ 'id', 'name', 'name_trans', @@ -29,135 +29,124 @@ MODx.grid.DashboardWidgets = function(config = {}) { 'namespace', 'lexicon', 'size', - 'cls' - ] - ,paging: true - ,remoteSort: true - ,sm: this.sm - ,plugins: [this.exp] - ,columns: [this.exp,this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 50 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name_trans' - ,width: 150 - ,sortable: true - ,editable: false - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=system/dashboards/widget/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('widget_type') - ,dataIndex: 'type' - ,width: 80 - ,sortable: true - },{ - header: _('widget_namespace') - ,dataIndex: 'namespace' - ,width: 120 - ,sortable: true - }] - ,tbar: [ + 'creator' + ], + paging: true, + remoteSort: true, + sm: this.sm, + plugins: [this.exp], + columns: [ + this.exp, + this.sm, { - text: _('create') - ,cls:'primary-button' - ,handler: this.createDashboard - ,scope: this - },{ - text: _('bulk_actions') - ,menu: [{ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this - }] + header: _('id'), + dataIndex: 'id', + width: 50, + sortable: true + }, { + header: _('name'), + dataIndex: 'name_trans', + width: 150, + sortable: true, + editable: false, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=system/dashboards/widget/update&id=${record.data.id}`, + title: _('dashboard_edit') + }) + : value + ; + }, + scope: this + } + }, { + header: _('widget_type'), + dataIndex: 'type', + width: 80, + sortable: true + }, { + header: _('widget_namespace'), + dataIndex: 'namespace', + width: 120, + sortable: true, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } }, + this.getCreatorColumnConfig('dashboard') + ], + tbar: [ + this.getCreateButton('dashboard', 'createDashboard'), + this.getBulkActionsButton('widget', 'System/Dashboard/Widget/RemoveMultiple'), '->', this.getQueryFilterField(`filter-query-dashboardWidgets:${queryValue}`), this.getClearFiltersButton('filter-query-dashboardWidgets') ] }); - MODx.grid.DashboardWidgets.superclass.constructor.call(this,config); + MODx.grid.DashboardWidgets.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete']; + + // Note there are currently no action-specific permissions for Dashboards + this.setUserCanEdit(['dashboards']); + this.setUserCanCreate(['dashboards']); + this.setUserCanDelete(['dashboards']); + this.setShowActionsMenu(); + + this.on({ + beforerender: function(grid) { + grid.view = new Ext.grid.GridView(grid.getViewConfig()); + } + }); }; -Ext.extend(MODx.grid.DashboardWidgets,MODx.grid.Grid,{ +Ext.extend(MODx.grid.DashboardWidgets, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this + const + record = this.getSelectionModel().getSelected(), + menu = [], + canDelete = this.userCanDelete && this.userCanDeleteRecord(record) + ; + if (this.getSelectionModel().getCount() > 1 && canDelete) { + menu.push({ + text: _('selected_remove'), + handler: this.removeSelected, + scope: this }); } else { - if (p.indexOf('pupdate') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateWidget + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateWidget }); } - if (p.indexOf('premove') != -1) { - if (m.length > 0) m.push('-'); - m.push({ - text: _('delete') - ,handler: this.removeWidget + if (canDelete) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('delete'), + handler: this.confirm.createDelegate(this, ['System/Dashboard/Widget/Remove', 'widget_remove_confirm']) }); } } - if (m.length > 0) { - this.addContextMenuItem(m); - } - } + return menu; + }, - ,createDashboard: function() { + createDashboard: function() { MODx.loadPage('system/dashboards/widget/create'); - } - - ,updateWidget: function() { - MODx.loadPage('system/dashboards/widget/update', 'id='+this.menu.record.id); - } + }, - ,removeWidget: function() { - MODx.msg.confirm({ - title: _('delete') - ,text: _('widget_remove_confirm') - ,url: this.config.url - ,params: { - action: 'System/Dashboard/Widget/Remove' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} - } - }); - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('widget_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'System/Dashboard/Widget/RemoveMultiple' - ,widgets: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; + updateWidget: function() { + MODx.loadPage('system/dashboards/widget/update', `id=${this.menu.record.id}`); } }); -Ext.reg('modx-grid-dashboard-widgets',MODx.grid.DashboardWidgets); +Ext.reg('modx-grid-dashboard-widgets', MODx.grid.DashboardWidgets); diff --git a/manager/assets/modext/widgets/system/modx.panel.context.js b/manager/assets/modext/widgets/system/modx.panel.context.js index 9a4270cab05..a838c59aaaf 100644 --- a/manager/assets/modext/widgets/system/modx.panel.context.js +++ b/manager/assets/modext/widgets/system/modx.panel.context.js @@ -4,152 +4,181 @@ * @param {Object} config An object of config properties * @xtype modx-panel-context */ -MODx.panel.Context = function(config) { - config = config || {}; - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { +MODx.panel.Context = function(config = {}) { + Ext.applyIf(config, { + url: MODx.config.connector_url, + baseParams: { action: 'Context/Get' - } - ,id: 'modx-panel-context' - ,cls: 'container' - ,class_key: 'modContext' - ,plugin: '' - ,bodyStyle: '' - ,items: [this.getPageHeader(config), MODx.getPageStructure([{ - title: _('general_information') - ,autoHeight: true - ,layout: 'form' - ,defaults: { border: false ,msgTarget: 'side' } - ,items:[{ - xtype: 'panel' - ,border: false - ,cls:'main-wrapper' - ,layout: 'form' - ,items: [{ - xtype: 'statictextfield' - ,fieldLabel: _('key') - ,name: 'key' - ,width: 300 - ,maxLength: 100 - ,enableKeyEvents: true - ,allowBlank: true - ,value: config.context - ,submitValue: true - },{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,name: 'name' - ,width: 300 - ,maxLength: 191 - },{ - xtype: 'textarea' - ,fieldLabel: _('description') - ,name: 'description' - ,width: 300 - ,grow: true - },{ - xtype: 'numberfield' - ,fieldLabel: _('rank') - ,name: 'rank' - ,allowBlank: true - ,width: 300 - },{ - html: MODx.onContextFormRender - ,border: false + }, + id: 'modx-panel-context', + cls: 'container', + class_key: 'modContext', + plugin: '', + bodyStyle: '', + items: [this.getPageHeader(config), MODx.getPageStructure([{ + title: _('general_information'), + autoHeight: true, + layout: 'form', + defaults: { border: false, msgTarget: 'side' }, + items: [{ + xtype: 'modx-description', + id: 'modx-context-general-desc', + hidden: true, + html: '' + }, { + xtype: 'panel', + border: false, + cls: 'main-wrapper', + layout: 'form', + items: [{ + xtype: 'statictextfield', + fieldLabel: _('key'), + name: 'key', + width: 300, + maxLength: 100, + enableKeyEvents: true, + value: config.context, + submitValue: true + }, { + xtype: config.context === 'mgr' ? 'statictextfield' : 'textfield', + fieldLabel: _('name'), + name: 'name', + width: 300, + maxLength: 191 + }, { + xtype: config.context === 'mgr' ? 'statictextarea' : 'textarea', + fieldLabel: _('description'), + name: 'description', + width: 300, + grow: true + }, { + xtype: config.context === 'mgr' ? 'statictextfield' : 'numberfield', + fieldLabel: _('rank'), + name: 'rank', + width: 300 + }, { + html: MODx.onContextFormRender, + border: false }] }] - },{ - title: _('context_settings') - ,autoHeight: true - ,layout: 'form' - ,items: [{ - html: '

    '+_('context_settings_desc')+'

    ' - ,id: 'modx-context-settings-desc' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-context-settings' - ,cls:'main-wrapper' - ,title: '' - ,preventRender: true - ,context_key: config.context - ,listeners: { - 'afteredit': {fn:function() { this.markDirty(); },scope:this} + }, { + title: _('context_settings'), + autoHeight: true, + layout: 'form', + items: [{ + html: `

    ${_('context_settings_desc')}

    `, + id: 'modx-context-settings-desc', + xtype: 'modx-description' + }, { + xtype: 'modx-grid-context-settings', + cls: 'main-wrapper', + title: '', + preventRender: true, + context_key: config.context, + listeners: { + afteredit: { + fn: function() { + this.markDirty(); + }, + scope: this + } } }] - },{ - title: _('access_permissions') - ,autoHeight: true - ,items:[{ - xtype: 'modx-grid-access-context' - ,cls:'main-wrapper' - ,title: '' - ,preventRender: true - ,context_key: config.context - ,listeners: { - 'afteredit': {fn:function() { this.markDirty(); },scope:this} + }, { + title: _('access_permissions'), + autoHeight: true, + items: [{ + xtype: 'modx-grid-access-context', + cls: 'main-wrapper', + title: '', + preventRender: true, + context_key: config.context, + listeners: { + afteredit: { + fn: function() { + this.markDirty(); + }, + scope: this + } } }] - }],{ + }], { id: 'modx-context-tabs' - })] - ,useLoadingMask: true - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + })], + useLoadingMask: true, + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.Context.superclass.constructor.call(this,config); + MODx.panel.Context.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Context,MODx.FormPanel,{ - initialized: false +Ext.extend(MODx.panel.Context, MODx.FormPanel, { + initialized: false, - ,setup: function() { + setup: function() { if (this.initialized || (this.config.context === '' || this.config.context === 0)) { this.fireEvent('ready'); return false; } MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'Context/Get' - ,key: this.config.context - } - ,listeners: { - 'success': {fn:function(r) { - this.getForm().setValues(r.object); - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(r.object.key)); - this.fireEvent('ready'); - MODx.fireEvent('ready'); - this.initialized = true; - },scope:this} + url: this.config.url, + params: { + action: 'Context/Get', + key: this.config.context + }, + listeners: { + success: { + fn: function(response) { + const record = response.object; + this.config.record = record; + if (record.isProtected && record.key !== 'web') { + const descriptionCmp = Ext.getCmp('modx-context-general-desc'); + descriptionCmp.update(_('context_reserved_general_desc')); + descriptionCmp.show(); + } + this.getForm().setValues(record); + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(record.key)); + this.fireEvent('ready'); + MODx.fireEvent('ready'); + this.initialized = true; + }, + scope: this + } } }); - } - - ,beforeSubmit: function(o) { - var r = {}; - - var g = Ext.getCmp('modx-grid-context-settings'); - if (g) { r.settings = g.encodeModified(); } + }, - Ext.apply(o.form.baseParams,r); - } - - ,success: function(o) { - var g = Ext.getCmp('modx-grid-context-settings'); - if (g) { g.getStore().commitChanges(); } + beforeSubmit: function(o) { + const + data = {}, + settingsCmp = Ext.getCmp('modx-grid-context-settings') + ; + if (settingsCmp) { + data.settings = settingsCmp.encodeModified(); + } + Ext.apply(o.form.baseParams, data); + }, - var t = parent.Ext.getCmp('modx-resource-tree'); - if (t) { t.refresh(); } - } + success: function(o) { + const + settingsCmp = Ext.getCmp('modx-grid-context-settings'), + tree = Ext.getCmp('modx-resource-tree') + ; + if (settingsCmp) { + settingsCmp.getStore().commitChanges(); + } + if (tree) { + tree.refresh(); + } + }, - ,getPageHeader: function(config) { + getPageHeader: function(config) { return MODx.util.getHeaderBreadCrumbs('modx-context-name', [{ text: _('contexts'), href: MODx.getPage('context') }]); } }); -Ext.reg('modx-panel-context',MODx.panel.Context); +Ext.reg('modx-panel-context', MODx.panel.Context); diff --git a/manager/assets/modext/widgets/system/modx.panel.dashboard.js b/manager/assets/modext/widgets/system/modx.panel.dashboard.js index 63e60af2815..bc0199d2a6f 100644 --- a/manager/assets/modext/widgets/system/modx.panel.dashboard.js +++ b/manager/assets/modext/widgets/system/modx.panel.dashboard.js @@ -4,148 +4,168 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-dashboard */ -MODx.panel.Dashboard = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-dashboard' - ,url: MODx.config.connector_url - ,baseParams: { +MODx.panel.Dashboard = function(config = {}) { + let generalIntro = {}; + if (config.record.reserved) { + generalIntro = { + xtype: 'box', + cls: 'panel-desc', + html: _('dashboard_reserved_general_desc') + }; + } + Ext.applyIf(config, { + id: 'modx-panel-dashboard', + url: MODx.config.connector_url, + baseParams: { action: 'System/Dashboard/Update' - } - ,cls: 'container' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [this.getPageHeader(config),{ - xtype: 'modx-tabs' - ,defaults: { - autoHeight: true - ,border: false - } - ,id: 'modx-dashboard-tabs' - ,forceLayout: true - ,deferredRender: false - ,stateful: true - ,stateId: 'modx-dashboard-tabpanel' - ,stateEvents: ['tabchange'] - ,getState:function() { - return {activeTab:this.items.indexOf(this.getActiveTab())}; - } + }, + cls: 'container', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [this.getPageHeader(config), { + xtype: 'modx-tabs', + defaults: { + autoHeight: true, + border: false + }, + id: 'modx-dashboard-tabs', + forceLayout: true, + deferredRender: false, + stateful: true, + stateId: 'modx-dashboard-tabpanel', + stateEvents: ['tabchange'], + getState: function() { + return { activeTab: this.items.indexOf(this.getActiveTab()) }; + }, // todo: the layout is inconsistent with other panels, refactor the structure - ,items: [{ - title: _('general_information') - ,cls: 'form-with-labels' - ,defaults: { border: false, cls: 'main-wrapper' } - ,layout: 'form' - ,id: 'modx-dashboard-form' - ,labelAlign: 'top' - ,items: [{ - xtype: 'hidden' - ,name: 'id' - ,id: 'modx-dashboard-id' - ,value: config.record.id - },{ - layout: 'column' - ,border: false - ,defaults: { - layout: 'form' - ,labelAlign: 'top' - ,anchor: '100%' - ,border: false - } - ,items: [{ - columnWidth: .7 - ,cls: 'main-content' - ,items: [{ - name: 'name' - ,id: 'modx-dashboard-name' - ,xtype: 'textfield' - ,fieldLabel: _('name') - ,description: MODx.expandHelp ? '' : _('dashboard_desc_name') - ,allowBlank: false - ,enableKeyEvents: true - ,anchor: '100%' - ,listeners: { - 'keyup': {scope:this,fn:function(f,e) { - Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); - }} + items: [{ + title: _('general_information'), + cls: 'form-with-labels', + defaults: { + border: false, + cls: 'main-wrapper' + }, + layout: 'form', + id: 'modx-dashboard-form', + labelAlign: 'top', + items: [generalIntro, { + xtype: 'hidden', + name: 'id', + id: 'modx-dashboard-id', + value: config.record.id + }, { + layout: 'column', + border: false, + defaults: { + layout: 'form', + labelAlign: 'top', + labelSeparator: '', + border: false + }, + items: [{ + columnWidth: 0.7, + cls: 'main-content', + defaults: { + msgTarget: 'under', + anchor: '100%' + }, + items: [{ + xtype: config.record.reserved ? 'statictextfield' : 'textfield', + name: 'name', + fieldLabel: _('name'), + description: MODx.expandHelp ? '' : _('dashboard_name_desc'), + allowBlank: false, + enableKeyEvents: true, + listeners: { + keyup: { + fn: function(f, e) { + Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(f.getValue())); + }, + scope: this + } } - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-dashboard-name' - ,html: _('dashboard_desc_name') - ,cls: 'desc-under' - },{ - name: 'description' - ,id: 'modx-dashboard-description' - ,xtype: 'textarea' - ,fieldLabel: _('description') - ,description: MODx.expandHelp ? '' : _('dashboard_desc_description') - ,anchor: '100%' - ,grow: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-dashboard-description' - ,html: _('dashboard_desc_description') - ,cls: 'desc-under' + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('dashboard_name_desc'), + cls: 'desc-under' + }, { + name: 'description', + xtype: 'textarea', + /** + * @todo - Change this xtype to the following once Lexicon-based name/desc is implemented for core dashboard + * xtype: config.record.reserved ? 'statictextfield' : 'textfield', + */ + fieldLabel: _('description'), + description: MODx.expandHelp ? '' : _('dashboard_description_desc'), + grow: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('dashboard_description_desc'), + cls: 'desc-under' }] - },{ - columnWidth: .3 - ,cls: 'main-content' - ,items: [{ - name: 'hide_trees' - ,id: 'modx-dashboard-hide-trees' - ,xtype: 'xcheckbox' - ,boxLabel: _('dashboard_hide_trees') - ,description: MODx.expandHelp ? '' : _('dashboard_desc_hide_trees') - ,inputValue: 1 - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-dashboard-hide-trees' - ,html: _('dashboard_desc_hide_trees') - ,cls: 'desc-under' - },{ - name: 'customizable' - ,id: 'modx-dashboard-customizable' - ,xtype: 'xcheckbox' - ,boxLabel: _('dashboard_customizable') - ,description: MODx.expandHelp ? '' : _('dashboard_desc_customizable') - ,inputValue: 1 - ,checked: true - },{ - xtype: MODx.expandHelp ? 'label' : 'hidden' - ,forId: 'modx-dashboard-customizable' - ,html: _('dashboard_desc_customizable') - ,cls: 'desc-under' + }, { + columnWidth: 0.3, + cls: 'main-content', + items: [{ + name: 'hide_trees', + xtype: 'xcheckbox', + ctCls: 'display-switch', + boxLabel: _('dashboard_hide_trees'), + description: MODx.expandHelp ? '' : _('dashboard_hide_trees_desc'), + inputValue: 1 + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('dashboard_hide_trees_desc'), + cls: 'desc-under' + }, { + name: 'customizable', + xtype: 'xcheckbox', + ctCls: 'display-switch', + boxLabel: _('dashboard_customizable'), + description: MODx.expandHelp ? '' : _('dashboard_customizable_desc'), + inputValue: 1, + checked: true + }, { + xtype: 'box', + hidden: !MODx.expandHelp, + html: _('dashboard_customizable_desc'), + cls: 'desc-under' }] }] - },{ - html: '

    '+_('dashboard_widgets.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-dashboard-widget-placements' - ,preventRender: true - ,dashboard: config.record.id - ,autoHeight: true - ,anchor: '100%' - ,listeners: { - 'afterRemoveRow': {fn:this.markDirty,scope:this} - ,'updateRole': {fn:this.markDirty,scope:this} - ,'addMember': {fn:this.markDirty,scope:this} + }, { + html: `

    ${_('dashboard_widgets.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-dashboard-widget-placements', + preventRender: true, + dashboard: config.record.id, + autoHeight: true, + anchor: '100%', + listeners: { + afterRemoveRow: { fn: this.markDirty, scope: this }, + updateRole: { fn: this.markDirty, scope: this }, + addMember: { fn: this.markDirty, scope: this } } }] }] - }] - ,listeners: { - 'setup': {fn:this.setup,scope:this} - ,'success': {fn:this.success,scope:this} - ,'beforeSubmit': {fn:this.beforeSubmit,scope:this} + }], + listeners: { + setup: { fn: this.setup, scope: this }, + success: { fn: this.success, scope: this }, + beforeSubmit: { fn: this.beforeSubmit, scope: this } } }); - MODx.panel.Dashboard.superclass.constructor.call(this,config); + MODx.panel.Dashboard.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Dashboard,MODx.FormPanel,{ - initialized: false +Ext.extend(MODx.panel.Dashboard, MODx.FormPanel, { + initialized: false, - ,setup: function() { + setup: function() { if (this.initialized) { return false; } if (Ext.isEmpty(this.config.record.id)) { this.fireEvent('ready'); @@ -154,45 +174,48 @@ Ext.extend(MODx.panel.Dashboard,MODx.FormPanel,{ this.getForm().setValues(this.config.record); Ext.getCmp('modx-header-breadcrumbs').updateHeader(Ext.util.Format.htmlEncode(this.config.record.name)); - var d = this.config.record.widgets; - var g = Ext.getCmp('modx-grid-dashboard-widget-placements'); - if (d && g) { - g.getStore().loadData(d); + const + { widgets } = this.config.record, + placementsGrid = Ext.getCmp('modx-grid-dashboard-widget-placements') + ; + if (widgets && placementsGrid) { + placementsGrid.getStore().loadData(widgets); } - this.fireEvent('ready',this.config.record); + this.fireEvent('ready', this.config.record); MODx.fireEvent('ready'); this.initialized = true; - } + }, - ,beforeSubmit: function(o) { - var bp = {}; - var wg = Ext.getCmp('modx-grid-dashboard-widget-placements'); - if (wg) { - bp['widgets'] = wg.encode(); + beforeSubmit: function(o) { + const + params = {}, + placementsGrid = Ext.getCmp('modx-grid-dashboard-widget-placements') + ; + if (placementsGrid) { + params.widgets = placementsGrid.encode(); } - Ext.apply(o.form.baseParams,bp); - } + Ext.apply(o.form.baseParams, params); + }, - ,success: function(o) { + success: function(o) { if (Ext.isEmpty(this.config.record) || Ext.isEmpty(this.config.record.id)) { - MODx.loadPage('system/dashboards/update', 'id='+o.result.object.id); + MODx.loadPage('system/dashboards/update', `id=${o.result.object.id}`); } else { Ext.getCmp('modx-abtn-save').setDisabled(false); - var wg = Ext.getCmp('modx-grid-dashboard-widget-placements'); + const wg = Ext.getCmp('modx-grid-dashboard-widget-placements'); if (wg) { wg.getStore().commitChanges(); } - } - } + }, - ,getPageHeader: function(config) { + getPageHeader: function(config) { return MODx.util.getHeaderBreadCrumbs('modx-dashboard-header', [{ text: _('dashboards'), href: MODx.getPage('system/dashboards') }]); } }); -Ext.reg('modx-panel-dashboard',MODx.panel.Dashboard); +Ext.reg('modx-panel-dashboard', MODx.panel.Dashboard); /** * @class MODx.grid.DashboardWidgetPlacements @@ -200,109 +223,139 @@ Ext.reg('modx-panel-dashboard',MODx.panel.Dashboard); * @param {Object} config An object of configuration properties * @xtype modx-grid-dashboard-widget-placements */ -MODx.grid.DashboardWidgetPlacements = function(config) { - config = config || {}; +MODx.grid.DashboardWidgetPlacements = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {description_trans}

    ' ) }); - Ext.applyIf(config,{ - id: 'modx-grid-dashboard-widget-placements' - ,url: MODx.config.connector_url - ,action: 'system/dashboard/widget/placement/getList' - ,fields: ['dashboard','widget','rank','name','name_trans','description','description_trans'] - ,autoHeight: true - ,primaryKey: 'widget' - ,cls: 'modx-grid modx-grid-draggable' - ,plugins: [this.exp,new Ext.ux.dd.GridDragDropRowOrder({ - copy: false // false by default - ,scrollable: true // enable scrolling support (default is false) - ,targetCfg: {} - ,listeners: { - 'afterrowmove': {fn:this.onAfterRowMove,scope:this} + Ext.applyIf(config, { + id: 'modx-grid-dashboard-widget-placements', + url: MODx.config.connector_url, + fields: [ + 'dashboard', + 'widget', + 'rank', + 'name', + 'name_trans', + 'description', + 'description_trans', + 'permissions' + ], + autoHeight: true, + primaryKey: 'widget', + cls: 'modx-grid modx-grid-draggable', + plugins: [this.exp, new Ext.ux.dd.GridDragDropRowOrder({ + scrollable: true, + targetCfg: {}, + listeners: { + afterrowmove: { + fn: this.onAfterRowMove, + scope: this + } } - })] - ,columns: [this.exp,{ - header: _('widget') - ,dataIndex: 'name_trans' - ,width: 600 - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=system/dashboards/widget/update&id=' + record.data.widget - ,target: '_blank' - }); - }, scope: this } - },{ - header: _('rank') - ,dataIndex: 'rank' - ,width: 80 - ,editor: { xtype: 'numberfield', allowBlank: false, allowNegative: false } - }] - ,tbar: [{ - text: _('widget_place') - ,cls:'primary-button' - ,handler: this.placeWidget - ,scope: this + })], + columns: [this.exp, { + header: _('widget'), + dataIndex: 'name_trans', + width: 150, + renderer: { + fn: function(value, metaData, record) { + return this.renderLink(value, { + href: `?a=system/dashboards/widget/update&id=${record.data.widget}`, + target: '_blank' + }); + }, + scope: this + } + }, { + header: _('rank'), + dataIndex: 'rank', + width: 80, + align: 'center', + editor: { + xtype: 'numberfield', + allowBlank: false, + allowNegative: false + } + }], + tbar: [{ + text: _('widget_place'), + cls: 'primary-button', + handler: this.placeWidget, + scope: this }] }); - MODx.grid.DashboardWidgetPlacements.superclass.constructor.call(this,config); - this.propRecord = Ext.data.Record.create(['dashboard','widget','rank','name','name_trans','description','description_trans']); + MODx.grid.DashboardWidgetPlacements.superclass.constructor.call(this, config); + this.propRecord = Ext.data.Record.create([ + 'dashboard', + 'widget', + 'rank', + 'name', + 'name_trans', + 'description', + 'description_trans', + 'permissions' + ]); }; -Ext.extend(MODx.grid.DashboardWidgetPlacements,MODx.grid.LocalGrid,{ +Ext.extend(MODx.grid.DashboardWidgetPlacements, MODx.grid.LocalGrid, { getMenu: function() { return [{ - text: _('widget_unplace') - ,handler: this.unplaceWidget - ,scope: this + text: _('widget_unplace'), + handler: this.unplaceWidget, + scope: this }]; - } - - ,onAfterRowMove: function(dt,sri,ri,sels) { - var s = this.getStore(); - var sourceRec = s.data.items[sri]; - var total = s.data.length; + }, - sourceRec.set('rank',sri); - sourceRec.commit(); + onAfterRowMove: function(dropTarget, fromRowIndex, toRowIndex, selections) { + const + store = this.getStore(), + firstDraggedRecord = store.data.items[fromRowIndex], + total = store.data.length + ; + firstDraggedRecord.set('rank', fromRowIndex); + firstDraggedRecord.commit(); - /* get all rows below ri, and up their rank by 1 */ - var brec; - for (var x=(ri-1);x 0 - // Get the rank of the last record - ? s.data.items[s.data.length - 1].get('rank') + 1 - // Or set it to '0' if no record found - : 0; - - var fldStore = fld.getStore(); - var fldRi = fldStore.find('id',fld.getValue()); - var rec = fldStore.getAt(fldRi); - - if (id != '' && this.fp.getForm().isValid()) { - - if (this.fireEvent('success',{ - widget: fld.getValue() - ,dashboard: g.config.dashboard - ,name: rec.data.name - ,name_trans: rec.data.name_trans - ,description: rec.data.description - ,description_trans: rec.data.description_trans - ,rank: rank - })) { - this.fp.getForm().reset(); - this.hide(); - return true; - } - } else { - MODx.msg.alert(_('error'),_('widget_err_ns')); - } - return true; - } -}); -Ext.reg('modx-window-dashboard-widget-place',MODx.window.DashboardWidgetPlace); - -/* -MODx.grid.DashboardUserGroups = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-grid-dashboard-usergroups' - ,url: MODx.config.connector_url - ,action: 'system/dashboard/group/getList' - ,fields: ['id','name'] - ,autoHeight: true - ,primaryKey: 'user' - ,columns: [{ - header: _('user_group') - ,dataIndex: 'name' - ,width: 600 - }] - ,tbar: [{ - text: _('dashboard_usergroup_add') - ,handler: this.addUserGroup - ,scope: this - }] - }); - MODx.grid.DashboardUserGroups.superclass.constructor.call(this,config); - this.propRecord = Ext.data.Record.create(['id','name']); -}; -Ext.extend(MODx.grid.DashboardUserGroups,MODx.grid.LocalGrid,{ - getMenu: function() { - return [{ - text: _('dashboard_usergroup_remove') - ,handler: this.remove.createDelegate(this,[{ - title: _('dashboard_usergroup_remove') - ,text: _('dashboard_usergroup_remove_confirm') - }]) - ,scope: this - }]; - } - - ,addUserGroup: function(btn,e) { - this.loadWindow(btn,e,{ - xtype: 'modx-window-dashboard-usergroup-add' - ,listeners: { - 'success': {fn:function(vs) { - var rec = new this.propRecord(vs); - this.getStore().add(rec); - },scope:this} - } - }); - var w = Ext.getCmp('modx-window-dashboard-usergroup-add'); - w.reset(); - w.setValues({ - dashboard: this.config.dashboard - }); - - } -}); -Ext.reg('modx-grid-dashboard-usergroups',MODx.grid.DashboardUserGroups); - -MODx.window.DashboardUserGroupAdd = function(config) { - config = config || {}; - this.ident = config.ident || 'dbugadd'+Ext.id(); - Ext.applyIf(config,{ - title: _('dashboard_usergroup_add') - ,frame: true - ,id: 'modx-window-dashboard-usergroup-add' - ,fields: [{ - xtype: 'modx-combo-usergroup' - ,fieldLabel: _('user_group') - ,name: 'usergroup' - ,hiddenName: 'usergroup' - ,id: 'modx-'+this.ident+'-usergroup' - ,allowBlank: false - }] - }); - MODx.window.DashboardUserGroupAdd.superclass.constructor.call(this,config); -}; -Ext.extend(MODx.window.DashboardUserGroupAdd,MODx.Window,{ - submit: function() { - var f = this.fp.getForm(); - var fld = f.findField('usergroup'); - - if (id != '' && this.fp.getForm().isValid()) { - if (this.fireEvent('success',{ - id: fld.getValue() - ,name: fld.getRawValue() + const + // Get the rank of the last record or set it to '0' if no record found + rank = store.data.length > 0 + ? store.data.items[store.data.length - 1].get('rank') + 1 + : 0, + widgetStore = widgetField.getStore(), + widgetRowIndex = widgetStore.find('id', selectedWidget), + record = widgetStore.getAt(widgetRowIndex) + ; + if (this.fp.getForm().isValid()) { + if (this.fireEvent('success', { + widget: widgetField.getValue(), + dashboard: widgetsGrid.config.dashboard, + name: record.data.name, + name_trans: record.data.name_trans, + description: record.data.description, + description_trans: record.data.description_trans, + rank: rank })) { this.fp.getForm().reset(); this.hide(); return true; } } else { - MODx.msg.alert(_('error'),_('user_group_err_ns')); + MODx.msg.alert(_('error'), _('widget_err_ns')); } return true; } }); -Ext.reg('modx-window-dashboard-usergroup-add',MODx.window.DashboardUserGroupAdd); -*/ +Ext.reg('modx-window-dashboard-widget-place', MODx.window.DashboardWidgetPlace); /** * @class MODx.combo.DashboardWidgets @@ -486,28 +442,36 @@ Ext.reg('modx-window-dashboard-usergroup-add',MODx.window.DashboardUserGroupAdd) * @param {Object} config An object of options. * @xtype modx-combo-dashboard-widgets */ -MODx.combo.DashboardWidgets = function(config) { - config = config || {}; - Ext.applyIf(config,{ - name: 'widget' - ,hiddenName: 'widget' - ,displayField: 'name_trans' - ,editable: true - ,valueField: 'id' - ,fields: ['id','name','name_trans','description','description_trans'] - ,pageSize: 20 - ,url: MODx.config.connector_url - ,baseParams: { - action: 'System/Dashboard/Widget/GetList' - ,combo: true - } - ,tpl: new Ext.XTemplate('' - ,'
    ' - ,'

    {name_trans:htmlEncode}

    ' - ,'

    {description_trans:htmlEncode}

    ' - ,'
    ') +MODx.combo.DashboardWidgets = function(config = {}) { + Ext.applyIf(config, { + name: 'widget', + hiddenName: 'widget', + displayField: 'name_trans', + editable: true, + valueField: 'id', + fields: [ + 'id', + 'name', + 'name_trans', + 'description', + 'description_trans' + ], + pageSize: 20, + url: MODx.config.connector_url, + baseParams: { + action: 'System/Dashboard/Widget/GetList', + combo: true + }, + tpl: new Ext.XTemplate(` + +
    +

    {name_trans:htmlEncode}

    +

    {description_trans:htmlEncode}

    +
    +
    + `) }); - MODx.combo.DashboardWidgets.superclass.constructor.call(this,config); + MODx.combo.DashboardWidgets.superclass.constructor.call(this, config); }; -Ext.extend(MODx.combo.DashboardWidgets,MODx.combo.ComboBox); -Ext.reg('modx-combo-dashboard-widgets',MODx.combo.DashboardWidgets); +Ext.extend(MODx.combo.DashboardWidgets, MODx.combo.ComboBox); +Ext.reg('modx-combo-dashboard-widgets', MODx.combo.DashboardWidgets); diff --git a/manager/assets/modext/widgets/system/modx.panel.dashboards.js b/manager/assets/modext/widgets/system/modx.panel.dashboards.js index 1be48b36c5d..99b5f47c06f 100644 --- a/manager/assets/modext/widgets/system/modx.panel.dashboards.js +++ b/manager/assets/modext/widgets/system/modx.panel.dashboards.js @@ -4,44 +4,46 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-dashboards */ -MODx.panel.Dashboards = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-dashboards' - ,cls: 'container' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('dashboards') - ,id: 'modx-dashboards-header' - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - layout: 'form' - ,title: _('dashboards') - ,items: [{ - html: '

    '+_('dashboards.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-dashboards' - ,cls: 'main-wrapper' - ,preventRender: true +MODx.panel.Dashboards = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-dashboards', + cls: 'container', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [{ + html: _('dashboards'), + id: 'modx-dashboards-header', + xtype: 'modx-header' + }, MODx.getPageStructure([{ + layout: 'form', + title: _('dashboards'), + items: [{ + html: `

    ${_('dashboards.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-dashboards', + cls: 'main-wrapper', + preventRender: true }] - },{ - layout: 'form' - ,title: _('widgets') - ,items: [{ - html: '

    '+_('widgets.intro_msg')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-dashboard-widgets' - ,cls: 'main-wrapper' - ,preventRender: true + }, { + layout: 'form', + title: _('widgets'), + items: [{ + html: `

    ${_('widgets.intro_msg')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-dashboard-widgets', + cls: 'main-wrapper', + preventRender: true }] }])] }); - MODx.panel.Dashboards.superclass.constructor.call(this,config); + MODx.panel.Dashboards.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Dashboards,MODx.FormPanel); -Ext.reg('modx-panel-dashboards',MODx.panel.Dashboards); +Ext.extend(MODx.panel.Dashboards, MODx.FormPanel); +Ext.reg('modx-panel-dashboards', MODx.panel.Dashboards); /** * @class MODx.grid.Dashboards @@ -52,72 +54,101 @@ Ext.reg('modx-panel-dashboards',MODx.panel.Dashboards); 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 - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-dashboards', + url: MODx.config.connector_url, + baseParams: { action: 'System/Dashboard/GetList', usergroup: MODx.request.usergroup || null - } - ,fields: [ + }, + fields: [ 'id', 'name', 'description', - 'cls' - ] - ,paging: true - ,autosave: true - ,save_action: 'System/Dashboard/UpdateFromGrid' - ,remoteSort: true - ,sm: this.sm - ,columns: [this.sm,{ - header: _('id') - ,dataIndex: 'id' - ,width: 50 - ,sortable: true - },{ - header: _('name') - ,dataIndex: 'name' - ,width: 150 - ,sortable: true - ,editor: { xtype: 'textfield' ,allowBlank: false } - ,renderer: { fn: function(v,md,record) { - return this.renderLink(v, { - href: '?a=system/dashboards/update&id=' + record.data.id - }); - }, scope: this } - },{ - header: _('description') - ,dataIndex: 'description' - ,width: 300 - ,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 - ,scope: this - }] - },'->',{ - xtype: 'modx-combo-usergroup' - ,itemId: 'filter-usergroup' - ,emptyText: _('user_group_filter') - ,baseParams: { - action: 'Security/Group/GetList' - ,addAll: true + 'creator' + ], + paging: true, + autosave: true, + save_action: 'System/Dashboard/UpdateFromGrid', + remoteSort: true, + sm: this.sm, + columns: [this.sm, { + header: _('id'), + dataIndex: 'id', + width: 50, + sortable: true + }, { + header: _('name'), + dataIndex: 'name', + width: 150, + sortable: true, + editor: { + xtype: 'textfield', + allowBlank: false, + blankText: _('dashboard_err_ns_name'), + validationEvent: 'change', + validator: function(value) { + const grid = Ext.getCmp('modx-grid-dashboards'), + reserved = this.gridEditor.record.json.reserved.name + ; + if (grid.valueIsReserved(reserved, value)) { + const msg = _('dashboard_err_name_reserved', { reservedName: value }); + Ext.Msg.alert(_('error'), msg); + return false; + } + return true; } - ,value: MODx.request.usergroup || null - ,width: 200 - ,listeners: { + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return this.userCanEditRecord(record) + ? this.renderLink(value, { + href: `?a=system/dashboards/update&id=${record.data.id}`, + title: _('dashboard_edit') + }) + : value + ; + }, + scope: this + } + }, { + header: _('description'), + dataIndex: 'description', + width: 300, + sortable: false, + editor: { + xtype: 'textarea' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses(record, [record.json.isProtected]); + return value; + }, + scope: this + } + }, + this.getCreatorColumnConfig('dashboard') + ], + tbar: [ + this.getCreateButton('dashboard', 'createDashboard'), + this.getBulkActionsButton('dashboard', 'System/Dashboard/RemoveMultiple'), + '->', + { + 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) { + fn: function(cmp, record, selectedIndex) { this.applyGridFilter(cmp, 'usergroup'); }, scope: this @@ -128,103 +159,74 @@ MODx.grid.Dashboards = function(config = {}) { this.getClearFiltersButton('filter-usergroup, filter-query') ] }); - MODx.grid.Dashboards.superclass.constructor.call(this,config); + MODx.grid.Dashboards.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete', 'duplicate']; + + // Note there are currently no action-specific permissions for Dashboards + this.setUserCanEdit(['dashboards']); + this.setUserCanCreate(['dashboards']); + this.setUserCanDelete(['dashboards']); + this.setShowActionsMenu(); + + this.on({ + beforerender: function(grid) { + grid.view = new Ext.grid.GridView(grid.getViewConfig()); + } + }); }; -Ext.extend(MODx.grid.Dashboards,MODx.grid.Grid,{ +Ext.extend(MODx.grid.Dashboards, MODx.grid.Grid, { getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.cls; - - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateDashboard }); - } else { - if (p.indexOf('pupdate') != -1) { - m.push({ - text: _('edit') - ,handler: this.updateDashboard - }); - } - if (p.indexOf('pduplicate') != -1) { - m.push({ - text: _('duplicate') - ,handler: this.duplicateDashboard - }); - } - if (p.indexOf('premove') != -1 && r.data.id != 1 && r.data.name != 'Default') { - if (m.length > 0) m.push('-'); - m.push({ - text: _('delete') - ,handler: this.removeDashboard - }); - } } - if (m.length > 0) { - this.addContextMenuItem(m); + if (this.userCanCreate && this.userCanDuplicateRecord(record)) { + menu.push({ + text: _('duplicate'), + handler: this.duplicateDashboard + }); } - } + if (this.userCanDelete && this.userCanDeleteRecord(record)) { + if (menu.length > 0) { + menu.push('-'); + } + menu.push({ + text: _('delete'), + handler: this.confirm.createDelegate(this, ['System/Dashboard/Remove', 'dashboard_remove_confirm']) + }); + } + return menu; + }, - ,createDashboard: function() { + createDashboard: function() { MODx.loadPage('system/dashboards/create'); - } + }, - ,updateDashboard: function() { - MODx.loadPage('system/dashboards/update', 'id='+this.menu.record.id); - } + updateDashboard: function() { + MODx.loadPage('system/dashboards/update', `id=${this.menu.record.id}`); + }, - ,duplicateDashboard: function(btn,e) { + duplicateDashboard: function(btn, e) { MODx.Ajax.request({ - url: this.config.url - ,params: { - action: 'System/Dashboard/Duplicate' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} - } - }); - } - - ,removeDashboard: function() { - MODx.msg.confirm({ - title: _('delete') - ,text: _('dashboard_remove_confirm') - ,url: this.config.url - ,params: { - action: 'System/Dashboard/Remove' - ,id: this.menu.record.id - } - ,listeners: { - 'success': {fn:this.refresh,scope:this} - } - }); - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('dashboard_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'System/Dashboard/RemoveMultiple' - ,dashboards: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} + url: this.config.url, + params: { + action: 'System/Dashboard/Duplicate', + id: this.menu.record.id + }, + listeners: { + success: { + fn: this.refresh, + scope: this + } } }); - return true; } - }); -Ext.reg('modx-grid-dashboards',MODx.grid.Dashboards); +Ext.reg('modx-grid-dashboards', MODx.grid.Dashboards); diff --git a/manager/assets/modext/workspace/lexicon/lexicon.grid.js b/manager/assets/modext/workspace/lexicon/lexicon.grid.js index 6b074fa3c78..8eaa1053c54 100644 --- a/manager/assets/modext/workspace/lexicon/lexicon.grid.js +++ b/manager/assets/modext/workspace/lexicon/lexicon.grid.js @@ -11,10 +11,10 @@ MODx.grid.Lexicon = function(config = {}) { 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: [ + Ext.applyIf(config, { + id: 'modx-grid-lexicon', + url: MODx.config.connector_url, + fields: [ 'name', 'value', 'namespace', @@ -22,47 +22,56 @@ MODx.grid.Lexicon = function(config = {}) { 'language', 'editedon', 'overridden' - ] - ,baseParams: { + ], + baseParams: { action: 'Workspace/Lexicon/GetList', namespace: this.namespaceFilterValue, topic: this.topicFilterValue, language: this.languageFilterValue - } - ,paging: true - ,autosave: true - ,save_action: 'Workspace/Lexicon/UpdateFromGrid' - ,columns: [{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - ,renderer: this._renderStatus - },{ - header: _('value') - ,dataIndex: 'value' - ,width: 500 - ,sortable: false - ,editor: {xtype: 'textarea'} - ,renderer: this._renderStatus - },{ - header: _('last_modified') - ,dataIndex: 'editedon' - ,width: 125 - }] - ,tbar: { + }, + paging: true, + autosave: true, + preventSaveRefresh: false, + save_action: 'Workspace/Lexicon/UpdateFromGrid', + columns: [{ + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + renderer: this._renderStatus + }, { + header: _('value'), + dataIndex: 'value', + width: 500, + sortable: false, + editor: { + xtype: 'textarea' + }, + renderer: this._renderStatus + }, { + header: _('last_modified'), + dataIndex: 'editedon', + width: 125, + renderer: this._renderLastModDate + }], + tbar: { cls: 'has-nested-filters', items: [ + this.getCreateButton('lexicon', 'createEntry'), { - xtype: 'button' - ,text: _('create') - ,cls: 'primary-button' - ,handler: this.createEntry - ,scope: this - },{ - text: _('lexicon_revert') - ,handler: this.reloadFromBase - ,scope: this + text: _('lexicon_revert'), + handler: this.reloadFromBase, + scope: this, + listeners: { + render: { + fn: function(btn) { + if (!this.userCanEdit) { + btn.hide(); + } + }, + scope: this + } + } }, '->', { @@ -78,8 +87,7 @@ MODx.grid.Lexicon = function(config = {}) { { xtype: 'label', html: _('namespace') - }, - { + }, { xtype: 'modx-combo-namespace', itemId: 'filter-namespace', hideLabel: true, @@ -127,8 +135,7 @@ MODx.grid.Lexicon = function(config = {}) { { xtype: 'label', html: _('language') - }, - { + }, { xtype: 'modx-combo-language', itemId: 'filter-language', hideLabel: true, @@ -177,8 +184,7 @@ MODx.grid.Lexicon = function(config = {}) { { xtype: 'label', html: _('topic') - }, - { + }, { xtype: 'modx-combo-lexicon-topic', itemId: 'filter-topic', hideLabel: true, @@ -219,110 +225,125 @@ MODx.grid.Lexicon = function(config = {}) { ] } }); - MODx.grid.Lexicon.superclass.constructor.call(this,config); + MODx.grid.Lexicon.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit']; + + // Note there are currently no action-specific permissions for Lexicons + this.setUserCanCreate(['lexicons']); + this.setUserCanEdit(['lexicons']); + this.setUserCanDelete(['lexicons']); + this.setShowActionsMenu(); }; -Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ - console: null +Ext.extend(MODx.grid.Lexicon, MODx.grid.Grid, { + console: null, - ,_renderStatus: function(v,md,rec,ri) { - switch (rec.data.overridden) { + _renderStatus: function(value, metaData, record, rowIndex) { + switch (record.data.overridden) { case 1: - return ''+v+'';break; + return `${value}`; case 2: - return ''+v+''; + return `${value}`; default: - return ''+v+''; + return `${value}`; } - } + }, /** * @deprecated since 3.0.5. To be removed in future release. Datetime formatting * now handled in back end processors to provide uniform display across components. */ - ,_renderLastModDate: function(value) { + _renderLastModDate: function(value) { return value; - } + }, - ,loadWindow2: function(btn,e,o) { + loadWindow2: function(btn, e, o) { this.menu.record = { namespace: this.getFilterComponent('filter-namespace').getValue(), language: this.getFilterComponent('filter-language').getValue() }; - if (o.xtype != 'modx-window-lexicon-import') { + if (o.xtype !== 'modx-window-lexicon-import') { this.menu.record.topic = this.getFilterComponent('filter-topic').getValue(); } this.loadWindow(btn, e, o); - } + }, - ,reloadFromBase: function() { - namespace = this.getFilterComponent('filter-namespace').getValue(), - topic = this.getFilterComponent('filter-topic').getValue(), - language = this.getFilterComponent('filter-language').getValue(), - registryTopic = '/workspace/lexicon/reload/'; + reloadFromBase: function() { + const + namespace = this.getFilterComponent('filter-namespace').getValue(), + topic = this.getFilterComponent('filter-topic').getValue(), + language = this.getFilterComponent('filter-language').getValue(), + registryTopic = '/workspace/lexicon/reload/' + ; MODx.msg.confirm({ text: _('lexicon_revert_confirm', { - namespace: namespace - ,topic: topic - ,language: language - }) - ,url: this.config.url - ,params: { - action: 'Workspace/Lexicon/ReloadFromBase' - ,register: 'mgr' - ,topic: registryTopic - ,namespace: namespace - ,lexiconTopic: topic - ,language: language - } - ,listeners: { - 'success': { - fn:function() { + namespace: namespace, + topic: topic, + language: language + }), + url: this.config.url, + params: { + action: 'Workspace/Lexicon/ReloadFromBase', + register: 'mgr', + topic: registryTopic, + namespace: namespace, + lexiconTopic: topic, + language: language + }, + listeners: { + success: { + fn: function() { this.console = MODx.load({ - xtype: 'modx-console' - ,register: 'mgr' - ,topic: registryTopic + xtype: 'modx-console', + register: 'mgr', + topic: registryTopic }); - this.console.on('complete',function(){ + this.console.on('complete', function() { this.refresh(); - },this); + }, this); this.console.show(Ext.getBody()); - } - ,scope:this + }, + scope: this } } }); - } + }, - ,revertEntry: function() { - var p = this.menu.record; - p.action = 'Workspace/Lexicon/Revert'; + revertEntry: function() { + const { record } = this.menu; + record.action = 'Workspace/Lexicon/Revert'; MODx.Ajax.request({ - url: this.config.url - ,params: p - ,listeners: { - 'success': {fn:function(r) { - this.refresh(); - },scope:this} + url: this.config.url, + params: record, + listeners: { + success: { + fn: function(r) { + this.refresh(); + }, + scope: this + } } }); - } + }, - ,getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var m = []; - if (r.data.overridden) { - m.push({ - text: _('entry_revert') - ,handler: this.revertEntry + getMenu: function() { + const + record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (record.data.overridden) { + menu.push({ + text: _('entry_revert'), + handler: this.revertEntry }); } - return m; - } + return menu; + }, - ,createEntry: function(btn, e) { + createEntry: function(btn, e) { const record = this.menu.record || {}; record.namespace = this.getFilterComponent('filter-namespace').getValue(); @@ -348,64 +369,49 @@ Ext.extend(MODx.grid.Lexicon,MODx.grid.Grid,{ this.createEntryWindow.show(e.target); } }); -Ext.reg('modx-grid-lexicon',MODx.grid.Lexicon); +Ext.reg('modx-grid-lexicon', MODx.grid.Lexicon); -MODx.window.LexiconEntryCreate = function(config) { - config = config || {}; - this.ident = config.ident || 'lexentc'+Ext.id(); - var r = config.record; - Ext.applyIf(config,{ - title: _('create') - ,url: MODx.config.connector_url - ,action: 'Workspace/Lexicon/Create' - ,fileUpload: true - ,fields: [{ - xtype: 'textfield' - ,fieldLabel: _('name') - ,id: 'modx-'+this.ident+'-name' - ,itemId: 'name' - ,name: 'name' - ,anchor: '100%' - ,msgTarget: 'under' - ,allowBlank: false - },{ - xtype: 'modx-combo-namespace' - ,fieldLabel: _('namespace') - ,name: 'namespace' - ,id: 'modx-'+this.ident+'-namespace' - ,itemId: 'namespace' - ,anchor: '100%' - ,msgTarget: 'under' - ,allowBlank: false - },{ - xtype: 'modx-combo-lexicon-topic' - ,fieldLabel: _('topic') - ,name: 'topic' - ,id: 'modx-'+this.ident+'-topic' - ,itemId: 'topic' - ,anchor: '100%' - ,msgTarget: 'under' - ,allowBlank: false - },{ - xtype: 'modx-combo-language' - ,fieldLabel: _('language') - ,name: 'language' - ,id: 'modx-'+this.ident+'-language' - ,itemId: 'language' - ,anchor: '100%' - ,msgTarget: 'under' - ,allowBlank: false - },{ - xtype: 'textarea' - ,fieldLabel: _('value') - ,id: 'modx-'+this.ident+'-value' - ,itemId: 'value' - ,name: 'value' - ,anchor: '100%' - ,msgTarget: 'under' +MODx.window.LexiconEntryCreate = function(config = {}) { + this.ident = config.ident || `lexentc${Ext.id()}`; + Ext.applyIf(config, { + title: _('create'), + url: MODx.config.connector_url, + action: 'Workspace/Lexicon/Create', + fileUpload: true, + formDefaults: { + anchor: '100%', + msgTarget: 'under', + allowBlank: false + }, + fields: [{ + xtype: 'textfield', + fieldLabel: _('name'), + itemId: 'name', + name: 'name' + }, { + xtype: 'modx-combo-namespace', + fieldLabel: _('namespace'), + name: 'namespace', + itemId: 'namespace' + }, { + xtype: 'modx-combo-lexicon-topic', + fieldLabel: _('topic'), + name: 'topic', + itemId: 'topic' + }, { + xtype: 'modx-combo-language', + fieldLabel: _('language'), + name: 'language', + itemId: 'language' + }, { + xtype: 'textarea', + fieldLabel: _('value'), + itemId: 'value', + name: 'value', + allowBlank: true }] }); - MODx.window.LexiconEntryCreate.superclass.constructor.call(this,config); + MODx.window.LexiconEntryCreate.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.LexiconEntryCreate,MODx.Window); -Ext.reg('modx-window-lexicon-entry-create',MODx.window.LexiconEntryCreate); +Ext.extend(MODx.window.LexiconEntryCreate, MODx.Window); +Ext.reg('modx-window-lexicon-entry-create', MODx.window.LexiconEntryCreate); diff --git a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js index 90aaaba74e7..f86cad9ec68 100644 --- a/manager/assets/modext/workspace/namespace/modx.namespace.panel.js +++ b/manager/assets/modext/workspace/namespace/modx.namespace.panel.js @@ -6,34 +6,41 @@ * @param {Object} config An object of configuration properties * @xtype modx-panel-namespaces */ -MODx.panel.Namespaces = function(config) { - config = config || {}; - Ext.applyIf(config,{ - id: 'modx-panel-namespaces' - ,cls: 'container' - ,bodyStyle: '' - ,defaults: { collapsible: false ,autoHeight: true } - ,items: [{ - html: _('namespaces') - ,id: 'modx-namespaces-header' - ,xtype: 'modx-header' - },MODx.getPageStructure([{ - title: _('namespaces') - ,layout: 'form' - ,items: [{ - html: '

    '+_('namespaces_desc')+'

    ' - ,xtype: 'modx-description' - },{ - xtype: 'modx-grid-namespace' - ,cls:'main-wrapper' - ,preventRender: true - }] - }])] +MODx.panel.Namespaces = function(config = {}) { + Ext.applyIf(config, { + id: 'modx-panel-namespaces', + cls: 'container', + bodyStyle: '', + defaults: { + collapsible: false, + autoHeight: true + }, + items: [ + { + html: _('namespaces'), + id: 'modx-namespaces-header', + xtype: 'modx-header' + }, + MODx.getPageStructure([{ + title: _('namespaces'), + layout: 'form', + items: [ + { + html: `

    ${_('namespaces_desc')}

    `, + xtype: 'modx-description' + }, { + xtype: 'modx-grid-namespace', + cls: 'main-wrapper', + preventRender: true + } + ] + }]) + ] }); - MODx.panel.Namespaces.superclass.constructor.call(this,config); + MODx.panel.Namespaces.superclass.constructor.call(this, config); }; -Ext.extend(MODx.panel.Namespaces,MODx.FormPanel); -Ext.reg('modx-panel-namespaces',MODx.panel.Namespaces); +Ext.extend(MODx.panel.Namespaces, MODx.FormPanel); +Ext.reg('modx-panel-namespaces', MODx.panel.Namespaces); /** * Loads a grid for managing namespaces. @@ -43,121 +50,159 @@ Ext.reg('modx-panel-namespaces',MODx.panel.Namespaces); * @param {Object} config An object of configuration properties * @xtype modx-grid-namespace */ -MODx.grid.Namespace = function(config) { - config = config || {}; +MODx.grid.Namespace = function(config = {}) { this.sm = new Ext.grid.CheckboxSelectionModel(); - Ext.applyIf(config,{ - url: MODx.config.connector_url - ,baseParams: { + Ext.applyIf(config, { + id: 'modx-grid-namespaces', + url: MODx.config.connector_url, + baseParams: { action: 'Workspace/PackageNamespace/GetList' - } - ,fields: [ - 'id', + }, + fields: [ 'name', 'path', 'assets_path', - 'perm' - ] - ,anchor: '100%' - ,paging: true - ,autosave: true - ,save_action: 'Workspace/PackageNamespace/UpdateFromGrid' - ,primaryKey: 'name' - ,remoteSort: true - ,sm: this.sm - ,columns: [this.sm,{ - header: _('name') - ,dataIndex: 'name' - ,width: 200 - ,sortable: true - },{ - header: _('namespace_path') - ,dataIndex: 'path' - ,width: 500 - ,sortable: false - ,editor: { xtype: 'textfield' } - },{ - header: _('namespace_assets_path') - ,dataIndex: 'assets_path' - ,width: 500 - ,sortable: false - ,editor: { xtype: 'textfield' } - }] - ,tbar: [ - { - text: _('create') - ,handler: { xtype: 'modx-window-namespace-create' ,blankValues: true } - ,cls:'primary-button' - ,scope: this + 'perm', + 'creator' + ], + anchor: '100%', + paging: true, + autosave: true, + save_action: 'Workspace/PackageNamespace/UpdateFromGrid', + primaryKey: 'name', + remoteSort: true, + sm: this.sm, + columns: [this.sm, { + header: _('name'), + dataIndex: 'name', + width: 200, + sortable: true, + // because PK is name, allowing edit is tricky as implemented; leave for now + listeners: { + click: { + fn: function(column, grid, rowIndex, e) { + if (e.target.classList.contains('simulated-link')) { + this.updateNamespace(e); + } + }, + scope: this + } + } + }, { + header: _('namespace_path'), + dataIndex: 'path', + width: 500, + sortable: false, + editor: { + xtype: 'textfield' }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses( + record, + [record.json.isProtected, record.json.isExtrasNamespace], + '', + false + ); + return value; + }, + scope: this + } + }, { + header: _('namespace_assets_path'), + dataIndex: 'assets_path', + width: 500, + sortable: false, + editor: { + xtype: 'textfield' + }, + renderer: { + fn: function(value, metaData, record) { + // eslint-disable-next-line no-param-reassign + metaData.css = this.setEditableCellClasses( + record, + [record.json.isProtected, record.json.isExtrasNamespace], + '', + false + ); + return value; + }, + scope: this + } + }, + this.getCreatorColumnConfig('namespace') + ], + tbar: [ + this.getCreateButton('namespace', { + xtype: 'modx-window-namespace-create', + blankValues: true + }), + this.getBulkActionsButton('namespace', 'Workspace/PackageNamespace/RemoveMultiple', 'string'), '->', this.getQueryFilterField(), this.getClearFiltersButton() - ] + ], + viewConfig: this.getViewConfig() + }); + MODx.grid.Namespace.superclass.constructor.call(this, config); + + this.gridMenuActions = ['edit', 'delete']; + + // Note there are currently no action-specific permissions for Namespaces + this.setUserCanEdit(['namespaces']); + this.setUserCanCreate(['namespaces']); + this.setUserCanDelete(['namespaces']); + this.setShowActionsMenu(); + + this.on({ + beforeedit: function(e) { + if (!this.userCanEditRecord(e.record) || e.record.json.isProtected || e.record.json.isExtrasNamespace) { + return false; + } + } }); - MODx.grid.Namespace.superclass.constructor.call(this,config); }; -Ext.extend(MODx.grid.Namespace,MODx.grid.Grid,{ +Ext.extend(MODx.grid.Namespace, MODx.grid.Grid, { + getMenu: function() { - var r = this.getSelectionModel().getSelected(); - var p = r.data.perm; - var m = []; - if (this.getSelectionModel().getCount() > 1) { - m.push({ - text: _('selected_remove') - ,handler: this.removeSelected - ,scope: this + const record = this.getSelectionModel().getSelected(), + menu = [] + ; + if (this.userCanEdit && this.userCanEditRecord(record)) { + menu.push({ + text: _('edit'), + handler: this.updateNamespace }); - } else { - m.push({ - text: _('edit') - ,handler: this.namespaceUpdate - }); - if (p.indexOf('premove') != -1 && this.menu.record.name != 'core') { - m.push({ - text: _('delete') - ,handler: this.remove.createDelegate(this,['namespace_remove_confirm','Workspace/PackageNamespace/Remove']) - }); + } + if (this.userCanDelete && this.userCanEditRecord(record)) { + if (menu.length > 0) { + menu.push('-'); } + menu.push({ + text: _('delete'), + handler: this.remove.createDelegate(this, ['namespace_remove_confirm', 'Workspace/PackageNamespace/Remove']) + }); } - return m; - } + return menu; + }, - ,namespaceUpdate: function(elem, vent) { - var win = MODx.load({ - xtype: 'modx-window-namespace-update' - ,record: this.menu.record - ,listeners: { - success: { - fn: this.refresh - ,scope: this + updateNamespace: function(e) { + const + record = this.getSelectionModel().getSelected().data, + window = MODx.load({ + xtype: 'modx-window-namespace-update', + record: record, + listeners: { + success: { + fn: this.refresh, + scope: this + } } - } - }); - win.setValues(this.menu.record); - win.show(vent.target); - } - - ,removeSelected: function() { - var cs = this.getSelectedAsList(); - if (cs === false) return false; - - MODx.msg.confirm({ - title: _('selected_remove') - ,text: _('namespace_remove_multiple_confirm') - ,url: this.config.url - ,params: { - action: 'Workspace/PackageNamespace/RemoveMultiple' - ,namespaces: cs - } - ,listeners: { - 'success': {fn:function(r) { - this.getSelectionModel().clearSelections(true); - this.refresh(); - },scope:this} - } - }); - return true; + }) + ; + window.setValues(record); + window.show(e.target); } }); -Ext.reg('modx-grid-namespace',MODx.grid.Namespace); +Ext.reg('modx-grid-namespace', MODx.grid.Namespace); diff --git a/manager/assets/modext/workspace/package/package.versions.grid.js b/manager/assets/modext/workspace/package/package.versions.grid.js index eac7118d076..3a4287ce4f5 100644 --- a/manager/assets/modext/workspace/package/package.versions.grid.js +++ b/manager/assets/modext/workspace/package/package.versions.grid.js @@ -1,107 +1,150 @@ -MODx.grid.PackageVersions = function(config) { - config = config || {}; +MODx.grid.PackageVersions = function(config = {}) { this.exp = new Ext.grid.RowExpander({ - tpl : new Ext.Template( + tpl: new Ext.Template( '

    {readme}

    ' ) }); - Ext.applyIf(config,{ - title: _('packages') - ,id: 'modx-grid-package-versions' - ,url: MODx.config.connector_url - ,baseParams: { - action: 'Workspace/Packages/Version/GetList' - ,signature: config.signature - ,package_name: MODx.request.package_name - } - ,fields: ['signature','name','version','release','created','updated','installed','state' - ,'workspace','provider','provider_name','disabled','source' - ,'readme','menu'] - ,plugins: [this.exp] - ,pageSize: 20 - ,columns: [this.exp,{ - header: _('name') ,dataIndex: 'name' } - ,{ header: _('version') ,dataIndex: 'version' } - ,{ header: _('release') ,dataIndex: 'release' } - ,{ header: _('installed') ,dataIndex: 'installed' ,renderer: this._rins } - ,{ - header: _('provider') - ,dataIndex: 'provider_name' - ,editable: false - }] - ,primaryKey: 'signature' - ,paging: true - ,autosave: true - ,tbar: [{ - text: _('package_versions_purge') - ,handler: this.purgePackageVersions + Ext.applyIf(config, { + title: _('packages'), + id: 'modx-grid-package-versions', + url: MODx.config.connector_url, + baseParams: { + action: 'Workspace/Packages/Version/GetList', + signature: config.signature, + package_name: MODx.request.package_name + }, + fields: [ + 'signature', + 'name', + 'version', + 'release', + 'created', + 'updated', + 'installed', + 'state', + 'workspace', + 'provider', + 'provider_name', + 'disabled', + 'source', + 'readme', + 'menu' + ], + plugins: [this.exp], + pageSize: 20, + columns: [this.exp, + { + header: _('name'), + dataIndex: 'name' + }, { + header: _('version'), + dataIndex: 'version' + }, { + header: _('release'), + dataIndex: 'release' + }, { + header: _('installed'), + dataIndex: 'installed', + renderer: this._rins + }, { + header: _('provider'), + dataIndex: 'provider_name', + editable: false + }], + primaryKey: 'signature', + paging: true, + autosave: true, + tbar: [{ + text: _('package_versions_purge'), + handler: this.purgePackageVersions, + listeners: { + render: { + fn: function(btn) { + if (!this.userCanDelete) { + btn.hide(); + } + }, + scope: this + } + } }] }); - MODx.grid.PackageVersions.superclass.constructor.call(this,config); + MODx.grid.PackageVersions.superclass.constructor.call(this, config); + + this.gridMenuActions = ['delete']; + + this.setUserCanDelete(['packages']); + this.setShowActionsMenu(); }; -Ext.extend(MODx.grid.PackageVersions,MODx.grid.Grid,{ +Ext.extend(MODx.grid.PackageVersions, MODx.grid.Grid, { _rins: function(value, metaData) { if (Ext.isEmpty(value) || value.includes(_('not_installed'))) { metaData.css = 'not-installed'; } return value; - } + }, - ,removePriorVersion: function(btn,e) { - var r = this.menu.record; + removePriorVersion: function(btn, e) { + const { record } = this.menu; MODx.msg.confirm({ - title: _('package_version_remove') - ,text: _('package_version_remove_confirm') - ,url: this.config.url - ,params: { - action: 'Workspace/Packages/Version/Remove' - ,signature: r.signature - } - ,listeners: { - 'success': {fn:function() { - if (this.fireEvent('afterRemoveRow',r)) { - this.removeActiveRow(r); - } - },scope:this} + title: _('package_version_remove'), + text: _('package_version_remove_confirm'), + url: this.config.url, + params: { + action: 'Workspace/Packages/Version/Remove', + signature: record.signature + }, + listeners: { + success: { + fn: function() { + if (this.fireEvent('afterRemoveRow', record)) { + this.removeActiveRow(record); + } + }, + scope: this + } } }); - } + }, /* Purge old package versions */ - ,purgePackageVersions: function(btn,e) { - var topic = '/Workspace/Packages/Purge/'; + purgePackageVersions: function(btn, e) { + const topic = '/Workspace/Packages/Purge/'; - this.loadWindow(btn,e,{ - xtype: 'modx-window-package-versions-purge' - ,record: { - packagename: this.config.package_name - ,topic: topic - ,register: 'mgr' - } - ,listeners: { - success: {fn: function(o) { - this.refresh(); - },scope:this} + this.loadWindow(btn, e, { + xtype: 'modx-window-package-versions-purge', + record: { + packagename: this.config.package_name, + topic: topic, + register: 'mgr' + }, + listeners: { + success: { + fn: function(o) { + this.refresh(); + }, + scope: this + } } }); - } + }, /* Load the console */ - ,loadConsole: function(btn,topic) { + loadConsole: function(btn, topic) { this.console = MODx.load({ - xtype: 'modx-console' - ,register: 'mgr' - ,topic: topic + xtype: 'modx-console', + register: 'mgr', + topic: topic }); this.console.show(btn); - } + }, - ,getConsole: function() { + getConsole: function() { return this.console; } }); -Ext.reg('modx-grid-package-versions',MODx.grid.PackageVersions); +Ext.reg('modx-grid-package-versions', MODx.grid.PackageVersions); /** * @class MODx.window.PurgePackageVersions @@ -109,53 +152,55 @@ Ext.reg('modx-grid-package-versions',MODx.grid.PackageVersions); * @param {Object} config An object of configuration parameters * @xtype modx-window-package-versions-purge */ -MODx.window.PurgePackageVersions = function(config) { - config = config || {}; - Ext.applyIf(config,{ - title: _('package_versions_purge') - ,url: MODx.config.connector_url - ,baseParams: { +MODx.window.PurgePackageVersions = function(config = {}) { + Ext.applyIf(config, { + title: _('package_versions_purge'), + url: MODx.config.connector_url, + baseParams: { action: 'Workspace/Packages/Purge' - } - ,cls: 'modx-confirm' - ,defaults: { border: false } - ,fields: [{ - xtype: 'hidden' - ,name: 'packagename' - ,id: 'modx-ppack-package_name' - ,value: config.packagename - },{ + }, + cls: 'modx-confirm', + defaults: { border: false }, + fields: [{ + xtype: 'hidden', + name: 'packagename', + id: 'modx-ppack-package_name', + value: config.packagename + }, { html: _('package_versions_purge_confirm') - }] - ,saveBtnText: _('package_versions_purge') + }], + saveBtnText: _('package_versions_purge') }); - MODx.window.PurgePackageVersions.superclass.constructor.call(this,config); + MODx.window.PurgePackageVersions.superclass.constructor.call(this, config); }; -Ext.extend(MODx.window.PurgePackageVersions,MODx.Window,{ +Ext.extend(MODx.window.PurgePackageVersions, MODx.Window, { submit: function() { - var r = this.config.record; + const { record } = this.config; if (this.fp.getForm().isValid()) { - Ext.getCmp('modx-grid-package-versions').loadConsole(Ext.getBody(),r.topic); + Ext.getCmp('modx-grid-package-versions').loadConsole(Ext.getBody(), record.topic); this.fp.getForm().baseParams = { - action: 'Workspace/Packages/Purge' - ,register: 'mgr' - ,topic: r.topic + action: 'Workspace/Packages/Purge', + register: 'mgr', + topic: record.topic }; this.fp.getForm().submit({ - waitMsg: _('saving') - ,scope: this - ,failure: function(frm,a) { - this.fireEvent('failure',frm,a); - var g = Ext.getCmp('modx-grid-package-versions'); + waitMsg: _('saving'), + scope: this, + failure: function(frm, a) { + this.fireEvent('failure', frm, a); + const g = Ext.getCmp('modx-grid-package-versions'); g.getConsole().fireEvent('complete'); g.refresh(); Ext.Msg.hide(); this.hide(); - } - ,success: function(frm,a) { - this.fireEvent('success',{f:frm,a:a}); - var g = Ext.getCmp('modx-grid-package-versions'); + }, + success: function(frm, a) { + this.fireEvent('success', { + f: frm, + a: a + }); + const g = Ext.getCmp('modx-grid-package-versions'); g.getConsole().fireEvent('complete'); g.refresh(); Ext.Msg.hide(); @@ -165,4 +210,4 @@ Ext.extend(MODx.window.PurgePackageVersions,MODx.Window,{ } } }); -Ext.reg('modx-window-package-versions-purge',MODx.window.PurgePackageVersions); +Ext.reg('modx-window-package-versions-purge', MODx.window.PurgePackageVersions); diff --git a/manager/controllers/default/security/user/index.class.php b/manager/controllers/default/security/user/index.class.php index ed940141a0d..cbe0fd24972 100644 --- a/manager/controllers/default/security/user/index.class.php +++ b/manager/controllers/default/security/user/index.class.php @@ -1,4 +1,5 @@ modx->hasPermission('edit_user'); + return $this->modx->hasPermission('view_user'); } /** diff --git a/manager/controllers/default/source/update.class.php b/manager/controllers/default/source/update.class.php index 1af8e61cdfd..d5ceaaaf6b5 100644 --- a/manager/controllers/default/source/update.class.php +++ b/manager/controllers/default/source/update.class.php @@ -1,4 +1,5 @@ modx->hasPermission('source_edit'); } @@ -40,18 +43,28 @@ public function checkPermissions() { * Register custom CSS/JS for the page * @return void */ - public function loadCustomCssJs() { - $mgrUrl = $this->modx->getOption('manager_url',null,MODX_MANAGER_URL); - $this->addJavascript($mgrUrl.'assets/modext/widgets/core/modx.grid.local.property.js'); - $this->addJavascript($mgrUrl.'assets/modext/widgets/source/modx.grid.source.properties.js'); - $this->addJavascript($mgrUrl.'assets/modext/widgets/source/modx.grid.source.access.js'); - $this->addJavascript($mgrUrl.'assets/modext/widgets/source/modx.panel.source.js'); - $this->addJavascript($mgrUrl.'assets/modext/sections/source/update.js'); - $this->addHtml(''); + public function loadCustomCssJs() + { + $mgrUrl = $this->modx->getOption('manager_url', null, MODX_MANAGER_URL); + $this->addJavascript($mgrUrl . 'assets/modext/widgets/core/modx.grid.local.property.js'); + $this->addJavascript($mgrUrl . 'assets/modext/widgets/source/modx.grid.source.properties.js'); + $this->addJavascript($mgrUrl . 'assets/modext/widgets/source/modx.grid.source.access.js'); + $this->addJavascript($mgrUrl . 'assets/modext/widgets/source/modx.panel.source.js'); + $this->addJavascript($mgrUrl . 'assets/modext/sections/source/update.js'); + $record = $this->modx->toJSON($this->sourceArray); + $defaultProps = $this->modx->toJSON($this->sourceDefaultProperties); + $pageCmp = << + Ext.onReady(function() { + MODx.load({ + xtype: 'modx-page-source-update', + record: {$record}, + defaultProperties: {$defaultProps} + }); + }); + +CMP; + $this->addHtml($pageCmp); } /** @@ -59,14 +72,24 @@ public function loadCustomCssJs() { * @param array $scriptProperties * @return mixed */ - public function process(array $scriptProperties = []) { - if (empty($this->scriptProperties['id']) || strlen($this->scriptProperties['id']) !== strlen((integer)$this->scriptProperties['id'])) { + public function process(array $scriptProperties = []) + { + if (empty($this->scriptProperties['id']) || strlen($this->scriptProperties['id']) !== strlen((int)$this->scriptProperties['id'])) { return $this->failure($this->modx->lexicon('source_err_ns')); } $this->source = $this->modx->getObject(modMediaSource::class, ['id' => $this->scriptProperties['id']]); - if (empty($this->source)) return $this->failure($this->modx->lexicon('source_err_nf')); + if (empty($this->source)) { + return $this->failure($this->modx->lexicon('source_err_nf')); + } $this->sourceArray = $this->source->toArray(); + + $coreSources = modMediaSource::getCoreSources(); + $sourceKey = $this->sourceArray['name']; + if (in_array($sourceKey, $coreSources)) { + $this->sourceArray['isProtected'] = true; + $this->sourceArray['reserved'] = true; + } $this->getProperties(); $this->getAccess(); @@ -75,7 +98,8 @@ public function process(array $scriptProperties = []) { return []; } - public function getProperties() { + public function getProperties() + { $properties = $this->source->getProperties(); $data = []; foreach ($properties as $property) { @@ -94,7 +118,8 @@ public function getProperties() { $this->sourceArray['properties'] = $data; } - public function getDefaultProperties() { + public function getDefaultProperties() + { $default = $this->source->getDefaultProperties(); $default = $this->source->prepareProperties($default); $data = []; @@ -115,7 +140,8 @@ public function getDefaultProperties() { return $data; } - public function getAccess() { + public function getAccess() + { $c = $this->modx->newQuery(modAccessMediaSource::class); $c->innerJoin(modMediaSource::class, 'Target'); $c->innerJoin(modAccessPolicy::class, 'Policy'); @@ -131,7 +157,7 @@ public function getAccess() { 'policy_name' => 'Policy.name', 'authority_name' => 'MinimumRole.name', ]); - $acls = $this->modx->getCollection(modAccessMediaSource::class,$c); + $acls = $this->modx->getCollection(modAccessMediaSource::class, $c); $access = []; /** @var modAccessMediaSource $acl */ foreach ($acls as $acl) { @@ -158,15 +184,17 @@ public function getAccess() { * * @return string */ - public function getPageTitle() { - return $this->modx->lexicon('source').': '.$this->sourceArray['name']; + public function getPageTitle() + { + return $this->modx->lexicon('source') . ': ' . $this->sourceArray['name']; } /** * Return the location of the template file * @return string */ - public function getTemplateFile() { + public function getTemplateFile() + { return ''; } @@ -174,7 +202,8 @@ public function getTemplateFile() { * Specify the language topics to load * @return array */ - public function getLanguageTopics() { + public function getLanguageTopics() + { return ['source','namespace','propertyset']; } @@ -182,7 +211,8 @@ public function getLanguageTopics() { * Get the Help URL * @return string */ - public function getHelpUrl() { + public function getHelpUrl() + { return 'Media+Sources'; } } diff --git a/manager/controllers/default/system/dashboards/update.class.php b/manager/controllers/default/system/dashboards/update.class.php index b7b53781491..88daf5c8c95 100644 --- a/manager/controllers/default/system/dashboards/update.class.php +++ b/manager/controllers/default/system/dashboards/update.class.php @@ -1,4 +1,5 @@ modx->hasPermission('dashboards'); } @@ -40,8 +43,9 @@ public function checkPermissions() { * * @return array */ - public function process(array $scriptProperties = []) { - if (empty($this->scriptProperties['id']) || strlen($this->scriptProperties['id']) !== strlen((integer)$this->scriptProperties['id'])) { + public function process(array $scriptProperties = []) + { + if (empty($this->scriptProperties['id']) || strlen($this->scriptProperties['id']) !== strlen((int)$this->scriptProperties['id'])) { $this->failure($this->modx->lexicon('dashboard_err_ns')); return []; } @@ -52,37 +56,49 @@ public function process(array $scriptProperties = []) { } $this->dashboardArray = $this->dashboard->toArray(); + + $coreDashboards = modDashboard::getCoreDashboards(); + $dashboardKey = $this->dashboardArray['name']; + if (in_array($dashboardKey, $coreDashboards)) { + $this->dashboardArray['isProtected'] = true; + $this->dashboardArray['reserved'] = true; + } + $this->dashboardArray['widgets'] = $this->getWidgets(); return $this->dashboardArray; - } /** * Get all the Widgets placed on this Dashboard * @return array */ - public function getWidgets() { + public function getWidgets() + { $c = $this->modx->newQuery(modDashboardWidgetPlacement::class); $c->where([ 'dashboard' => $this->dashboard->get('id'), 'user' => 0, ]); - $c->sortby('modDashboardWidgetPlacement.rank','ASC'); + $c->sortby('modDashboardWidgetPlacement.rank', 'ASC'); $placements = $this->modx->getCollection(modDashboardWidgetPlacement::class, $c); $list = []; /** @var modDashboardWidgetPlacement $placement */ foreach ($placements as $placement) { $placement->getOne('Widget'); - if (!($placement->Widget instanceof modDashboardWidget)) { continue; } - if ($placement->Widget->get('lexicon') != 'core:dashboards') { $this->modx->lexicon->load($placement->Widget->get('lexicon')); } $widgetArray = $placement->Widget->toArray(); + // Currently Dashboards do not have action-specific permissions, so hard code them + // here to true since view permission is needed to get to this point + $widgetArray['permissions'] = [ + 'edit' => true, + 'delete' => true + ]; $list[] = [ $placement->get('dashboard'), $placement->get('widget'), @@ -91,6 +107,7 @@ public function getWidgets() { $widgetArray['name_trans'], $widgetArray['description'], $widgetArray['description_trans'], + $widgetArray['permissions'] ]; } return $list; @@ -100,7 +117,8 @@ public function getWidgets() { * Get all the User Groups assigned to this Dashboard * @return array */ - public function getUserGroups() { + public function getUserGroups() + { $list = []; $c = $this->modx->newQuery(modUserGroup::class); $c->where([ @@ -121,9 +139,10 @@ public function getUserGroups() { * Register custom CSS/JS for the page * @return void */ - public function loadCustomCssJs() { - $this->addJavascript($this->modx->getOption('manager_url')."assets/modext/widgets/system/modx.panel.dashboard.js"); - $this->addJavascript($this->modx->getOption('manager_url').'assets/modext/sections/system/dashboards/update.js'); + public function loadCustomCssJs() + { + $this->addJavascript($this->modx->getOption('manager_url') . "assets/modext/widgets/system/modx.panel.dashboard.js"); + $this->addJavascript($this->modx->getOption('manager_url') . 'assets/modext/sections/system/dashboards/update.js'); $data = json_encode([ 'xtype' => 'modx-page-dashboard-update', 'record' => $this->dashboardArray, @@ -136,15 +155,17 @@ public function loadCustomCssJs() { * * @return string */ - public function getPageTitle() { - return $this->modx->lexicon('dashboards').': '.$this->dashboardArray['name']; + public function getPageTitle() + { + return $this->modx->lexicon('dashboards') . ': ' . $this->dashboardArray['name']; } /** * Return the location of the template file * @return string */ - public function getTemplateFile() { + public function getTemplateFile() + { return ''; } @@ -152,7 +173,8 @@ public function getTemplateFile() { * Specify the language topics to load * @return array */ - public function getLanguageTopics() { + public function getLanguageTopics() + { return ['dashboards','user']; } @@ -160,7 +182,8 @@ public function getLanguageTopics() { * Get the Help URL * @return string */ - public function getHelpUrl() { + public function getHelpUrl() + { return 'Dashboards'; } }