diff --git a/.eslintrc.js b/.eslintrc.js
index 9c3b37adfb8..fdcbea474f7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -31,6 +31,7 @@ module.exports = {
curly: ['error', 'all'],
eqeqeq: ['error', 'smart'],
'func-names': ['warn', 'as-needed'],
+ 'guard-for-in': 'warn',
indent: ['error', 4, {
VariableDeclarator: 'first',
SwitchCase: 1
@@ -39,15 +40,19 @@ module.exports = {
code: 140,
ignoreComments: true
}],
+ 'new-cap': 'warn',
'no-continue': 'warn',
+ 'no-global-assign': 'warn',
'no-new': 'warn',
'no-param-reassign': 'warn',
- 'no-plusplus': ['error', {
+ 'no-plusplus': ['warn', {
allowForLoopAfterthoughts: true
}],
+ 'no-restricted-syntax': 'warn',
'no-underscore-dangle': 'warn',
'no-unused-vars': ['error', { args: 'none' }],
'no-use-before-define': ['error', 'nofunc'],
+ 'no-useless-escape': 'warn',
'object-shorthand': ['error', 'consistent'],
'one-var': ['error', 'consecutive'],
'prefer-arrow-callback': 'warn',
diff --git a/_build/templates/default/sass/_forms.scss b/_build/templates/default/sass/_forms.scss
index 77c9e97b32c..5324f929efa 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;
@@ -150,7 +150,9 @@ input::-moz-focus-inner {
@extend %pseudo-font;
box-sizing: border-box;
color: scale-color($coreFieldLabelColor, $lightness: 50%);
- content: fa-content($fa-var-undo-alt); /* better match IMO for the action being taken */
+ content: fa-content(
+ $fa-var-undo-alt
+ ); /* better match IMO for the action being taken */
font-size: 14px;
position: relative;
bottom: 2px;
@@ -161,12 +163,12 @@ input::-moz-focus-inner {
height: 16px;
}
&.modx-field-reset {
- &::before {
- content: fa-content($fa-var-undo-alt);
- }
- &:hover::before {
- color: $green;
- }
+ &::before {
+ content: fa-content($fa-var-undo-alt);
+ }
+ &:hover::before {
+ color: $green;
+ }
}
&.modx-field-clear {
&::before {
@@ -242,19 +244,21 @@ input::-moz-focus-inner {
border-style: solid;
border-width: 10px 10px 10px 0;
border-color: transparent $lightGray transparent transparent;
- content: '';
+ content: "";
position: absolute;
top: 0;
left: -10px;
- transform: rotate(360deg); /* for better anti-aliasing in webkit browsers */
+ transform: rotate(
+ 360deg
+ ); /* for better anti-aliasing in webkit browsers */
width: 0;
height: 0;
}
&:after {
background-color: $white;
- border-radius: 50%; /* make a circle */
- content: '';
+ border-radius: 50%; /* make a circle */
+ content: "";
position: absolute;
top: 8px;
left: -4px;
@@ -276,7 +280,8 @@ input::-moz-focus-inner {
background-color: darken($colorSplash, 6%);
&:before {
- border-color: transparent darken($colorSplash, 6%) transparent transparent;
+ border-color: transparent darken($colorSplash, 6%) transparent
+ transparent;
}
}
}
@@ -288,7 +293,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;
@@ -332,7 +337,6 @@ input::-moz-focus-inner {
}
.x-window & {
-
.x-form-item-label {
padding: 10px 0 4px 0; /* move the form fields a bit tighter together inside windows */
}
@@ -352,7 +356,7 @@ input::-moz-focus-inner {
&.disabled {
label {
- color: scale-color($coreFieldLabelColor, $lightness: 50%);
+ color: scale-color($coreFieldLabelColor, $lightness: 50%);
}
}
@@ -374,8 +378,7 @@ input::-moz-focus-inner {
/* prevent columns used inside form elements to have too much spacing, some custom TV types need this */
.x-column-inner > .x-column {
-
- ~.x-column {
+ ~ .x-column {
margin-left: 5px;
}
@@ -409,7 +412,7 @@ input::-moz-focus-inner {
}
&.toggle-slider-above {
- margin: .3em 0;
+ margin: 0.3em 0;
padding-left: 3.9em;
}
@@ -421,15 +424,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 +442,7 @@ input::-moz-focus-inner {
}
.example-input,
.copy-this {
- padding: 0 .3em;
+ padding: 0 0.3em;
border-radius: 2px;
transition: width 1s;
}
@@ -477,7 +480,14 @@ input::-moz-focus-inner {
}
}
}
- }
+ &:active {
+ color: $darkGray;
+ &::after {
+ color: $darkGray;
+ }
+ }
+ }
+
.feedback {
margin-left: 1.4rem;
color: scale-color($blue, $lightness: -35%);
@@ -500,13 +510,12 @@ input::-moz-focus-inner {
.deemphasize {
font-style: normal;
}
-
}
.fs-toggle {
padding-top: 1em;
margin-top: 2em;
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
border-top: 1px dashed $borderColor;
}
@@ -565,7 +574,6 @@ input::-moz-focus-inner {
}
}
}
-
}
.x-form-field {
@@ -635,7 +643,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,
@@ -681,7 +689,6 @@ input::-moz-focus-inner {
content: fa-content($fa-var-file-code);
font-weight: 400;
}
-
}
&.x-datetime-wrap {
@@ -815,7 +822,7 @@ input::-moz-focus-inner {
padding-left: 3px;
&:before {
- content: '';
+ content: "";
}
}
@@ -823,7 +830,7 @@ input::-moz-focus-inner {
@extend %pseudo-font;
box-sizing: border-box;
- content: '';
+ content: "";
font-size: 18px;
padding-right: 3px;
position: absolute;
@@ -922,8 +929,7 @@ input::-moz-focus-inner {
.x-form-check-wrap,
.x-fieldset-checkbox-toggle legend,
.x-fieldset legend {
- [type="checkbox"]{
-
+ [type="checkbox"] {
position: absolute;
left: -9999px;
html[dir="rtl"] & {
@@ -931,11 +937,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;
@@ -943,9 +949,9 @@ input::-moz-focus-inner {
&:before,
&:after {
- content: '';
+ content: "";
position: absolute;
- transition: all .2s ease;
+ transition: all 0.2s ease;
font-size: inherit;
}
@@ -962,7 +968,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 +978,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 +993,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 +1003,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;
@@ -1033,12 +1036,11 @@ input::-moz-focus-inner {
}
/* applies to new xcheckboxgroup custom checkbox group */
&.aggregated-group {
- padding-left: 1em;
- padding-right: 1em;
+ padding-left: 1em;
+ padding-right: 1em;
}
}
-
/* superboxselect / multi-select field */
.x-superboxselect {
height: auto !important; /* override the extjs default theme style of 18px */
@@ -1088,13 +1090,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%;
@@ -1177,7 +1179,7 @@ input::-moz-focus-inner {
margin-bottom: 2px;
}
- input[type=text],
+ input[type="text"],
textarea {
background-color: $coreFieldBg;
background-image: none;
@@ -1187,7 +1189,7 @@ input::-moz-focus-inner {
width: 97%;
}
- input[type=text] {
+ input[type="text"] {
font-size: 13px;
height: 20px !important;
padding: 5px;
@@ -1225,21 +1227,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%;
-}
-
-.x-grid-editor .x-form-field-wrap input {
- background-color: transparent !important;
+ background-color: $white;
}
-.x-grid-editor .x-form-field-wrap img {
- background-color: $white;
- background-image: url($imgPath + 'modx-theme/form/trigger.png');
+.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;
+ }
+ img {
+ background-color: $white;
+ background-image: url($imgPath+"modx-theme/form/trigger.png");
+ }
+ }
}
.x-form-grow-sizer {
@@ -1269,7 +1272,6 @@ input::-moz-focus-inner {
.x-grid3 {
.x-small-editor {
-
.x-form-text,
.x-form-field-wrap {
font: $fontSmall;
@@ -1373,7 +1375,7 @@ input::-moz-focus-inner {
.x-btn {
padding: 1px;
- transition: color .25s;
+ transition: color 0.25s;
&.x-btn-over,
&:hover,
@@ -1388,7 +1390,7 @@ input::-moz-focus-inner {
&.x-item-disabled {
color: $buttonColor;
- opacity: .4;
+ opacity: 0.4;
}
button:before {
@@ -1432,7 +1434,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 +1450,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 +1467,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;
@@ -1501,7 +1515,7 @@ input::-moz-focus-inner {
}
.x-combo-list-hd {
- background-image: url($imgPath + 'modx-theme/layout/panel-title-light-bg.gif');
+ background-image: url($imgPath+"modx-theme/layout/panel-title-light-bg.gif");
border-bottom-color: #bcbcbc;
color: #464646;
}
@@ -1548,18 +1562,18 @@ 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;
box-sizing: border-box;
color: $colorSplash;
- content: '';
+ content: "";
font-size: 18px;
position: absolute;
top: 0;
@@ -1788,7 +1802,7 @@ td.x-date-mp-sep {
border-radius: $borderRadius;
background-color: $coreFieldBg;
border: 1px solid $borderColor;
- background: url('../images/tp-no-preview.png') no-repeat center center;
+ background: url("../images/tp-no-preview.png") no-repeat center center;
overflow: hidden;
.x-panel-bwrap,
@@ -1809,6 +1823,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..c5d51c8c66f 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%);
+ }
+}
diff --git a/_build/templates/default/sass/index.scss b/_build/templates/default/sass/index.scss
index 67a987c5d60..574515ba63b 100644
--- a/_build/templates/default/sass/index.scss
+++ b/_build/templates/default/sass/index.scss
@@ -161,8 +161,8 @@ hr {
}
.wait {
- background: transparent url($imgPath + "style/wait.gif") no-repeat scroll
- center 55px;
+ background: transparent url($imgPath+"style/wait.gif") no-repeat scroll center
+ 55px;
color: $darkestGray;
font-size: 15px;
font-weight: bold;
@@ -443,7 +443,7 @@ textarea.x-form-field {
bottom: 0;
display: block;
content: " ";
- background: transparent url($imgPath + "restyle/dragndrop.svg") no-repeat
+ background: transparent url($imgPath+"restyle/dragndrop.svg") no-repeat
center;
background-size: 50% 50%;
opacity: 0.1;
@@ -476,7 +476,7 @@ textarea.x-form-field {
}
#modx-panel-packages.drag-n-drop:before {
- background: transparent url($imgPath + "restyle/dragndrop.svg") no-repeat top;
+ background: transparent url($imgPath+"restyle/dragndrop.svg") no-repeat top;
background-size: 50% 30%;
z-index: 0;
}
@@ -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,11 @@ 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;
+ }
}
.x-editable-column {
@@ -910,6 +915,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;
}
@@ -923,8 +959,8 @@ a.x-grid-link:focus {
}
.ux-row-action-item span {
- background: transparent url($imgPath + "style/go-next.png") no-repeat scroll
- 1px 4px;
+ background: transparent url($imgPath+"style/go-next.png") no-repeat scroll 1px
+ 4px;
display: inline !important;
line-height: 24px;
margin: 0 5px;
@@ -933,22 +969,22 @@ a.x-grid-link:focus {
}
.icon-uninstall span {
- background: url($imgPath + "style/delete.png") no-repeat scroll 1px 4px
+ background: url($imgPath+"style/delete.png") no-repeat scroll 1px 4px
transparent;
}
.package-details span {
- background: url($imgPath + "style/info.png") no-repeat scroll 1px 4px
+ background: url($imgPath+"style/info.png") no-repeat scroll 1px 4px
transparent;
}
.package-download span {
- background: url($imgPath + "style/download.png") no-repeat scroll 1px 4px
+ background: url($imgPath+"style/download.png") no-repeat scroll 1px 4px
transparent;
}
.package-installed span {
- background: url($imgPath + "style/accept.png") no-repeat scroll 1px 4px
+ background: url($imgPath+"style/accept.png") no-repeat scroll 1px 4px
transparent;
}
@@ -1182,7 +1218,7 @@ a.x-grid-link:focus {
}
.x-rbtn td {
- background-image: url($imgPath + "restyle/icons/rbtn.gif");
+ background-image: url($imgPath+"restyle/icons/rbtn.gif");
background-repeat: no-repeat;
border: 0 none;
height: 21px;
@@ -1418,38 +1454,36 @@ iframe[classname="x-hidden"] {
/* file upload, is this the old legacy (single file) uploader? */
.ext-ux-uploaddialog-addbtn {
- background: url($imgPath + "restyle/fileup/file-add.gif") no-repeat left
- center !important;
+ background: url($imgPath+"restyle/fileup/file-add.gif") no-repeat left center !important;
}
.ext-ux-uploaddialog-removebtn {
- background: url($imgPath + "restyle/fileup/file-remove.gif") no-repeat left
+ background: url($imgPath+"restyle/fileup/file-remove.gif") no-repeat left
center !important;
}
.ext-ux-uploaddialog-resetbtn {
- background: url($imgPath + "restyle/fileup/reset.gif") no-repeat left center !important;
+ background: url($imgPath+"restyle/fileup/reset.gif") no-repeat left center !important;
}
.ext-ux-uploaddialog-uploadstartbtn {
- background: url($imgPath + "restyle/fileup/upload-start.gif") no-repeat left
+ background: url($imgPath+"restyle/fileup/upload-start.gif") no-repeat left
center !important;
}
.ext-ux-uploaddialog-uploadstopbtn {
- background: url($imgPath + "restyle/fileup/upload-stop.gif") no-repeat left
+ background: url($imgPath+"restyle/fileup/upload-stop.gif") no-repeat left
center !important;
}
.ext-ux-uploaddialog-indicator-stoped {
- background: url($imgPath + "restyle/fileup/done.gif") no-repeat center center;
+ background: url($imgPath+"restyle/fileup/done.gif") no-repeat center center;
height: 16px;
width: 16px;
}
.ext-ux-uploaddialog-indicator-processing {
- background: url($imgPath + "restyle/fileup/loading.gif") no-repeat center
- center;
+ background: url($imgPath+"restyle/fileup/loading.gif") no-repeat center center;
height: 16px;
width: 16px;
}
@@ -1461,19 +1495,19 @@ iframe[classname="x-hidden"] {
}
.ext-ux-uploaddialog-state-0 {
- background-image: url($imgPath + "restyle/fileup/uncheck.gif");
+ background-image: url($imgPath+"restyle/fileup/uncheck.gif");
}
.ext-ux-uploaddialog-state-1 {
- background-image: url($imgPath + "restyle/fileup/check.gif");
+ background-image: url($imgPath+"restyle/fileup/check.gif");
}
.ext-ux-uploaddialog-state-2 {
- background-image: url($imgPath + "restyle/fileup/failed.gif");
+ background-image: url($imgPath+"restyle/fileup/failed.gif");
}
.ext-ux-uploaddialog-state-3 {
- background-image: url($imgPath + "restyle/fileup/file-uploading.gif");
+ background-image: url($imgPath+"restyle/fileup/file-uploading.gif");
}
/* tree grid */
@@ -1883,6 +1917,12 @@ iframe[classname="x-hidden"] {
user-select: text !important;
}
+.x-selectable {
+ &.simulated-link * {
+ @include textLink;
+ }
+}
+
/* Lightbox */
#ux-lightbox {
left: 0;
@@ -1915,7 +1955,7 @@ iframe[classname="x-hidden"] {
}
#ux-lightbox-loading {
- background: url($imgPath + "style/loading.gif") no-repeat scroll center 15%
+ background: url($imgPath+"style/loading.gif") no-repeat scroll center 15%
transparent;
height: 25%;
left: 0;
@@ -2004,7 +2044,7 @@ iframe[classname="x-hidden"] {
}
#ux-lightbox-data #ux-lightbox-navClose {
- background: transparent url($imgPath + "style/close.png") no-repeat scroll 0 0;
+ background: transparent url($imgPath+"style/close.png") no-repeat scroll 0 0;
float: right;
height: 16px;
outline: medium none;
diff --git a/core/lexicon/en/context.inc.php b/core/lexicon/en/context.inc.php
index 7352df973c2..84d25abc900 100644
--- a/core/lexicon/en/context.inc.php
+++ b/core/lexicon/en/context.inc.php
@@ -6,23 +6,31 @@
* @package modx
* @subpackage lexicon
*/
+$_lang['_context_manager_description'] = 'The default manager or administration context for content management activity.';
+$_lang['_context_manager_name'] = 'Manager';
+$_lang['_context_website_description'] = 'The default front-end context for your website.';
+$_lang['_context_website_name'] = 'Website';
$_lang['context'] = 'Context';
$_lang['context_add'] = 'Add Context';
$_lang['context_data'] = 'Context Data';
+$_lang['context_edit'] = 'Edit the settings and User Group access for this Context';
$_lang['context_err_ae'] = 'A Context with that name already exists.';
$_lang['context_err_create'] = 'An error occurred while creating the Context.';
$_lang['context_err_duplicate'] = 'An error occurred while trying to duplicate the Context.';
$_lang['context_err_load_data'] = 'Error loading context data.';
+$_lang['context_err_name_reserved'] = 'The context name “[[+reservedName]]” is reserved. Please choose another name.';
$_lang['context_err_nf'] = 'Context not found!';
$_lang['context_err_nfs'] = 'Context not found with key: [[+key]]';
$_lang['context_err_ns'] = 'Context not specified.';
$_lang['context_err_ns_key'] = 'Please specify a valid key for the Context.';
+$_lang['context_err_ns_name'] = 'Please specify a valid name for the Context.';
$_lang['context_err_remove'] = 'An error occurred while trying to delete the Context.';
$_lang['context_err_reserved'] = 'The Context key you chose is reserved for system use only. Please specify a different key.';
$_lang['context_err_save'] = 'An error occurred while saving the Context.';
$_lang['context_id'] = 'Ctx ID';
$_lang['context_key'] = 'Context Key';
$_lang['context_management_message'] = 'Manage site Contexts.';
+$_lang['context_reserved_general_desc'] = 'Note that this is a protected, built-in Context. The values shown below are for informational purposes only. Its settings and assigned User Group(s) are, however, editable by users with the appropriate permissions.';
$_lang['context_settings'] = 'Context Settings';
$_lang['context_settings_desc'] = 'Here you can set settings specific to this Context. Context settings will override any System Settings with the same key. 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..9b0f9277ecb 100644
--- a/core/lexicon/en/dashboards.inc.php
+++ b/core/lexicon/en/dashboards.inc.php
@@ -6,10 +6,14 @@
* @subpackage lexicon
* @language en
*/
+$_lang['_dashboards_default_name'] = 'Default';
+$_lang['_dashboards_default_description'] = 'The built-in MODX dashboard';
+
$_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_edit'] = 'Edit the settings and Widget placements for this Dashboard';
$_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';
diff --git a/core/lexicon/en/default.inc.php b/core/lexicon/en/default.inc.php
index 58145332f7f..8f2386dee40 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.';
@@ -216,6 +218,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';
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..0415f255264 100644
--- a/core/lexicon/en/policy.inc.php
+++ b/core/lexicon/en/policy.inc.php
@@ -6,6 +6,89 @@
* @package modx
* @subpackage lexicon
*/
+
+ // Reserved Policies
+$_lang['_policy_administrator_description'] = 'Context administration policy with all permissions.';
+$_lang['_policy_administrator_name'] = 'Administrator';
+
+$_lang['_policy_developer_description'] = 'Context administration policy with most Permissions except Administrator and Security functions.';
+$_lang['_policy_developer_name'] = 'Developer';
+
+$_lang['_policy_content_editor_description'] = 'Context administration policy with limited, content-editing related Permissions, but no publishing.';
+$_lang['_policy_content_editor_name'] = 'Content Editor';
+
+$_lang['_policy_context_description'] = 'A standard Context policy that you can apply when creating Context ACLs for basic read/write and view_unpublished access within a Context.';
+$_lang['_policy_context_name'] = 'Context';
+
+$_lang['_policy_element_description'] = 'MODX Element policy with all attributes.';
+$_lang['_policy_element_name'] = 'Element';
+
+$_lang['_policy_hidden_namespace_description'] = 'Hidden Namespace policy, will not show Namespace in lists.';
+$_lang['_policy_hidden_namespace_name'] = 'Hidden Namespace';
+
+$_lang['_policy_load_list_view_description'] = 'Provides load, list and view permissions only.';
+$_lang['_policy_load_list_view_name'] = 'Load, List and View';
+
+$_lang['_policy_load_only_description'] = 'A minimal policy with permission to load an object.';
+$_lang['_policy_load_only_name'] = 'Load Only';
+
+$_lang['_policy_media_source_admin_description'] = 'Media Source administration policy.';
+$_lang['_policy_media_source_admin_name'] = 'Media Source Admin';
+
+$_lang['_policy_media_source_user_description'] = 'Media Source user policy, with basic viewing and using - but no editing - of Media Sources.';
+$_lang['_policy_media_source_user_name'] = 'Media Source User';
+
+$_lang['_policy_object_description'] = 'An Object policy with all permissions.';
+$_lang['_policy_object_name'] = 'Object';
+
+$_lang['_policy_resource_description'] = 'MODX Resource Policy with all attributes.';
+$_lang['_policy_resource_name'] = 'Resource';
+
+// Reserved Policy Templates
+$_lang['_policytemplate_administrator_template_description'] = 'Context administration policy template with all permissions.';
+$_lang['_policytemplate_administrator_template_name'] = 'AdministratorTemplate';
+
+$_lang['_policytemplate_context_template_description'] = 'Context Policy Template with all attributes.';
+$_lang['_policytemplate_context_template_name'] = 'ContextTemplate';
+
+$_lang['_policytemplate_element_template_description'] = 'Element Policy Template with all attributes.';
+$_lang['_policytemplate_element_template_name'] = 'ElementTemplate';
+
+$_lang['_policytemplate_media_source_template_description'] = 'Media Source Policy Template with all attributes.';
+$_lang['_policytemplate_media_source_template_name'] = 'MediaSourceTemplate';
+
+$_lang['_policytemplate_namespace_template_description'] = 'Namespace Policy Template with all attributes.';
+$_lang['_policytemplate_namespace_template_name'] = 'NamespaceTemplate';
+
+$_lang['_policytemplate_object_template_description'] = 'Object Policy Template with all attributes.';
+$_lang['_policytemplate_object_template_name'] = 'ObjectTemplate';
+
+$_lang['_policytemplate_resource_template_description'] = 'Resource Policy Template with all attributes.';
+$_lang['_policytemplate_resource_template_name'] = 'ResourceTemplate';
+
+// Reserved Template Groups
+$_lang['_templategroup_administrator_description'] = 'All admin policy templates.';
+$_lang['_templategroup_administrator_name'] = 'Administrator';
+
+$_lang['_templategroup_context_description'] = 'All Context based policy templates.';
+$_lang['_templategroup_context_name'] = 'Context';
+
+$_lang['_templategroup_element_description'] = 'All Element-based policy templates.';
+$_lang['_templategroup_element_name'] = 'Element';
+
+$_lang['_templategroup_mediasource_description'] = 'All Media Source-based policy templates.';
+$_lang['_templategroup_mediasource_name'] = 'Media Source';
+
+$_lang['_templategroup_namespace_description'] = 'All Namespace based policy templates.';
+$_lang['_templategroup_namespace_name'] = 'Namespace';
+
+$_lang['_templategroup_object_description'] = 'All Object-based policy templates.';
+$_lang['_templategroup_object_name'] = 'Object';
+
+$_lang['_templategroup_resource_description'] = 'All Resource-based policy templates.';
+$_lang['_templategroup_resource_name'] = 'Resource';
+
+// General
$_lang['template_group'] = 'Template Group';
$_lang['active_of'] = '[[+active]] of [[+total]]';
$_lang['active_permissions'] = 'Active Permissions';
@@ -28,6 +111,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 +131,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,33 +146,53 @@
$_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.';
-$_lang['policy_template_resource_desc'] = 'Resource Policy Template with all attributes.';
-$_lang['policy_template_object_desc'] = 'Object Policy Template with all attributes.';
-$_lang['policy_template_element_desc'] = 'Element Policy Template with all attributes.';
-$_lang['policy_template_mediasource_desc'] = 'Media Source Policy Template with all attributes.';
-$_lang['policy_template_context_desc'] = 'Context Policy Template with all attributes.';
-$_lang['policy_template_namespace_desc'] = 'Namespace Policy Template with all attributes.';
-$_lang['policy_template_group_administrator_desc'] = 'All admin policy templates.';
-$_lang['policy_template_group_object_desc'] = 'All Object-based policy templates.';
-$_lang['policy_template_group_resource_desc'] = 'All Resource-based policy templates.';
-$_lang['policy_template_group_element_desc'] = 'All Element-based policy templates.';
-$_lang['policy_template_group_mediasource_desc'] = 'All Media Source-based policy templates.';
-$_lang['policy_template_group_namespace_desc'] = 'All Namespace based policy templates.';
-$_lang['policy_template_group_context_desc'] = 'All Context based policy templates.';
-$_lang['policy_resource_desc'] = 'MODX Resource Policy with all attributes.';
-$_lang['policy_administrator_desc'] = 'Context administration policy with all permissions.';
-$_lang['policy_load_only_desc'] = 'A minimal policy with permission to load an object.';
-$_lang['policy_load_list_and_view_desc'] = 'Provides load, list and view permissions only.';
-$_lang['policy_object_desc'] = 'An Object policy with all permissions.';
-$_lang['policy_element_desc'] = 'MODX Element policy with all attributes.';
-$_lang['policy_content_editor_desc'] = 'Context administration policy with limited, content-editing related Permissions, but no publishing.';
-$_lang['policy_media_source_admin_desc'] = 'Media Source administration policy.';
-$_lang['policy_media_source_user_desc'] = 'Media Source user policy, with basic viewing and using - but no editing - of Media Sources.';
-$_lang['policy_developer_desc'] = 'Context administration policy with most Permissions except Administrator and Security functions.';
-$_lang['policy_context_desc'] = 'A standard Context policy that you can apply when creating Context ACLs for basic read/write and view_unpublished access within a Context.';
-$_lang['policy_hidden_namespace_desc'] = 'Hidden Namespace policy, will not show Namespace in lists.';
$_lang['policy_count'] = 'Policy Count';
$_lang['policy_query_name_only'] = 'Search by Name only';
+
+// Deprecated keys, keep for BC until ...
+$_lang['policy_administrator_desc'] = $_lang['_policy_administrator_description'];
+$_lang['policy_context_desc'] = $_lang['_policy_context_description'];
+$_lang['policy_content_editor_desc'] = $_lang['_policy_content_editor_description'];
+$_lang['policy_developer_desc'] = $_lang['_policy_developer_description'];
+$_lang['policy_element_desc'] = $_lang['_policy_element_description'];
+$_lang['policy_hidden_namespace_desc'] = $_lang['_policy_hidden_namespace_description'];
+$_lang['policy_load_list_and_view_desc'] = $_lang['_policy_load_list_view_description'];
+$_lang['policy_load_only_desc'] = $_lang['_policy_load_only_description'];
+$_lang['policy_media_source_admin_desc'] = $_lang['_policy_media_source_admin_description'];
+$_lang['policy_media_source_user_desc'] = $_lang['_policy_media_source_user_description'];
+$_lang['policy_object_desc'] = $_lang['_policy_object_description'];
+$_lang['policy_resource_desc'] = $_lang['_policy_resource_description'];
+
+$_lang['policy_template_administrator_desc'] = $_lang['_policytemplate_administrator_template_description'];
+$_lang['policy_template_context_desc'] = $_lang['_policytemplate_context_template_description'];
+$_lang['policy_template_element_desc'] = $_lang['_policytemplate_element_template_description'];
+$_lang['policy_template_mediasource_desc'] = $_lang['_policytemplate_media_source_template_description'];
+$_lang['policy_template_namespace_desc'] = $_lang['_policytemplate_namespace_template_description'];
+$_lang['policy_template_object_desc'] = $_lang['_policytemplate_object_template_description'];
+$_lang['policy_template_resource_desc'] = $_lang['_policytemplate_resource_template_description'];
+
+// Temporary keys needed before future change of Policy Template names (adding spaces to allow translation)
+$_lang['_policytemplate_administratortemplate_description'] = $_lang['_policytemplate_administrator_template_description'];
+$_lang['_policytemplate_contexttemplate_description'] = $_lang['_policytemplate_context_template_description'];
+$_lang['_policytemplate_elementtemplate_description'] = $_lang['_policytemplate_element_template_description'];
+$_lang['_policytemplate_mediasourcetemplate_description'] = $_lang['_policytemplate_media_source_template_description'];
+$_lang['_policytemplate_namespacetemplate_description'] = $_lang['_policytemplate_namespace_template_description'];
+$_lang['_policytemplate_objecttemplate_description'] = $_lang['_policytemplate_object_template_description'];
+$_lang['_policytemplate_resourcetemplate_description'] = $_lang['_policytemplate_resource_template_description'];
+
+$_lang['policy_template_group_administrator_desc'] = $_lang['_templategroup_administrator_description'];
+$_lang['policy_template_group_context_desc'] = $_lang['_templategroup_context_description'];
+$_lang['policy_template_group_element_desc'] = $_lang['_templategroup_element_description'];
+$_lang['policy_template_group_mediasource_desc'] = $_lang['_templategroup_mediasource_description'];
+$_lang['policy_template_group_namespace_desc'] = $_lang['_templategroup_namespace_description'];
+$_lang['policy_template_group_object_desc'] = $_lang['_templategroup_object_description'];
+$_lang['policy_template_group_resource_desc'] = $_lang['_templategroup_resource_description'];
diff --git a/core/lexicon/en/source.inc.php b/core/lexicon/en/source.inc.php
index 13a1f4932aa..8303bcccfab 100644
--- a/core/lexicon/en/source.inc.php
+++ b/core/lexicon/en/source.inc.php
@@ -6,6 +6,8 @@
* @package modx
* @subpackage lexicon
*/
+$_lang['_source_filesystem_description'] = 'The default manager source containing all files this installation of MODX has access to.';
+$_lang['_source_filesystem_name'] = 'Filesystem';
$_lang['access'] = 'Access Permissions';
$_lang['base_path'] = 'Base Path';
$_lang['base_path_relative'] = 'Base Path Relative?';
@@ -20,7 +22,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 +34,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/user.inc.php b/core/lexicon/en/user.inc.php
index 984b0326fe0..d5d7f618c80 100644
--- a/core/lexicon/en/user.inc.php
+++ b/core/lexicon/en/user.inc.php
@@ -6,6 +6,11 @@
* @package modx
* @subpackage lexicon
*/
+$_lang['_role_member_description'] = 'The lowest-authority role, usually a user of the site but not of the manager.';
+$_lang['_role_member_name'] = 'Member';
+$_lang['_role_super_user_description'] = 'The highest-authority role, for manager users with complete control over all aspects of the site.';
+$_lang['_role_super_user_name'] = 'Super User';
+
$_lang['active'] = 'Active';
$_lang['address'] = 'Address';
$_lang['administrator'] = 'Administrator';
@@ -41,11 +46,12 @@
$_lang['role_err_ae'] = 'A role already exists with that name.';
$_lang['role_err_duplicate'] = 'An error occurred while duplicating the role.';
$_lang['role_err_has_users'] = 'There are users with this role. It cannot be deleted.';
+$_lang['role_err_name_reserved'] = 'The role name “[[+reservedName]]” is reserved. Please choose another name.';
$_lang['role_err_nf'] = 'Role not found.';
$_lang['role_err_nfs'] = 'Role not found with id: [[+role]]';
$_lang['role_err_ns'] = 'Role not specified!';
$_lang['role_err_ns_authority'] = 'Please specify an authority level for this role.';
-$_lang['role_err_ns_name'] = 'Please specify a name for the role.';
+$_lang['role_err_ns_name'] = 'Please specify a name for this role.';
$_lang['role_err_remove'] = 'An error occurred while trying to delete the role.';
$_lang['role_err_remove_admin'] = 'The role you are trying to delete is the admin role. This role cannot be deleted!';
$_lang['role_remove'] = 'Delete Role';
@@ -75,6 +81,7 @@
$_lang['user_country'] = 'Country';
$_lang['user_dob'] = 'Date of birth';
$_lang['user_doesnt_exist'] = 'User does not exist';
+$_lang['user_edit_account'] = 'Edit User’s Account';
$_lang['user_edit_self_msg'] = 'You may need to log out and log in again after saving to fully update your information.
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 +185,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';
diff --git a/core/src/Revolution/Processors/Context/Get.php b/core/src/Revolution/Processors/Context/Get.php
index 624c8a77086..56f489e0af0 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);
+ $limitTo = [];
+ if ($contextKey === $this->classKey::CONTEXT_DEFAULT) {
+ if ($contextData['name'] === $this->classKey::CONTEXT_DEFAULT_NAME) {
+ $limitTo[] = 'name';
+ }
+ if (empty($contextData['description'])) {
+ $limitTo[] = 'description';
+ }
+ }
+ $this->modx->lexicon->setTranslatedCoreDescriptors($contextData, 'context', 'name', $limitTo);
+ foreach ($contextData as $key => $value) {
+ $this->object->set($key, $value);
+ }
+ }
+ }
}
diff --git a/core/src/Revolution/Processors/Context/GetList.php b/core/src/Revolution/Processors/Context/GetList.php
index 56aafadbe55..b1ac4d2ed4b 100644
--- a/core/src/Revolution/Processors/Context/GetList.php
+++ b/core/src/Revolution/Processors/Context/GetList.php
@@ -1,12 +1,12 @@
setDefaultProperties([
- 'query' => '',
- 'exclude' => '',
+ 'search' => '',
+ 'exclude' => 'creator'
]);
+ $this->isGridFilter = $this->getProperty('isGridFilter', false);
+
$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 +78,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 +152,43 @@ 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';
+ $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);
+
+ if ($isCoreContext) {
+ $limitTo = [];
+ if ($contextKey === $this->classKey::CONTEXT_DEFAULT) {
+ if ($contextData['name'] === $this->classKey::CONTEXT_DEFAULT_NAME) {
+ $limitTo[] = 'name';
+ }
+ if (empty($contextData['description'])) {
+ $limitTo[] = 'description';
+ }
+ }
+ $this->modx->lexicon->setTranslatedCoreDescriptors($contextData, 'context', 'name', $limitTo);
}
- if (!in_array($object->get('key'), $this->classKey::RESERVED_KEYS) && $this->canRemove) {
- $contextArray['perm'][] = 'premove';
+
+ $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/Security/Access/Policy/GetList.php b/core/src/Revolution/Processors/Security/Access/Policy/GetList.php
index 94c420f6b5b..2e1b584ceb8 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,64 @@ 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');
+ $isCorePolicy = $object->isCorePolicy($policyName);
+ $this->setActivePermissionsCount($policyData, $object->get('data'));
- $policy['cls'] = $this->prepareRowClasses($object);
+ if ($isCorePolicy) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($policyData, 'policy');
+ }
+ if (in_array($policyData['template_name'], $this->corePolicyTemplates)) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($policyData, 'policytemplate', 'name:template_name', true);
+ }
+
+ $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;
+ unset($policyData['data']);
- $permissions = [];
+ return $policyData;
+ }
+
+ 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
* @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..74732c29135 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,42 @@ public function prepareQueryAfterCount(xPDOQuery $c)
}
/**
- * @param xPDOObject $object
+ * @param xPDOObject|modAccessPolicyTemplate $object
* @return array
*/
public function prepareRow(xPDOObject $object)
{
- $template = $object->toArray();
+ $permissions = [
+ 'create' => $this->canCreate,
+ 'duplicate' => $this->canCreate,
+ 'update' => $this->canEdit,
+ 'delete' => $this->canRemove
+ ];
+ $templateData = $object->toArray();
+ $templateName = $object->get('name');
+ $isCoreTemplate = $object->isCoreTemplate($templateName);
+
+ if ($isCoreTemplate) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($templateData, 'policytemplate');
+ }
+ if (in_array($templateData['template_group_name'], $this->corePolicyTemplateGroups)) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($templateData, 'templategroup', 'name:template_group_name', ['name']);
+ }
- $template['description_trans'] = $this->modx->lexicon($template['description']);
- $template['cls'] = $this->prepareRowClasses($object);
+ $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;
- return $template;
+ return $templateData;
}
/**
* @param xPDOObject|modAccessPolicyTemplate $object
- *
+ * @deprecated
* @return string
*/
protected function prepareRowClasses(xPDOObject $object)
diff --git a/core/src/Revolution/Processors/Security/Role/GetList.php b/core/src/Revolution/Processors/Security/Role/GetList.php
index 69693afbed5..555585fe9fb 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,33 @@ 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 ($isCoreRole) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($roleData, 'role:user');
+ } elseif ($this->isAssigned($roleId)) {
+ $roleData['isAssigned'] = 1;
}
- $objectArray['perm'] = implode(' ', $perm);
+ $roleData['reserved'] = ['name' => $this->coreRoles];
+ $roleData['isProtected'] = $isCoreRole;
+ $roleData['creator'] = $isCoreRole ? 'modx' : strtolower($this->modx->lexicon('user')) ;
+ $roleData['permissions'] = !$isCoreRole ? $permissions : [] ;
- return $objectArray;
+ return $roleData;
}
}
diff --git a/core/src/Revolution/Processors/Source/GetList.php b/core/src/Revolution/Processors/Source/GetList.php
index 33cd1fbf571..d50da5063cf 100644
--- a/core/src/Revolution/Processors/Source/GetList.php
+++ b/core/src/Revolution/Processors/Source/GetList.php
@@ -36,6 +36,12 @@ 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 $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,36 @@ 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);
+
+ if ($isCoreSource) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($sourceData, 'source');
+ }
+
+ $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'];
- }
-
- $cls = [];
- if ($canSave && $canEdit && $object->checkPolicy('save')) {
- $cls[] = 'pupdate';
+ $sourceData['iconCls'] = $props['iconCls'];
}
- 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..341d9dc03a7 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/Dashboard/GetList.php b/core/src/Revolution/Processors/System/Dashboard/GetList.php
index 199b1b00913..9832cac8f21 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,33 @@ 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);
+
+ if ($isCoreDashboard) {
+ $this->modx->lexicon->setTranslatedCoreDescriptors($dashboardData, 'dashboards');
+ }
+
+ $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/Workspace/PackageNamespace/GetList.php b/core/src/Revolution/Processors/Workspace/PackageNamespace/GetList.php
index dd2e7297c62..8e0255a4993 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;
@@ -40,6 +41,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 $canRemove = 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' => ''
+ 'search' => false,
+ '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 = $this->getExtrasNamespaces();
+
return $initialized;
}
@@ -168,18 +193,72 @@ public function prepareQueryAfterCount(xPDOQuery $c)
return $c;
}
+ public function getExtrasNamespaces()
+ {
+ $namespaceList = [];
+
+ $c = $this->modx->newQuery(modTransportPackage::class);
+ $c->select([
+ 'name' => 'DISTINCT SUBSTRING_INDEX(`signature`,"-",1)'
+ ]);
+ $namespaces = $this->modx->getIterator(modTransportPackage::class, $c);
+ $namespaces->rewind();
+ if ($namespaces->valid()) {
+ foreach ($namespaces as $namespace) {
+ $namespaceList[] = $namespace->get('name');
+ }
+ }
+ return $namespaceList;
+ }
+
/**
* 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'] = 'extra';
+ break;
+ case $isCoreNamespace:
+ $namespaceData['creator'] = 'modx';
+ break;
+ default:
+ $namespaceData['creator'] = 'user';
+ $namespaceData['isProtected'] = false;
+ }
+ // 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/Sources/modMediaSource.php b/core/src/Revolution/Sources/modMediaSource.php
index fd01c5fd39f..7c9bdb6491a 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,18 +2077,12 @@ protected function getAllowedExtensionsArray($properties = [])
/**
* @param array $properties
*
- * @return array|mixed|string
+ * @return array
*/
protected function getSkipExtensionsArray($properties = [])
{
$skipExtensions = $this->getOption('skipExtensions', $properties, '');
- if (empty($skipExtensions)) {
- $skipExtensions = [];
- } else {
- $skipExtensions = explode(',', $skipExtensions);
- }
-
- return !empty($skipExtensions) ? explode(',', $skipExtensions) : [];
+ return $skipExtensions ? explode(',', $skipExtensions) : [];
}
@@ -2450,4 +2459,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..83720f8ab3f 100644
--- a/core/src/Revolution/modAccessPolicyTemplate.php
+++ b/core/src/Revolution/modAccessPolicyTemplate.php
@@ -3,6 +3,7 @@
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
@@ -16,17 +17,19 @@
* @property modAccessPermission[] $Permissions
* @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/modContext.php b/core/src/Revolution/modContext.php
index a86ca5c44e9..7368de82112 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,24 @@ 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)),
- ]))) {
+ $cacheKey = $this->xpdo->getOption('cache_context_settings_key', null, 'context_settings');
+ $cacheHandler = $this->xpdo->getOption(
+ 'cache_context_settings_handler',
+ null,
+ $this->xpdo->getOption(xPDO::OPT_CACHE_HANDLER, null, 'xPDO\Cache\xPDOFileCache')
+ );
+ $cacheFormat = (int)$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 => $cacheKey,
+ xPDO::OPT_CACHE_HANDLER => $cacheHandler,
+ xPDO::OPT_CACHE_FORMAT => $cacheFormat
+ ]))
+ ) {
$context = $this->xpdo->cacheManager->generateContext($this->get('key'), $options);
}
if (!empty($context)) {
@@ -202,9 +215,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 +297,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 +493,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/modLexicon.php b/core/src/Revolution/modLexicon.php
index c3d6f34d956..1e1202bc259 100644
--- a/core/src/Revolution/modLexicon.php
+++ b/core/src/Revolution/modLexicon.php
@@ -62,6 +62,8 @@ class modLexicon
*/
protected $_loadedTopics = [];
+ protected array $config;
+
/**
* Creates the modLexicon instance.
*
@@ -672,6 +674,14 @@ public function getFilterLanguageList($namespace = 'core', $topic = '')
public function process($key, array $params = [], $language = '')
{
$language = !empty($language) ? $language : $this->modx->getOption('cultureKey', null, 'en');
+ // if (strpos($key, 'name') || strpos($key, 'descr')) {
+ // $this->modx->log(
+ // \modX::LOG_LEVEL_ERROR,
+ // "\r\t process:
+ // \t\$language: {$language}
+ // \t\$this->_lexicon: " . print_r($this->_lexicon, true)
+ // );
+ // }
/* make sure key exists */
if (!is_string($key) || !isset($this->_lexicon[$language][$key])) {
$this->modx->log(xPDO::LOG_LEVEL_DEBUG, 'Language string not found: "' . $key . '"');
@@ -794,4 +804,127 @@ public function clear($language = '')
$this->_lexicon = [$this->modx->getOption('cultureKey', null, 'en') => []];
}
}
+
+ /**
+ * Evaluates and sets a built-in, core object's language-specific name and, optionally, description
+ * @param array $objectData A reference to the data being prepared for output
+ * @param string $objectSpec Identifies the type of object being worked with and
+ * optionally an alternate Lexicon topic specification (used when the object type
+ * does not match the Lexicon topic), i.e.:
+ * * Access Policy = 'policy'
+ * * Context = 'context'
+ * * Media Source = 'source'
+ * * Namespace = 'namespace'
+ * * Policy Template = 'policytemplate:policy'
+ * * Template Group = 'templategroup:policy'
+ * * Role = 'role:user'
+ * * etc...
+ * @param string $namingKey The database column (and optional alias) containing the name of the object.
+ * For example, pass in
+ * * 'name' - when there is no alias needed (only one object with a db column 'name' is being processed)
+ * * 'name:template_name' - when the object being processed is a secondary one having the same
+ * db column name as the primary one, thus requiring use of its alias of 'template_name' to avoid conflicts
+ * @param array $limitToFields Transforms only the field(s) given in the given list
+ */
+ public function setTranslatedCoreDescriptors(array &$objectData, string $objectSpec, string $namingKey = 'name', array $limitToFields = []): void
+ {
+ $useTranslation = $this->modx->getOption('cultureKey') !== 'en';
+ $fallbackTopic = $objectSpec;
+ /*
+ In a couple instances, more than one core object type is shown on/in the same page/grid.
+ For example, the Access Policies grid also displays the associated Policy Template. In
+ these cases, where both objects' db columns are "name," aliased names are used to differentiate
+ them (e.g., "template_name" is the alias for Policy Templates shown in the Policies grid). However,
+ all generated lexicon keys for name fields should end with their db column name (usually "_name").
+ */
+ if (strpos($namingKey, ':') > 0) {
+ $keys = explode(':', $namingKey);
+ $valueKey = $keys[1];
+ $nameKey = $keys[0];
+ } else {
+ $valueKey = $namingKey;
+ $nameKey = $namingKey;
+ }
+
+ if (strpos($objectSpec, ':') > 0) {
+ $keys = explode(':', $objectSpec);
+ $fallbackTopic = $keys[1];
+ $objectType = $keys[0];
+ } else {
+ $fallbackTopic = $objectSpec;
+ $objectType = $objectSpec;
+ }
+
+ // Gets the value of the naming field (usually "name," with a few exceptions [e.g., Context is "key"])
+ $nameValue = trim($objectData[$valueKey]);
+
+ // Set up the fields array to translate; at minimum, will include the field holding the object's name
+ $fieldNames = [$nameKey];
+ // if (!$skipDescription) {
+ // $fieldNames[] = 'description';
+ // }
+ if (empty($limitToFields)) {
+ // $fieldNames = [$nameKey, 'description'];
+ $fieldNames[] = 'description';
+ } else {
+ $fieldNames = $limitToFields;
+ }
+ /*
+ Collapse any spaces, punctuation, and the words 'and' & 'or' to
+ provide a clean array key-comatible lexicon key based on
+ the object's type and name.
+
+ For example, the built-in Access Policy with the name "Load, List and View" will
+ yield the following lexicon keys for that Policy's name and description:
+ _policy_load_list_view_name
+ _policy_load_list_view_description
+
+ Note that these descriptors have the underscore prefix to denote
+ that they represent a typically immutable core-installed object
+ */
+ $find = [' and ', ' or ', ','];
+ $objectKey = str_replace($find, ' ', $nameValue);
+ $objectKey = preg_replace('/[\s]+/', '_', $objectKey);
+ $baseKey = '_' . $objectType . '_' . strtolower($objectKey) . '_';
+
+ // _templategroup_administrator_name
+ foreach ($fieldNames as $fieldName) {
+ $lexiconKey = $baseKey . $fieldName;
+ $dataName = $fieldName === $nameKey ? $valueKey : $fieldName ;
+ /*
+ $objectData[$dataName . '_trans'] = $this->modx->lexicon($lexiconKey);
+ $valueIsLexiconKey = $objectData[$dataName . '_trans'] === $lexiconKey;
+ $hasTranslation = !empty($objectData[$dataName . '_trans']) && !$valueIsLexiconKey;
+ $objectData[$dataName . '_trans_missing'] = !$hasTranslation;
+
+ // Fall back to English entry when no translation is found for this field
+ if ($useTranslation && !$hasTranslation && empty($objectData[$dataName])) {
+ $en = $this->getFileTopic('en', 'core', $fallbackTopic);
+ $objectData[$dataName] = $en[$lexiconKey];
+ $this->modx->log(
+ \modX::LOG_LEVEL_ERROR,
+ "\r\t setTranslatedCoreDescriptors:
+ \t\t\$lexiconKey: {$lexiconKey}
+ \t\t{$dataName}: {$objectData[$dataName]}
+ \t\ten arr: " . print_r($en, true)
+ );
+ }
+ */
+ $value = $this->modx->lexicon($lexiconKey);
+ $valueIsLexiconKey = $value === $lexiconKey;
+
+ if ($useTranslation) {
+ $hasTranslation = !empty($value) && !$valueIsLexiconKey;
+ if ($hasTranslation) {
+ $objectData[$dataName] = $value;
+ } else if (empty($objectData[$dataName])) {
+ // Fall back to English entry when no translation is found for this field
+ $en = $this->getFileTopic('en', 'core', $fallbackTopic);
+ $objectData[$dataName] = $en[$lexiconKey];
+ }
+ } else {
+ $objectData[$dataName] = $value;
+ }
+ }
+ }
}
diff --git a/core/src/Revolution/modNamespace.php b/core/src/Revolution/modNamespace.php
index 7c06ba8a161..4e686af03b2 100644
--- a/core/src/Revolution/modNamespace.php
+++ b/core/src/Revolution/modNamespace.php
@@ -27,6 +27,8 @@
*/
class modNamespace extends modAccessibleObject
{
+ public const NAMESPACE_CORE = 'core';
+
public function save($cacheFlag = null)
{
$saved = parent::save();
@@ -157,4 +159,26 @@ public function findPolicy($context = '')
return $policy;
}
+
+ /**
+ * 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..d558e618787 100644
--- a/core/src/Revolution/modUserGroupRole.php
+++ b/core/src/Revolution/modUserGroupRole.php
@@ -13,11 +13,36 @@
*
* @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..0be86d8e9f5 100644
--- a/manager/assets/modext/util/utilities.js
+++ b/manager/assets/modext/util/utilities.js
@@ -63,8 +63,8 @@ MODx.util.UrlParams = {
this.set({})
},
parse(str) {
- const params = new URLSearchParams(str)
- return Object.fromEntries(params.entries())
+ const params = new URLSearchParams(str);
+ return Object.fromEntries(params.entries());
}
}
@@ -141,6 +141,20 @@ MODx.StaticTextField = Ext.extend(Ext.form.TextField, {
});
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
*/
@@ -1164,3 +1178,37 @@ MODx.util.FileDownload = function (fields) {
me.isFinished(successCallback, failureCallback);
}
};
+
+// MODx.util.Render = {
+// /**
+// * Get a translated field value from an object's record; primarily used
+// * for core-installed objects
+// * @param {String} fieldName The field's name or dataIndex
+// * @param {Object} record The object's complete record
+// * @param {Boolean} translateProtectedOnly Whether to translate only protected (typically core-installed) objects
+// * @returns {String} The translated field or its default if not translation is available
+// */
+// translatedValue: function (fieldName, record, translateProtectedOnly = true) {
+// const
+// isGridRecord = Object.hasOwn(record, 'json'),
+// dataObject = isGridRecord ? record.json : record
+// ;
+// if (MODx.cultureKey === 'en' || (translateProtectedOnly && !dataObject.isProtected)) {
+// // console.log(`translatedValue :: do not translate ${record[fieldName]}...`);
+// /*
+// To update a grid's display of a single record after a successful
+// edit without refreshing the entire grid (causing a new request
+// and query to the back end), a call to the grid store's commit()
+// method is made to update the row. This is only available in the
+// data prop (the json prop is populated from the back end)
+// */
+// return isGridRecord ? record.data[fieldName] : record[fieldName] ;
+// }
+// // Return default value when no translation has been processed for this object
+// // console.log(`translatedValue :: translating ${fieldName}...`);
+// return Object.hasOwn(dataObject, `${fieldName}_trans`) && !dataObject[`${fieldName}_trans_missing`]
+// ? dataObject[`${fieldName}_trans`]
+// : dataObject[fieldName]
+// ;
+// }
+// }
\ No newline at end of file
diff --git a/manager/assets/modext/widgets/core/modx.grid.js b/manager/assets/modext/widgets/core/modx.grid.js
index fb88431782b..35422877976 100644
--- a/manager/assets/modext/widgets/core/modx.grid.js
+++ b/manager/assets/modext/widgets/core/modx.grid.js
@@ -5,98 +5,103 @@ MODx.grid.Grid = function(config = {}) {
this._loadStore();
this._loadColumnModel();
- 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: {
+ Ext.applyIf(config, {
+ store: this.store,
+ cm: this.cm,
+ sm: new Ext.grid.RowSelectionModel({ singleSelect: true }),
+ // eslint-disable-next-line no-unneeded-ternary
+ 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,{
+ Ext.applyIf(config, {
view: new Ext.grid.GroupingView(config.groupingConfig)
});
}
if (config.tbar) {
- for (var ix = 0;ix 1)) {
return false;
}
@@ -113,10 +118,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,26 +133,28 @@ 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);
+ MODx.grid.Grid.superclass.constructor.call(this, config);
this._loadMenu(config);
- this.addEvents('beforeRemoveRow','afterRemoveRow','afterAutoSave');
+ this.addEvents('beforeRemoveRow', 'afterRemoveRow', 'afterAutoSave');
if (this.autosave) {
this.on('afterAutoSave', this.onAfterAutoSave, this);
}
- if (!config.preventRender) { this.render(); }
+ if (!config.preventRender) {
+ this.render();
+ }
this.on({
render: {
fn: function() {
const topToolbar = this.getTopToolbar();
- if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls == 'has-nested-filters') {
+ if (topToolbar && topToolbar.initialConfig.cls && topToolbar.initialConfig.cls === 'has-nested-filters') {
this.hasNestedFilters = true;
}
},
@@ -159,32 +166,57 @@ MODx.grid.Grid = function(config = {}) {
}
});
if (config.autosave) {
- this.on('afteredit',this.saveRecord,this);
+ this.on('afteredit', this.saveRecord, this);
}
if (config.paging && config.grouping) {
this.getBottomToolbar().bind(this.store);
}
- if (!config.paging && !config.hasOwnProperty('pageSize')) {
+ if (!config.paging && !Object.hasOwn(config, '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)
+ start: config.pageStart || 0,
+ limit: Object.hasOwn(config, 'pageSize') ? config.pageSize : (parseInt(MODx.config.default_per_page, 10) || 20)
}
});
- this.getStore().on('exception',this.onStoreException,this);
+ 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.Grid, Ext.grid.EditorGridPanel, {
+
+ windows: {},
+
+ protectedIdentifiers: null,
+
+ /**
+ * The data index, not necessarily the primary key, used
+ * to determine if a row can be deleted / or if the value
+ * of the row's data index is an un-usable, reserved value
+ */
+ protectedDataIndex: null,
+
+ userCanEdit: false,
+
+ userCanCreate: false,
- ,onStoreException: function(dataProxy, type, action, options, response) {
+ userCanDelete: false,
+
+ gridMenuActions: [],
+
+ /** @property {Boolean} userHasPermissions Whether user has permissions of any kind to manipulate the current grid's data */
+ hasPermissions: false,
+
+ /** @property {Boolean} userHasSavePermissions Whether user has the general ability to save (to either create or edit) */
+ userHasSavePermissions: false,
+
+ showActionsMenu: null,
+
+ onStoreException: function(dataProxy, type, action, options, response) {
const responseStatusCode = response.status || 'Unknown',
responseStatusText = !Ext.isEmpty(response.statusText) ? `(${response.statusText})` : ''
;
@@ -223,58 +255,76 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
}
this.getView().emptyText = `${msg}
`;
this.getView().refresh(false);
- }
+ },
- ,saveRecord: function(e) {
+ /**
+ * 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;
- 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);
+ 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: d
- }
- ,listeners: {
+ url: url,
+ params: {
+ action: this.config.save_action || 'updateFromGrid',
+ data: data
+ },
+ listeners: {
success: {
- fn: function(r) {
+ fn: function(response) {
if (this.config.save_callback) {
- Ext.callback(this.config.save_callback,this.config.scope || this,[r]);
+ 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);
}
- this.fireEvent('afterAutoSave',r);
- }
- ,scope: this
- }
- ,failure: {
- fn: function(r) {
+ 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', r);
- }
- ,scope: this
+ this.fireEvent('afterAutoSave', response);
+ },
+ scope: this
}
}
});
- }
+ },
/**
* 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) {
+ onAfterAutoSave: function(response) {
if (!response.success && response.message === '') {
- var msg = '';
+ 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;
+ msg += (msg !== '' ? '
' : '') + data.msg;
}, this);
}
if (Ext.isEmpty(msg)) {
@@ -283,93 +333,103 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
}
MODx.msg.alert(_('error'), msg);
}
- }
+ },
- ,onChangePerPage: function(tf,nv) {
- if (Ext.isEmpty(nv)) return false;
- nv = parseInt(nv);
+ onChangePerPage: function(tf, nv) {
+ if (Ext.isEmpty(nv)) { return false; }
+ nv = parseInt(nv, 10);
this.getBottomToolbar().pageSize = nv;
- this.store.load({params:{
- start:0
- ,limit: nv
- }});
- }
+ this.store.load({
+ params: {
+ start: 0,
+ limit: nv
+ }
+ });
+ },
- ,loadWindow: function(btn,e,win,or) {
- var r = this.menu.record;
+ loadWindow: function(btn, e, win, or) {
+ const 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}
+ 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);
+ Ext.apply(win, or);
}
this.windows[win.xtype] = Ext.ComponentMgr.create(win);
}
- if (this.windows[win.xtype].setValues && win.blankValues !== true && r != undefined) {
+ if (this.windows[win.xtype].setValues && win.blankValues !== true && r !== undefined) {
this.windows[win.xtype].setValues(r);
}
this.windows[win.xtype].show(e.target);
- }
+ },
- ,confirm: function(type,text) {
- var p = { action: type };
- var k = this.config.primaryKey || 'id';
+ confirm: function(type, text) {
+ const
+ p = { action: type },
+ k = this.config.primaryKey || 'id'
+ ;
p[k] = this.menu.record[k];
MODx.msg.confirm({
- title: _(type)
- ,text: _(text) || _('confirm_remove')
- ,url: this.config.url
- ,params: p
- ,listeners: {
- 'success': {fn:this.refresh,scope:this}
+ title: _(type),
+ text: _(text) || _('confirm_remove'),
+ url: this.config.url,
+ params: p,
+ listeners: {
+ success: { fn: this.refresh, scope: this }
}
});
- }
+ },
- ,remove: function(text, action) {
+ remove: function(text, action) {
if (this.destroying) {
return MODx.grid.Grid.superclass.remove.apply(this, arguments);
}
- var r = this.menu.record;
+ const 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';
+ const p = this.config.saveParams || {};
+ Ext.apply(p, { action: action || 'remove' });
+ const k = this.config.primaryKey || 'id';
p[k] = r[k];
- if (this.fireEvent('beforeRemoveRow',r)) {
+ 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}
+ title: _('warning'),
+ text: _(text, r),
+ url: this.config.url,
+ params: p,
+ listeners: {
+ success: {
+ fn: function() {
+ this.removeActiveRow(r);
+ },
+ scope: this
+ }
}
});
}
- }
+ },
- ,removeActiveRow: function(r) {
- if (this.fireEvent('afterRemoveRow',r)) {
- var rx = this.getSelectionModel().getSelected();
+ removeActiveRow: function(r) {
+ if (this.fireEvent('afterRemoveRow', r)) {
+ const rx = this.getSelectionModel().getSelected();
this.getStore().remove(rx);
}
- }
+ },
- ,_loadMenu: function() {
+ _loadMenu: function() {
this.menu = new Ext.menu.Menu(this.config.menuConfig);
- }
+ },
- ,_showMenu: function(g,ri,e) {
+ _showMenu: function(g, ri, e) {
e.stopEvent();
e.preventDefault();
this.menu.record = this.getStore().getAt(ri).data;
@@ -377,41 +437,42 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
this.getSelectionModel().selectRow(ri);
}
this.menu.removeAll();
+ let menu;
if (this.getMenu) {
- var m = this.getMenu(g,ri,e);
- if (m && m.length && m.length > 0) {
- this.addContextMenuItem(m);
+ menu = this.getMenu(g, ri, e);
+ if (menu && menu.length && menu.length > 0) {
+ this.addContextMenuItem(menu);
}
}
- if ((!m || m.length <= 0) && this.menu.record.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);
}
- }
+ },
- ,_loadStore: function() {
+ _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: {
+ 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) {
@@ -438,15 +499,15 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
});
} 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:{
+ 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) {
@@ -456,25 +517,27 @@ Ext.extend(MODx.grid.Grid,Ext.grid.EditorGridPanel,{
}
});
}
- }
+ },
- ,_loadColumnModel: function() {
+ _loadColumnModel: function() {
if (this.config.columns) {
- var c = this.config.columns;
- for (var i=0;i