diff --git a/src/base/Field.php b/src/base/Field.php index abd9e270003..3b973fbaea0 100644 --- a/src/base/Field.php +++ b/src/base/Field.php @@ -380,6 +380,11 @@ protected function defineRules(): array 'propagateAll', 'propagating', 'ref', + 'relatedToAssets', + 'relatedToCategories', + 'relatedToEntries', + 'relatedToTags', + 'relatedToUsers', 'resaving', 'revisionId', 'rgt', diff --git a/src/config/GeneralConfig.php b/src/config/GeneralConfig.php index 58f74d8ba30..1805611c62c 100644 --- a/src/config/GeneralConfig.php +++ b/src/config/GeneralConfig.php @@ -954,6 +954,26 @@ class GeneralConfig extends BaseConfig */ public string|array|null $disabledPlugins = null; + /** + * @var string[] Array of utility IDs that should be disabled. + * + * ::: code + * ```php Static Config + * ->disabledUtilities([ + * 'updates', + * 'find-replace', + * ]) + * ``` + * ```shell Environment Override + * CRAFT_DISABLED_UTILITIES=updates,find-replace + * ``` + * ::: + * + * @group System + * @since 4.6.0 + */ + public array $disabledUtilities = []; + /** * @var bool Whether front end requests should respond with `X-Robots-Tag: none` HTTP headers, indicating that pages should not be indexed, * and links on the page should not be followed, by web crawlers. @@ -4129,6 +4149,33 @@ public function disabledPlugins(string|array|null $value): self return $this; } + /** + * Array of utility IDs that should be disabled. + * + * ::: code + * ```php Static Config + * ->disabledUtilities([ + * 'updates', + * 'find-replace', + * ]) + * ``` + * ```shell Environment Override + * CRAFT_DISABLED_UTILITIES=updates,find-replace + * ``` + * ::: + * + * @group System + * @param string[] $value + * @return self + * @see $disabledUtilities + * @since 4.6.0 + */ + public function disabledUtilities(array $value): self + { + $this->disabledUtilities = $value; + return $this; + } + /** * Whether front end requests should respond with `X-Robots-Tag: none` HTTP headers, indicating that pages should not be indexed, * and links on the page should not be followed, by web crawlers. diff --git a/src/fields/BaseRelationField.php b/src/fields/BaseRelationField.php index a3269b9f4f0..5f0174393b9 100644 --- a/src/fields/BaseRelationField.php +++ b/src/fields/BaseRelationField.php @@ -946,7 +946,7 @@ public function afterElementSave(ElementInterface $element, bool $isNew): void { // Skip if nothing changed, or the element is just propagating and we're not localizing relations if ( - ($element->isFieldDirty($this->handle) || $this->maintainHierarchy) && + ($element->duplicateOf || $element->isFieldDirty($this->handle) || $this->maintainHierarchy) && (!$element->propagating || $this->localizeRelations) ) { /** @var ElementQueryInterface|ElementCollection $value */ diff --git a/src/helpers/Api.php b/src/helpers/Api.php index 43a66792025..22a9a59d29d 100644 --- a/src/helpers/Api.php +++ b/src/helpers/Api.php @@ -207,7 +207,7 @@ public static function processResponseHeaders(array $headers): void if (isset($headers['x-craft-license-info'])) { $oldLicenseInfo = $cache->get('licenseInfo') ?: []; $licenseInfo = []; - $allCombinedInfo = explode(',', reset($headers['x-craft-license-info'])); + $allCombinedInfo = array_filter(explode(',', reset($headers['x-craft-license-info']))); foreach ($allCombinedInfo as $combinedInfo) { [$handle, $combinedValues] = explode(':', $combinedInfo, 2); if ($combinedValues === LicenseKeyStatus::Invalid->value) { diff --git a/src/services/Security.php b/src/services/Security.php index 0aa0c901e9d..df065c27f2b 100644 --- a/src/services/Security.php +++ b/src/services/Security.php @@ -40,7 +40,14 @@ class Security extends \yii\base\Security public function init(): void { parent::init(); + $this->_blowFishHashCost = Craft::$app->getConfig()->getGeneral()->blowfishHashCost; + + // normalize the sensitive keywords + $this->sensitiveKeywords = array_map( + fn(string $word) => Inflector::camel2words($word, false), + $this->sensitiveKeywords, + ); } /** diff --git a/src/services/Utilities.php b/src/services/Utilities.php index 879b90f46dd..d01682380c5 100644 --- a/src/services/Utilities.php +++ b/src/services/Utilities.php @@ -96,7 +96,12 @@ public function getAllUtilityTypes(): array ]); $this->trigger(self::EVENT_REGISTER_UTILITIES, $event); - return $event->types; + $disabledUtilities = array_flip(Craft::$app->getConfig()->getGeneral()->disabledUtilities); + + return array_values(array_filter($event->types, function(string $class) use ($disabledUtilities) { + /** @var string|UtilityInterface $class */ + return !isset($disabledUtilities[$class::id()]); + })); } /** diff --git a/src/web/Request.php b/src/web/Request.php index 2f6ecc5b879..e4875d6c870 100644 --- a/src/web/Request.php +++ b/src/web/Request.php @@ -697,7 +697,11 @@ public function getActionSegments(): ?array */ public function getIsPreview(): bool { - return $this->getQueryParam('x-craft-preview') !== null || $this->getQueryParam('x-craft-live-preview') !== null; + return ( + ($this->getQueryParam('x-craft-preview') ?? $this->getQueryParam('x-craft-live-preview')) !== null && + // If there's a token but it expired, they're looking at the live site + (!$this->getHadToken() || $this->getToken() !== null) + ); } /** diff --git a/src/web/assets/garnish/dist/garnish.js b/src/web/assets/garnish/dist/garnish.js index b8a01d7a581..afe83aef5b2 100644 --- a/src/web/assets/garnish/dist/garnish.js +++ b/src/web/assets/garnish/dist/garnish.js @@ -1,3 +1,3 @@ /*! For license information please see garnish.js.LICENSE.txt */ -!function(){var t={55:function(t,e,i){var s=i(38),n=i(820),o=s.default;void 0===n.Garnish&&(n.Garnish=o),t.exports=s},820:function(t,e,i){"use strict";t.exports=function(){if("object"==typeof globalThis)return globalThis;var t;try{t=this||new Function("return this")()}catch(t){if("object"==typeof window)return window;if("object"==typeof self)return self;if(void 0!==i.g)return i.g}return t}()},38:function(t,e,i){"use strict";i.r(e),i.d(e,{default:function(){return R}});var s=jQuery,n=i.n(s);function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}var r=function(){};function a(t){return a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},a(t)}r.extend=function(t,e){var i=r.prototype.extend;r._prototyping=!0;var s=new this;i.call(s,t),s.base=function(){},delete r._prototyping;var n=s.constructor,o=s.constructor=function(){if(!r._prototyping)if(this._constructing||this.constructor==o)this._constructing=!0,n.apply(this,arguments),delete this._constructing;else if(null!=arguments[0])return(arguments[0].extend||i).call(arguments[0],s)};return o.ancestor=this,o.extend=this.extend,o.forEach=this.forEach,o.implement=this.implement,o.prototype=s,o.toString=this.toString,o.valueOf=function(t){return"object"==t?o:n.valueOf()},i.call(o,e),"function"==typeof o.init&&o.init(),o},r.prototype={extend:function(t,e){if(arguments.length>1){var i=this[t];if(i&&"function"==typeof e&&(!i.valueOf||i.valueOf()!=e.valueOf())&&/\bbase\b/.test(e)){var s=e.valueOf();e=function(){var t=this.base||r.prototype.base;this.base=i;var e=s.apply(this,arguments);return this.base=t,e},e.valueOf=function(t){return"object"==t?e:s},e.toString=r.toString}this[t]=e}else if(t){var n=r.prototype.extend;r._prototyping||"function"==typeof this||(n=this.extend||n);for(var a={toSource:null},h=["constructor","toString","valueOf"],l=r._prototyping?0:1;u=h[l++];)t[u]!=a[u]&&n.call(this,u,t[u]);for(var u in t)if(!a[u]){var d=Object.getOwnPropertyDescriptor(t,u);"undefined"!=o(d.value)?n.call(this,u,d.value):Object.defineProperty(this,u,d)}}return this}},r=r.extend({constructor:function(){this.extend(arguments[0])}},{ancestor:Object,version:"1.1",forEach:function(t,e,i){for(var s in t)void 0===this.prototype[s]&&e.call(i,t[s],s,t)},implement:function(){for(var t=0;t=0;n--){var o=this._eventHandlers[n];o.type!==s[0]||s[1]&&o.namespace!==s[1]||o.handler!==e||this._eventHandlers.splice(n,1)}},trigger:function(t,e){var i,s,o,r={type:t,target:this};for(i=0;it.length)&&(e=t.length);for(var i=0,s=new Array(e);ithis.drag._maxMouseScrollY&&(this.drag._scrollProperty="scrollTop",this.drag._scrollAxis="Y",this.drag._scrollDist=Math.round((this.mouseY-this.drag._maxMouseScrollY)/2)))),this.drag._scrollProperty||this.settings.axis===K.Y_AXIS||(this.drag._winScrollLeft=K.$win.scrollLeft(),this.drag._minMouseScrollX=this.drag._winScrollLeft+K.BaseDrag.windowScrollTargetSize,this.mouseXthis.drag._maxMouseScrollX&&(this.drag._scrollProperty="scrollLeft",this.drag._scrollAxis="X",this.drag._scrollDist=Math.round((this.mouseX-this.drag._maxMouseScrollX)/2)))),this.drag._scrollProperty?(this.scrollProperty||(this.scrollProxy||(this.scrollProxy=this._scrollWindow.bind(this)),this.scrollFrame&&(K.cancelAnimationFrame(this.scrollFrame),this.scrollFrame=null),this.scrollFrame=K.requestAnimationFrame(this.scrollProxy)),this.scrollProperty=this.drag._scrollProperty,this.scrollAxis=this.drag._scrollAxis,this.scrollDist=this.drag._scrollDist):this._cancelWindowScroll()),this.onDrag()},stopDragging:function(){this.dragging=!1,this.onDragStop(),this._cancelWindowScroll()},addItems:function(t){var e,i=this,s=function(t,e){var i="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!i){if(Array.isArray(t)||(i=function(t,e){if(t){if("string"==typeof t)return u(t,e);var i=Object.prototype.toString.call(t).slice(8,-1);return"Object"===i&&t.constructor&&(i=t.constructor.name),"Map"===i||"Set"===i?Array.from(t):"Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?u(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){i&&(t=i);var s=0,n=function(){};return{s:n,n:function(){return s>=t.length?{done:!0}:{done:!1,value:t[s++]}},e:function(t){throw t},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,r=!0,a=!1;return{s:function(){i=i.call(t)},n:function(){var t=i.next();return r=t.done,t},e:function(t){a=!0,o=t},f:function(){try{r||null==i.return||i.return()}finally{if(a)throw o}}}}(t=n().makeArray(t));try{var o=function(){var t=e.value;n().data(t,"drag")&&(console.warn("Element was added to more than one dragger"),n().data(t,"drag").removeItems(t)),n().data(t,"drag",i),i.addListener(i._getItemHandle(t),"mousedown",(function(e){i._handleMouseDown(e,t)}))};for(s.s();!(e=s.n()).done;)o()}catch(t){s.e(t)}finally{s.f()}this.$items=this.$items.add(t)},removeItems:function(t){t=n().makeArray(t);for(var e=0;e=(null!==(e=this.settings.minMouseDist)&&void 0!==e?e:K.BaseDrag.minMouseDist)&&this.startDragging()),this.dragging&&this.drag(!0)},_handleMouseUp:function(t){this.removeAllListeners(K.$doc),this.dragging&&this.stopDragging(),this.$targetItem=null},_scrollWindow:function(){this._.scrollPos=K.$scrollContainer[this.scrollProperty](),this._.scrollTargetPos=this._.scrollPos+this.scrollDist,this._.scrollTargetPos<0?this._.scrollTargetPos=0:(this._.$scrollContainer=K.$scrollContainer[0]===K.$win[0]?K.$bod:K.$scrollContainer,"Y"===this.scrollAxis?this._.scrollMax=this._.$scrollContainer[0].clientHeight-K.$scrollContainer.height():this._.scrollMax=this._.$scrollContainer[0].clientWidth-K.$scrollContainer.width(),this._.scrollTargetPos>this._.scrollMax&&(this._.scrollTargetPos=this._.scrollMax)),K.$scrollContainer[this.scrollProperty](this._.scrollTargetPos),this["mouse"+this.scrollAxis]-=this._.scrollPos-K.$scrollContainer[this.scrollProperty](),this["realMouse"+this.scrollAxis]=this["mouse"+this.scrollAxis],this.drag(),this.scrollFrame=K.requestAnimationFrame(this.scrollProxy)},_cancelWindowScroll:function(){this.scrollFrame&&(K.cancelAnimationFrame(this.scrollFrame),this.scrollFrame=null),this.scrollProperty=null,this.scrollAxis=null,this.scrollDist=null},_deinitItem:function(t){this.removeAllListeners(t),n().removeData(t,"drag")}},{minMouseDist:1,windowScrollTargetSize:25,defaults:{minMouseDist:null,handle:null,axis:null,ignoreHandleSelector:"input, textarea, button, select, .btn",onDragStart:n().noop,onDrag:n().noop,onDragStop:n().noop}}),c=h.extend({$container:null,$all:null,$options:null,init:function(t){this.$container=n()(t),this.$container.data("checkboxSelect")&&(console.warn("Double-instantiating a checkbox select on an element"),this.$container.data("checkboxSelect").destroy()),this.$container.data("checkboxSelect",this);var e=this.$container.find("input");this.$all=e.filter(".all:first"),this.$options=e.not(this.$all),this.addListener(this.$all,"change","onAllChange")},onAllChange:function(){var t=this.$all.prop("checked");this.$options.prop({checked:t,disabled:t})},destroy:function(){this.$container.removeData("checkboxSelect"),this.base()}}),g=h.extend({$target:null,options:null,$menu:null,showingMenu:!1,init:function(t,e,i){this.$target=n()(t),this.$target.data("contextmenu")&&(console.warn("Double-instantiating a context menu on an element"),this.$target.data("contextmenu").destroy()),this.$target.data("contextmenu",this),this.options=e,this.setSettings(i,K.ContextMenu.defaults),K.ContextMenu.counter++,this.enable()},buildMenu:function(){this.$menu=n()('