diff --git a/src/Database/Relations/AttachMany.php b/src/Database/Relations/AttachMany.php index e57465d1b..36172083e 100644 --- a/src/Database/Relations/AttachMany.php +++ b/src/Database/Relations/AttachMany.php @@ -39,6 +39,18 @@ public function __construct(Builder $query, Model $parent, $type, $id, $isPublic */ public function setSimpleValue($value) { + // Nulling the relationship + if (!$value) { + $this->parent->unsetRelation($this->relationName); + + if ($this->parent->exists) { + $this->parent->bindEventOnce('model.afterSave', function() { + $this->ensureRelationIsEmpty(); + }); + } + return; + } + // Append a single newly uploaded file(s) if ($value instanceof UploadedFile) { $this->parent->bindEventOnce('model.afterSave', function () use ($value) { diff --git a/src/Database/Relations/AttachOne.php b/src/Database/Relations/AttachOne.php index f7e2a65a7..52d7c386b 100644 --- a/src/Database/Relations/AttachOne.php +++ b/src/Database/Relations/AttachOne.php @@ -43,6 +43,18 @@ public function setSimpleValue($value) $value = reset($value); } + // Nulling the relationship + if (!$value) { + $this->parent->setRelation($this->relationName, null); + + if ($this->parent->exists) { + $this->parent->bindEventOnce('model.afterSave', function() { + $this->ensureRelationIsEmpty(); + }); + } + return; + } + // Newly uploaded file if ($value instanceof UploadedFile) { $this->parent->bindEventOnce('model.afterSave', function () use ($value) { @@ -67,6 +79,10 @@ public function setSimpleValue($value) // The relation is set here to satisfy validation $this->parent->setRelation($this->relationName, $value); + + $this->parent->bindEventOnce('model.afterValidate', function() { + $this->parent->unsetRelation($this->relationName); + }); } /** diff --git a/src/Database/Relations/AttachOneOrMany.php b/src/Database/Relations/AttachOneOrMany.php index 6901ea613..e6f0ad40f 100644 --- a/src/Database/Relations/AttachOneOrMany.php +++ b/src/Database/Relations/AttachOneOrMany.php @@ -404,6 +404,21 @@ public function makeValidationFile($value) return $value; } + /** + * ensureRelationIsEmpty ensures the relation is empty, either deleted or nulled. + */ + protected function ensureRelationIsEmpty() + { + $options = $this->parent->getRelationDefinition($this->relationName); + + if (array_get($options, 'delete', false)) { + $this->delete(); + } + else { + $this->update([$this->getForeignKeyName() => null]); + } + } + /** * getRelatedKeyName * @return string diff --git a/src/Database/Relations/HasMany.php b/src/Database/Relations/HasMany.php index d10f9e969..27c74b7bd 100644 --- a/src/Database/Relations/HasMany.php +++ b/src/Database/Relations/HasMany.php @@ -37,6 +37,8 @@ public function setSimpleValue($value) { // Nulling the relationship if (!$value) { + $this->parent->unsetRelation($this->relationName); + if ($this->parent->exists) { $this->parent->bindEventOnce('model.afterSave', function() { $this->ensureRelationIsEmpty(); diff --git a/src/Database/Relations/HasOne.php b/src/Database/Relations/HasOne.php index 57db078c6..65c333760 100644 --- a/src/Database/Relations/HasOne.php +++ b/src/Database/Relations/HasOne.php @@ -39,6 +39,8 @@ public function setSimpleValue($value) // Nulling the relationship if (!$value) { + $this->parent->setRelation($this->relationName, null); + if ($this->parent->exists) { $this->parent->bindEventOnce('model.afterSave', function() { $this->ensureRelationIsEmpty(); diff --git a/src/Database/Scopes/MultisiteScope.php b/src/Database/Scopes/MultisiteScope.php index 7efe089f6..97dc394fe 100644 --- a/src/Database/Scopes/MultisiteScope.php +++ b/src/Database/Scopes/MultisiteScope.php @@ -16,7 +16,7 @@ class MultisiteScope implements ScopeInterface /** * @var array extensions to be added to the builder. */ - protected $extensions = ['WithSite', 'WithSites']; + protected $extensions = ['WithSite', 'WithSites', 'WithSyncSites']; /** * apply the scope to a given Eloquent query builder. @@ -29,12 +29,15 @@ public function apply(BuilderBase $builder, ModelBase $model) } /** - * addWithSite + * addWithSite removes this scope and includes the specified site */ protected function addWithSite(BuilderBase $builder) { $builder->macro('withSite', function (BuilderBase $builder, $siteId) { - return $builder->where($builder->getModel()->getQualifiedSiteIdColumn(), $siteId); + return $builder + ->withoutGlobalScope($this) + ->where($builder->getModel()->getQualifiedSiteIdColumn(), $siteId) + ; }); } @@ -43,8 +46,25 @@ protected function addWithSite(BuilderBase $builder) */ protected function addWithSites(BuilderBase $builder) { - $builder->macro('withSites', function (BuilderBase $builder) { - return $builder->withoutGlobalScope($this); + $builder->macro('withSites', function (BuilderBase $builder, $siteIds = null) { + if (!is_array($siteIds)) { + return $builder->withoutGlobalScope($this); + } + + return $builder + ->withoutGlobalScope($this) + ->whereIn($builder->getModel()->getQualifiedSiteIdColumn(), $siteIds) + ; + }); + } + + /** + * addWithSyncSites removes this scope and includes sites that should be synced with this model + */ + protected function addWithSyncSites(BuilderBase $builder) + { + $builder->macro('withSyncSites', function (BuilderBase $builder) { + return $builder->withSites($builder->getModel()->getMultisiteSyncSites()); }); } diff --git a/src/Database/Traits/Multisite.php b/src/Database/Traits/Multisite.php index 4a2b3bc24..7cc49a349 100644 --- a/src/Database/Traits/Multisite.php +++ b/src/Database/Traits/Multisite.php @@ -90,18 +90,20 @@ public function multisiteSaveComplete() Site::withGlobalContext(function() { $otherModels = $this->newOtherSiteQuery()->get(); - $otherSites = $otherModels->pluck('site_id')->all(); + $otherSites = $this->getMultisiteSyncSites(); // Propagate attributes to known records if ($this->propagatable) { - foreach ($otherModels as $model) { - $this->propagateToSite($model->site_id, $model); + foreach ($otherSites as $siteId) { + if ($model = $otherModels->where('site_id', $siteId)->first()) { + $this->propagateToSite($siteId, $model); + } } } // Sync non-existent records if ($this->isMultisiteSyncEnabled()) { - $missingSites = array_diff($this->getMultisiteSyncSites(), $otherSites); + $missingSites = array_diff($otherSites, $otherModels->pluck('site_id')->all()); foreach ($missingSites as $missingSite) { $this->propagateToSite($missingSite); } @@ -280,6 +282,18 @@ public function propagateToSite($siteId, $otherModel = null) return $otherModel; } + /** + * getMultisiteKey returns the root key if multisite is used + */ + public function getMultisiteKey() + { + if (!$this->isMultisiteEnabled()) { + return $this->getKey(); + } + + return $this->site_root_id ?: $this->getKey(); + } + /** * isMultisiteEnabled allows for programmatic toggling * @return bool @@ -385,7 +399,7 @@ public function findOrCreateForSite($siteId = null) // Newly created model if (!$otherModel->exists) { - $otherModel->save(); + $otherModel->save(['force' => true]); } // Restoring a trashed model diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index 45f292765..7780b9a69 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -45,7 +45,7 @@ trait ExtendableTrait public function extendableConstruct() { // Apply init callbacks - $classes = array_merge([static::class], class_parents($this)); + $classes = array_merge([static::class], class_parents(static::class)); foreach ($classes as $class) { if (isset(Container::$classCallbacks[$class]) && is_array(Container::$classCallbacks[$class])) { foreach (Container::$classCallbacks[$class] as $callback) { @@ -435,7 +435,7 @@ public function extendableGet($name) } } - $parent = get_parent_class(); + $parent = get_parent_class(self::class); if ($parent !== false && method_exists($parent, '__get')) { return parent::__get($name); } @@ -468,7 +468,7 @@ public function extendableSet($name, $value) } // This targets trait usage in particular - $parent = get_parent_class(); + $parent = get_parent_class(self::class); if ($parent !== false && method_exists($parent, '__set')) { parent::__set($name, $value); $found = true; @@ -504,7 +504,7 @@ public function extendableCall($name, $params = null) return call_user_func_array($callable, $params); } - $parent = get_parent_class(); + $parent = get_parent_class(self::class); if ($parent !== false && method_exists($parent, '__call')) { return parent::__call($name, $params); } diff --git a/src/Filesystem/Filesystem.php b/src/Filesystem/Filesystem.php index 58021938a..f94d8160c 100644 --- a/src/Filesystem/Filesystem.php +++ b/src/Filesystem/Filesystem.php @@ -1,5 +1,6 @@ \Illuminate\Auth\Middleware\Authenticate::class, - // 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, - // 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, // 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, ]; @@ -79,9 +79,9 @@ class Kernel extends HttpKernel protected $middlewarePriority = [ \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, - // \Illuminate\Auth\Middleware\Authenticate::class, - // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\Auth\Middleware\Authenticate::class, + \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, - // \Illuminate\Auth\Middleware\Authorize::class, + \Illuminate\Auth\Middleware\Authorize::class, ]; } diff --git a/src/Html/Helper.php b/src/Html/Helper.php index d1912b5dc..0b4f3eadd 100644 --- a/src/Html/Helper.php +++ b/src/Html/Helper.php @@ -17,7 +17,7 @@ class Helper */ public static function nameToId($string) { - return rtrim(str_replace('--', '-', str_replace(['[', ']'], '-', $string)), '-'); + return rtrim(str_replace('--', '-', str_replace(['[', ']', ':', '.'], '-', $string)), '-'); } /** diff --git a/src/Support/ClassLoader.php b/src/Support/ClassLoader.php index 1a7673813..f46a98aa4 100644 --- a/src/Support/ClassLoader.php +++ b/src/Support/ClassLoader.php @@ -33,6 +33,11 @@ class ClassLoader */ public $manifest = []; + /** + * @var array unknownClasses cache + */ + protected $unknownClasses = []; + /** * @var bool manifestDirty if manifest needs to be written */ @@ -68,6 +73,10 @@ public function __construct(Filesystem $files, string $basePath) */ public function load($class): bool { + if (!str_contains($class, '\\')) { + return false; + } + if ( isset($this->manifest[$class]) && is_file($fullPath = $this->basePath.DIRECTORY_SEPARATOR.$this->manifest[$class]) @@ -76,6 +85,10 @@ public function load($class): bool return true; } + if (isset($this->unknownClasses[$class])) { + return false; + } + [$lowerClass, $upperClass] = $this->normalizeClass($class); // Load namespaces @@ -94,6 +107,8 @@ public function load($class): bool } } + $this->unknownClasses[$class] = true; + return false; } @@ -150,7 +165,7 @@ public function register(): void $this->registered = spl_autoload_register(function($class) { $this->load($class); - }, true, true); + }); } /** diff --git a/src/Support/Facades/Site.php b/src/Support/Facades/Site.php index 76736a686..4b7dc575b 100644 --- a/src/Support/Facades/Site.php +++ b/src/Support/Facades/Site.php @@ -13,9 +13,11 @@ * @method static bool hasAnySite() * @method static bool hasMultiSite() * @method static bool hasSiteGroups() - * @method static array listEnabled() + * @method static \System\Classes\SiteCollection listEnabled() + * @method static \System\Classes\SiteCollection listSites() * @method static array listSiteIds() - * @method static iterable listSites() + * @method static array listSiteIdsInGroup($siteId) + * @method static array listSiteIdsInLocale($siteId) * @method static mixed getEditSite() * @method static string getEditSiteId() * @method static void setEditSiteId(string $siteId) @@ -23,15 +25,13 @@ * @method static mixed getAnyEditSite() * @method static bool hasAnyEditSite() * @method static bool hasMultiEditSite() - * @method static iterable listEditEnabled() + * @method static \System\Classes\SiteCollection listEditEnabled() * @method static void applyEditSite(mixed $site) * @method static mixed getActiveSite() * @method static string getActiveSiteId() * @method static void setActiveSiteId(string $siteId) * @method static void setActiveSite(mixed $site) * @method static void applyActiveSite(mixed $site) - * @method static array listSiteIdsInGroup($siteId) - * @method static array listSiteIdsInLocale($siteId) * @method static int|null getSiteIdFromContext() * @method static string|null getSiteCodeFromContext() * @method static mixed getSiteFromContext()