diff --git a/helpers/Schema.php b/helpers/Schema.php index 6d59529a3..7181f7147 100644 --- a/helpers/Schema.php +++ b/helpers/Schema.php @@ -5,4 +5,4 @@ * * @see \Illuminate\Database\Schema\Builder */ -class Schema extends Illuminate\Support\Facades\Schema {} +class Schema extends October\Rain\Support\Facades\Schema {} diff --git a/helpers/Url.php b/helpers/Url.php index 58a7fdc5b..fae888c2b 100644 --- a/helpers/Url.php +++ b/helpers/Url.php @@ -5,4 +5,4 @@ * * @see \Illuminate\Routing\UrlGenerator */ -class Url extends Illuminate\Support\Facades\URL {} +class Url extends October\Rain\Support\Facades\Url {} diff --git a/helpers/Validator.php b/helpers/Validator.php index fb77bb194..e2c5c5eec 100644 --- a/helpers/Validator.php +++ b/helpers/Validator.php @@ -5,4 +5,4 @@ * * @see \October\Rain\Validation\Factory */ -class Validator extends Illuminate\Support\Facades\Validator {} +class Validator extends October\Rain\Support\Facades\Validator {} diff --git a/src/Argon/Argon.php b/src/Argon/Argon.php index f96112b13..0570cbb04 100644 --- a/src/Argon/Argon.php +++ b/src/Argon/Argon.php @@ -3,7 +3,7 @@ use Carbon\Carbon as DateBase; /** - * Argon is an umbrella class for Carbon + * Argon is an umbrella class for Carbon that automatically applies localizations * * @package october\argon * @author Alexey Bobkov, Samuel Georges @@ -11,37 +11,17 @@ class Argon extends DateBase { /** - * @var string|callable|null formatFunction function to call instead of format + * format */ - protected static $formatFunction = 'translatedFormat'; - - /** - * @var string|callable|null createFromFormatFunction function to call instead - * of createFromFormat - */ - protected static $createFromFormatFunction = 'createFromFormatWithCurrentLocale'; - - /** - * @var string|callable|null parseFunction function to call instead of parse. - */ - protected static $parseFunction = 'parseWithCurrentLocale'; - - /** - * parseWithCurrentLocale - */ - public static function parseWithCurrentLocale($time = null, $timezone = null) + public function format($format) { - if (is_string($time)) { - $time = static::translateTimeString($time, static::getLocale(), 'en'); - } - - return parent::rawParse($time, $timezone); + return parent::translatedFormat($format); } /** - * createFromFormatWithCurrentLocale + * createFromFormat */ - public static function createFromFormatWithCurrentLocale($format, $time = null, $timezone = null) + public static function createFromFormat($format, $time, $timezone = null) { if (is_string($time)) { $time = static::translateTimeString($time, static::getLocale(), 'en'); @@ -51,14 +31,14 @@ public static function createFromFormatWithCurrentLocale($format, $time = null, } /** - * getLanguageFromLocale gets the language portion of the locale. - * @param string $locale - * @return string + * parse */ - public static function getLanguageFromLocale($locale) + public static function parse($time = null, $timezone = null) { - $parts = explode('_', str_replace('-', '_', $locale)); + if (is_string($time)) { + $time = static::translateTimeString($time, static::getLocale(), 'en'); + } - return $parts[0]; + return parent::rawParse($time, $timezone); } } diff --git a/src/Argon/ArgonServiceProvider.php b/src/Argon/ArgonServiceProvider.php index 07c400141..59b7ca5e3 100644 --- a/src/Argon/ArgonServiceProvider.php +++ b/src/Argon/ArgonServiceProvider.php @@ -1,5 +1,10 @@ app['config']->get('app.locale'); - $this->setArgonLocale($locale); + $this->setCarbonLocale($locale); $this->app['events']->listen('locale.changed', function ($locale) { - $this->setArgonLocale($locale); + $this->setCarbonLocale($locale); }); } /** - * setArgonLocale sets the locale using the correct load order. + * setCarbonLocale sets the locale using the correct load order. */ - protected function setArgonLocale($locale) + protected function setCarbonLocale($locale) { - Argon::setLocale($locale); + Carbon::setLocale($locale); + CarbonImmutable::setLocale($locale); + CarbonPeriod::setLocale($locale); + CarbonInterval::setLocale($locale); $fallbackLocale = $this->getFallbackLocale($locale); if ($locale !== $fallbackLocale) { - Argon::setFallbackLocale($fallbackLocale); + Carbon::setFallbackLocale($fallbackLocale); } } diff --git a/src/Composer/Manager.php b/src/Composer/Manager.php index d3eaf1121..27ceb14aa 100644 --- a/src/Composer/Manager.php +++ b/src/Composer/Manager.php @@ -140,6 +140,40 @@ public function removePackages(array $packageNames) $this->writePackages($requirements); } + /** + * getPackageVersions returns version numbers for the specified packages + */ + public function getPackageVersions(array $packageNames): array + { + $result = []; + $packages = $this->listAllPackages(); + + foreach ($packageNames as $wantPackage) { + $wantPackageLower = mb_strtolower($wantPackage); + + foreach ($packages as $package) { + if (!isset($package['name'])) { + continue; + } + if (mb_strtolower($package['name']) === $wantPackageLower) { + $result[$wantPackage] = $package['version'] ?? null; + } + } + } + + return $result; + } + + /** + * hasPackage returns true if the specified package is installed + */ + public function hasPackage($name): bool + { + $name = mb_strtolower($name); + + return array_key_exists($name, $this->getPackageVersions([$name])); + } + /** * listPackages returns a list of directly installed packages */ @@ -226,6 +260,22 @@ public function addAuthCredentials($hostname, $username, $password, $type = null ]); } + /** + * getAuthCredentials returns auth credentials added to the config file + */ + public function getAuthCredentials($hostname, $type = null): ?array + { + if ($type === null) { + $type = 'http-basic'; + } + + $authFile = $this->getAuthPath(); + + $config = json_decode(file_get_contents($authFile), true); + + return $config[$type][$hostname] ?? null; + } + /** * makeComposer returns a new instance of composer */ diff --git a/src/Database/Concerns/HasRelationships.php b/src/Database/Concerns/HasRelationships.php index 6ef9c3854..001d46af2 100644 --- a/src/Database/Concerns/HasRelationships.php +++ b/src/Database/Concerns/HasRelationships.php @@ -19,7 +19,22 @@ use InvalidArgumentException; /** - * HasRelationships concern for a model + * HasRelationships concern for a model, using a cleaner declaration of relationships. + * + * Uses a similar approach to the relation methods used by Eloquent, but as separate properties + * that make the class file less cluttered. + * + * It should be declared with keys as the relation name, and value being a mixed array. + * The relation type $morphTo does not include a class name as the first value. + * + * Example: + * + * class Order extends Model + * { + * protected $hasMany = [ + * 'items' => Item::class + * ]; + * } * * @package october\database * @author Alexey Bobkov, Samuel Georges @@ -27,126 +42,116 @@ trait HasRelationships { /** - * Cleaner declaration of relationships. - * Uses a similar approach to the relation methods used by Eloquent, but as separate properties - * that make the class file less cluttered. - * - * It should be declared with keys as the relation name, and value being a mixed array. - * The relation type $morphTo does not include a class name as the first value. + * @var array hasOne related record, inverse of belongsTo. * - * Example: - * class Order extends Model - * { - * protected $hasMany = [ - * 'items' => 'Item' - * ]; - * } + * protected $hasOne = [ + * 'owner' => [User::class, 'key' => 'user_id'] + * ]; * - * @var array */ - public $hasMany = []; + public $hasOne = []; /** - * protected $hasOne = [ - * 'owner' => ['User', 'key' => 'user_id'] - * ]; + * @var array hasMany related records, inverse of belongsTo. * - * @var array + * protected $hasMany = [ + * 'items' => Item::class + * ]; */ - public $hasOne = []; + public $hasMany = []; /** + * @var array belongsTo another record with a local key attribute + * * protected $belongsTo = [ - * 'parent' => ['Category', 'key' => 'parent_id'] + * 'parent' => [Category::class, 'key' => 'parent_id'] * ]; - * - * @var array */ public $belongsTo = []; /** + * @var array belongsToMany to multiple records using a join table. + * * protected $belongsToMany = [ - * 'groups' => ['Group', 'table'=> 'join_groups_users'] + * 'groups' => [Group::class, 'table'=> 'join_groups_users'] * ]; - * - * @var array */ public $belongsToMany = []; /** + * @var array morphTo another record using local key and type attributes + * * protected $morphTo = [ * 'pictures' => [] * ]; - * - * @var array */ public $morphTo = []; /** + * @var array morphOne related record, inverse of morphTo. + * * protected $morphOne = [ - * 'log' => ['History', 'name' => 'user'] + * 'log' => [History::class, 'name' => 'user'] * ]; - * - * @var array */ public $morphOne = []; /** + * @var array morphMany related records, inverse of morphTo. + * * protected $morphMany = [ - * 'log' => ['History', 'name' => 'user'] + * 'log' => [History::class, 'name' => 'user'] * ]; - * - * @var array */ public $morphMany = []; /** + * @var array morphToMany to multiple records using a join table. + * * protected $morphToMany = [ - * 'tag' => ['Tag', 'table' => 'tagables', 'name' => 'tagable'] + * 'tag' => [Tag::class, 'table' => 'tagables', 'name' => 'tagable'] * ]; - * - * @var array */ public $morphToMany = []; /** - * @var array + * @var array morphedByMany */ public $morphedByMany = []; /** + * @var array attachOne file attachment. + * * protected $attachOne = [ - * 'picture' => ['October\Rain\Database\Attach\File', 'public' => false] + * 'picture' => [\October\Rain\Database\Attach\File::class, 'public' => false] * ]; - * - * @var array */ public $attachOne = []; /** + * @var array attachMany file attachments. + * * protected $attachMany = [ - * 'pictures' => ['October\Rain\Database\Attach\File', 'name'=> 'imageable'] + * 'pictures' => [\October\Rain\Database\Attach\File::class, 'name'=> 'imageable'] * ]; - * - * @var array */ public $attachMany = []; /** + * @var array hasManyThrough is related records through another record. + * * protected $hasManyThrough = [ - * 'posts' => ['Posts', 'through' => 'User'] + * 'posts' => [Post::class, 'through' => User::class] * ]; - * - * @var array */ public $hasManyThrough = []; /** + * @var array hasOneThrough is a related record through another record. + * * protected $hasOneThrough = [ - * 'post' => ['Posts', 'through' => 'User'] + * 'post' => [Post::class, 'through' => User::class] * ]; - * - * @var array */ public $hasOneThrough = []; diff --git a/src/Database/Model.php b/src/Database/Model.php index 2f165a989..48be9c88a 100644 --- a/src/Database/Model.php +++ b/src/Database/Model.php @@ -1,9 +1,11 @@ format('Y-m-d H:i:s.u'), $value->getTimezone() ); } if (is_numeric($value)) { - return Argon::createFromTimestamp($value); + return Date::createFromTimestamp($value); } if ($this->isStandardDateFormat($value)) { - return Argon::createFromFormat('Y-m-d', $value)->startOfDay(); + return Date::createFromFormat('Y-m-d', $value)->startOfDay(); } - return Argon::createFromFormat( - str_replace('.v', '.u', $this->getDateFormat()), - $value - ); - } + $format = $this->getDateFormat(); - /** - * fromDateTime convert a DateTime to a storable string. - * @param \DateTime|int $value - * @return string - */ - public function fromDateTime($value) - { - if (is_null($value)) { - return $value; + try { + $date = Date::createFromFormat($format, $value); + } + catch (InvalidArgumentException $ex) { + $date = false; } - return parent::fromDateTime($value); + return $date ?: Date::parse($value); } /** diff --git a/src/Database/Models/DeferredBinding.php b/src/Database/Models/DeferredBinding.php index 139ef1fb2..b00c5175f 100644 --- a/src/Database/Models/DeferredBinding.php +++ b/src/Database/Models/DeferredBinding.php @@ -55,6 +55,24 @@ public function beforeCreate() return false; } + /** + * getPivotDataForBind strips attributes beginning with an underscore, allowing + * meta data to be stored using the column alongside the data. + */ + public function getPivotDataForBind(): array + { + $data = []; + + foreach ((array) $this->pivot_data as $key => $value) { + if (str_starts_with($key, '_')) { + continue; + } + + $data[$key] = $value; + } + return $data; + } + /** * findBindingRecord finds a duplicate binding record */ diff --git a/src/Database/Relations/AttachOneOrMany.php b/src/Database/Relations/AttachOneOrMany.php index f20c3a336..2cf0c171f 100644 --- a/src/Database/Relations/AttachOneOrMany.php +++ b/src/Database/Relations/AttachOneOrMany.php @@ -110,10 +110,6 @@ public function addEagerConstraints(array $models) */ public function save(Model $model, $sessionKey = null) { - if ($sessionKey === null) { - $this->ensureAttachOneIsSingular(); - } - if (!array_key_exists('is_public', $model->attributes)) { $model->setAttribute('is_public', $this->isPublic()); } @@ -121,10 +117,12 @@ public function save(Model $model, $sessionKey = null) $model->setAttribute('field', $this->relationName); if ($sessionKey === null) { + $this->ensureAttachOneIsSingular(); return parent::save($model); } $this->add($model, $sessionKey); + return $model->save() ? $model : false; } @@ -133,16 +131,16 @@ public function save(Model $model, $sessionKey = null) */ public function create(array $attributes = [], $sessionKey = null) { - if ($sessionKey === null) { - $this->ensureAttachOneIsSingular(); - } - if (!array_key_exists('is_public', $attributes)) { $attributes = array_merge(['is_public' => $this->isPublic()], $attributes); } $attributes['field'] = $this->relationName; + if ($sessionKey === null) { + $this->ensureAttachOneIsSingular(); + } + $model = parent::create($attributes); if ($sessionKey !== null) { @@ -221,6 +219,7 @@ public function add(Model $model, $sessionKey = null) $this->parent->fireEvent('model.relation.add', [$this->relationName, $model]); } else { + $this->ensureAttachOneIsSingular($sessionKey); $this->parent->bindDeferred($this->relationName, $model, $sessionKey); } } @@ -319,9 +318,20 @@ protected function isModelRemovable($model): bool * ensureAttachOneIsSingular ensures AttachOne only has one attachment, * by deleting siblings for singular relations. */ - protected function ensureAttachOneIsSingular() + protected function ensureAttachOneIsSingular($sessionKey = null) { - if ($this instanceof AttachOne && $this->parent->exists) { + if (!$this instanceof AttachOne) { + return; + } + + if ($sessionKey) { + foreach ($this->withDeferred($sessionKey)->get() as $record) { + $this->parent->unbindDeferred($this->relationName, $record, $sessionKey); + } + return; + } + + if ($this->parent->exists) { $this->delete(); } } diff --git a/src/Database/Traits/BaseIdentifier.php b/src/Database/Traits/BaseIdentifier.php new file mode 100644 index 000000000..1e0a8c1cc --- /dev/null +++ b/src/Database/Traits/BaseIdentifier.php @@ -0,0 +1,67 @@ +bindEvent('model.saveInternal', function () { + $this->baseIdentifyAttributes(); + }); + } + + /** + * baseIdentifyAttributes + */ + public function baseIdentifyAttributes() + { + $baseidAttribute = $this->getBaseIdentifierColumnName(); + if (!$this->{$baseidAttribute}) { + $this->attributes[$baseidAttribute] = $this->getBaseIdentifierUniqueAttributeValue($baseidAttribute); + } + } + + /** + * generateBaseIdentifier returns a random encoded 64 bit number + */ + public function generateBaseIdentifier() + { + return rtrim(strtr(base64_encode(random_bytes(8)), '+/', '-_'), '='); + } + + /** + * getBaseIdentifierUniqueAttributeValue ensures a unique attribute value, if the value + * is already used another base identifier is created. Returns a safe value that is unique. + * @param string $name + * @return string + */ + protected function getBaseIdentifierUniqueAttributeValue($name) + { + $value = $this->generateBaseIdentifier(); + + while ($this->newQueryWithoutScopes()->where($name, $value)->count() > 0) { + $value = $this->generateBaseIdentifier(); + } + + return $value; + } + + /** + * getBaseIdentifierColumnName gets the name of the "baseid" column. + * @return string + */ + public function getBaseIdentifierColumnName() + { + return defined('static::BASEID') ? static::BASEID : 'baseid'; + } +} diff --git a/src/Database/Traits/DeferredBinding.php b/src/Database/Traits/DeferredBinding.php index 531c9c468..3af09366b 100644 --- a/src/Database/Traits/DeferredBinding.php +++ b/src/Database/Traits/DeferredBinding.php @@ -196,7 +196,7 @@ protected function commitDeferredOfType($sessionKey, $include = null, $exclude = $relationObj = $this->$relationName(); if ($binding->is_bind) { if (in_array($relationType, ['belongsToMany', 'morphToMany', 'morphedByMany'])) { - $relationObj->add($slaveModel, null, (array) $binding->pivot_data); + $relationObj->add($slaveModel, null, $binding->getPivotDataForBind()); } else { $relationObj->add($slaveModel); diff --git a/src/Database/Traits/Multisite.php b/src/Database/Traits/Multisite.php index 486f6e080..32583aad9 100644 --- a/src/Database/Traits/Multisite.php +++ b/src/Database/Traits/Multisite.php @@ -250,7 +250,7 @@ public function isMultisiteSyncEnabled() */ public function getMultisiteSyncSites() { - return Site::listSiteIds(); + return Site::listSiteIdsInContext(); } /** diff --git a/src/Database/Traits/SimpleTree.php b/src/Database/Traits/SimpleTree.php index ca362b53d..ecdf2eb35 100644 --- a/src/Database/Traits/SimpleTree.php +++ b/src/Database/Traits/SimpleTree.php @@ -114,6 +114,24 @@ public function getChildCount() return count($this->getAllChildren()); } + /** + * getParents returns an array of parents, this is a heavy query and can produce + * in multiple queries. + */ + public function getParents() + { + $result = []; + + $parent = $this; + $result[] = $parent; + + while ($parent = $parent->parent) { + $result[] = $parent; + } + + return array_reverse($result); + } + // // Scopes // diff --git a/src/Database/Traits/Sluggable.php b/src/Database/Traits/Sluggable.php index c750972a5..1129e2e21 100644 --- a/src/Database/Traits/Sluggable.php +++ b/src/Database/Traits/Sluggable.php @@ -5,7 +5,7 @@ use Exception; /** - * Sluggable trait + * Sluggable trait performs automatic slug generation for new models * * @package october\database * @author Alexey Bobkov, Samuel Georges diff --git a/src/Database/Traits/SluggableTree.php b/src/Database/Traits/SluggableTree.php new file mode 100644 index 000000000..728d9216c --- /dev/null +++ b/src/Database/Traits/SluggableTree.php @@ -0,0 +1,76 @@ +setFullSluggedValue($this); + } + + /** + * setFullSluggedValue will set the fullslug value on a model + */ + protected function setFullSluggedValue($model) + { + $fullslugAttr = $this->getFullSluggableFullSlugColumnName(); + $proposedSlug = $this->getFullSluggableAttributeValue($model); + + if ($model->{$fullslugAttr} != $proposedSlug) { + $model + ->newQuery() + ->where($model->getKeyName(), $model->getKey()) + ->update([$fullslugAttr => $proposedSlug]); + } + + if ($children = $model->children) { + foreach ($children as $child) { + $this->setFullSluggedValue($child); + } + } + } + + /** + * getFullSluggableAttributeValue + */ + protected function getFullSluggableAttributeValue($model, $fullslug = '') + { + $slugAttr = $this->getFullSluggableSlugColumnName(); + $fullslug = $model->{$slugAttr} . '/' . $fullslug; + + if ($parent = $model->parent()->withoutGlobalScopes()->first()) { + $fullslug = $this->getFullSluggableAttributeValue($parent, $fullslug); + } + + return rtrim($fullslug, '/'); + } + + /** + * getFullSluggableFullSlugColumnName gets the name of the "fullslug" column. + * @return string + */ + public function getFullSluggableFullSlugColumnName() + { + return defined('static::FULLSLUG') ? static::FULLSLUG : 'fullslug'; + } + + /** + * getFullSluggableSlugColumnName gets the name of the "slug" column. + * @return string + */ + public function getFullSluggableSlugColumnName() + { + return defined('static::SLUG') ? static::SLUG : 'slug'; + } +} diff --git a/src/Database/TreeCollection.php b/src/Database/TreeCollection.php index ce7bb5baf..7b9d0af57 100644 --- a/src/Database/TreeCollection.php +++ b/src/Database/TreeCollection.php @@ -12,23 +12,20 @@ class TreeCollection extends Collection { /** * toNested converts a flat collection of nested set models to an set where - * children is eager loaded - * @param bool $removeOrphans Remove nodes that exist without their parents. + * children is eager loaded. removeOrphans removes nodes that exist without + * their parents. + * @param bool $removeOrphans * @return \October\Rain\Database\Collection */ public function toNested($removeOrphans = true) { - /* - * Set new collection for "children" relations - */ + // Set new collection for "children" relations $collection = $this->getDictionary(); foreach ($collection as $key => $model) { $model->setRelation('children', new Collection); } - /* - * Assign all child nodes to their parents - */ + // Assign all child nodes to their parents $nestedKeys = []; foreach ($collection as $key => $model) { if (!$parentKey = $model->getParentId()) { @@ -44,9 +41,7 @@ public function toNested($removeOrphans = true) } } - /* - * Remove processed nodes - */ + // Remove processed nodes foreach ($nestedKeys as $key) { unset($collection[$key]); } @@ -64,9 +59,7 @@ public function toNested($removeOrphans = true) */ public function listsNested($value, $key = null, $indent = '   ') { - /* - * Recursive helper function - */ + // Recursive helper function $buildCollection = function ($items, $depth = 0) use (&$buildCollection, $value, $key, $indent) { $result = []; @@ -92,9 +85,7 @@ public function listsNested($value, $key = null, $indent = '   ') return $result; }; - /* - * Build a nested collection - */ + // Build a nested collection $rootItems = $this->toNested(); return $buildCollection($rootItems); } diff --git a/src/Exception/AjaxException.php b/src/Exception/AjaxException.php index 87c5b3cad..91478897e 100644 --- a/src/Exception/AjaxException.php +++ b/src/Exception/AjaxException.php @@ -17,11 +17,14 @@ class AjaxException extends ExceptionBase /** * __construct the exception */ - public function __construct($contents) + public function __construct($contents = null) { if (is_string($contents)) { $contents = ['result' => $contents]; } + elseif (!is_array($contents)) { + $contents = []; + } $this->contents = $contents; @@ -35,4 +38,12 @@ public function getContents() { return $this->contents; } + + /** + * addContent is used to add extra data to an AJAX exception + */ + public function addContent(string $key, $val) + { + $this->contents[$key] = $val; + } } diff --git a/src/Extension/Container.php b/src/Extension/Container.php new file mode 100644 index 000000000..be184caa9 --- /dev/null +++ b/src/Extension/Container.php @@ -0,0 +1,51 @@ + [] ]; - /** - * @var array extendableCallbacks is used to extend the constructor of an extendable class. Eg: - * - * Class::extend(function($obj) { }) - * - */ - protected static $extendableCallbacks = []; - /** * @var array extendableStaticMethods is a collection of static methods used by behaviors */ @@ -55,8 +47,8 @@ public function extendableConstruct() // Apply init callbacks $classes = array_merge([get_class($this)], class_parents($this)); foreach ($classes as $class) { - if (isset(self::$extendableCallbacks[$class]) && is_array(self::$extendableCallbacks[$class])) { - foreach (self::$extendableCallbacks[$class] as $callback) { + if (isset(Container::$classCallbacks[$class]) && is_array(Container::$classCallbacks[$class])) { + foreach (Container::$classCallbacks[$class] as $callback) { call_user_func($callback, $this); } } @@ -97,21 +89,21 @@ public static function extendableExtendCallback($callback) { $class = get_called_class(); if ( - !isset(self::$extendableCallbacks[$class]) || - !is_array(self::$extendableCallbacks[$class]) + !isset(Container::$classCallbacks[$class]) || + !is_array(Container::$classCallbacks[$class]) ) { - self::$extendableCallbacks[$class] = []; + Container::$classCallbacks[$class] = []; } - self::$extendableCallbacks[$class][] = $callback; + Container::$classCallbacks[$class][] = $callback; } /** - * clearExtendedClasses clears the list of extended classes so they will be re-extended + * @deprecated use \October\Rain\Extension\Container::clearExtensions() */ public static function clearExtendedClasses() { - self::$extendableCallbacks = []; + Container::clearExtensions(); } /** @@ -543,9 +535,7 @@ public static function extendableCallStatic($name, $params = null) array_key_exists('implement', $defaultProperties) && ($implement = $defaultProperties['implement']) ) { - /* - * Apply extensions - */ + // Apply extensions if (is_string($implement)) { $uses = explode(',', $implement); } diff --git a/src/Extension/ExtensionTrait.php b/src/Extension/ExtensionTrait.php index 11d3c795a..6aa966681 100644 --- a/src/Extension/ExtensionTrait.php +++ b/src/Extension/ExtensionTrait.php @@ -10,29 +10,27 @@ trait ExtensionTrait { /** - * @var array Used to extend the constructor of an extension class. Eg: - * - * BehaviorClass::extend(function($obj) { }) - * + * @var string extendableStaticCalledClass is the calling class when using a static method. */ - protected static $extensionCallbacks = []; + public static $extendableStaticCalledClass = null; /** - * @var string The calling class when using a static method. + * @var array extensionHidden are properties and methods that cannot be accessed. */ - public static $extendableStaticCalledClass = null; - protected $extensionHidden = [ 'fields' => [], 'methods' => ['extensionIsHiddenField', 'extensionIsHiddenMethod'] ]; + /** + * extensionApplyInitCallbacks + */ public function extensionApplyInitCallbacks() { $classes = array_merge([get_class($this)], class_parents($this)); foreach ($classes as $class) { - if (isset(self::$extensionCallbacks[$class]) && is_array(self::$extensionCallbacks[$class])) { - foreach (self::$extensionCallbacks[$class] as $callback) { + if (isset(Container::$extensionCallbacks[$class]) && is_array(Container::$extensionCallbacks[$class])) { + foreach (Container::$extensionCallbacks[$class] as $callback) { call_user_func($callback, $this); } } @@ -40,7 +38,7 @@ public function extensionApplyInitCallbacks() } /** - * Helper method for `::extend()` static method + * extensionExtendCallback is a helper method for `::extend()` static method * @param callable $callback * @return void */ @@ -48,35 +46,50 @@ public static function extensionExtendCallback($callback) { $class = get_called_class(); if ( - !isset(self::$extensionCallbacks[$class]) || - !is_array(self::$extensionCallbacks[$class]) + !isset(Container::$extensionCallbacks[$class]) || + !is_array(Container::$extensionCallbacks[$class]) ) { - self::$extensionCallbacks[$class] = []; + Container::$extensionCallbacks[$class] = []; } - self::$extensionCallbacks[$class][] = $callback; + Container::$extensionCallbacks[$class][] = $callback; } + /** + * extensionHideField + */ protected function extensionHideField($name) { $this->extensionHidden['fields'][] = $name; } + /** + * extensionHideMethod + */ protected function extensionHideMethod($name) { $this->extensionHidden['methods'][] = $name; } + /** + * extensionIsHiddenField + */ public function extensionIsHiddenField($name) { return in_array($name, $this->extensionHidden['fields']); } + /** + * extensionIsHiddenMethod + */ public function extensionIsHiddenMethod($name) { return in_array($name, $this->extensionHidden['methods']); } + /** + * getCalledExtensionClass + */ public static function getCalledExtensionClass() { return self::$extendableStaticCalledClass; diff --git a/src/Foundation/Exception/Handler.php b/src/Foundation/Exception/Handler.php index f7a161659..31007a76f 100644 --- a/src/Foundation/Exception/Handler.php +++ b/src/Foundation/Exception/Handler.php @@ -144,7 +144,7 @@ public function render($request, Throwable $exception) * }); */ $statusCode = $this->getStatusCode($exception); - if ($event = Event::fire('exception.beforeRender', [$exception, $statusCode, $request], true)) { + if (($event = Event::fire('exception.beforeRender', [$exception, $statusCode, $request], true)) !== null) { return Response::make($event, $statusCode); } diff --git a/src/Html/UrlServiceProvider.php b/src/Html/UrlServiceProvider.php index 7987973a4..0b8f45dfe 100644 --- a/src/Html/UrlServiceProvider.php +++ b/src/Html/UrlServiceProvider.php @@ -59,7 +59,10 @@ public function registerRelativeHelper() $provider = $this->app['url']; $provider->macro('toRelative', function($url) use ($provider) { - return parse_url($provider->to($url), PHP_URL_PATH); + $fullUrl = $provider->to($url); + return parse_url($fullUrl, PHP_URL_PATH) + . (($query = parse_url($fullUrl, PHP_URL_QUERY)) ? '?' . $query : '') + . (($fragment = parse_url($fullUrl, PHP_URL_FRAGMENT)) ? '#' . $fragment : ''); }); } diff --git a/src/Mail/FakeMailer.php b/src/Mail/FakeMailer.php index f06764703..895168f11 100644 --- a/src/Mail/FakeMailer.php +++ b/src/Mail/FakeMailer.php @@ -65,6 +65,8 @@ public function buildMailable($view, $data, $callback, $queued = false) $mailable->locale('en'); + $mailable->siteContext(1); + if ($queued) { $mailable->view($view)->withSerializedData($data); } diff --git a/src/Mail/MailManager.php b/src/Mail/MailManager.php index 225e327e0..142256059 100644 --- a/src/Mail/MailManager.php +++ b/src/Mail/MailManager.php @@ -19,6 +19,9 @@ class MailManager extends MailManagerBase */ protected function resolve($name) { + // Extensibility + $this->app['events']->dispatch('mailer.beforeResolve', [$this, $name]); + $config = $this->getConfig($name); if (is_null($config)) { @@ -46,6 +49,9 @@ protected function resolve($name) $this->setGlobalAddress($mailer, $config, $type); } + // Extensibility + $this->app['events']->dispatch('mailer.resolve', [$this, $name, $mailer]); + return $mailer; } } diff --git a/src/Mail/MailServiceProvider.php b/src/Mail/MailServiceProvider.php index f0bd5d427..67a084686 100644 --- a/src/Mail/MailServiceProvider.php +++ b/src/Mail/MailServiceProvider.php @@ -16,13 +16,13 @@ class MailServiceProvider extends MailServiceProviderBase protected function registerIlluminateMailer() { $this->app->singleton('mail.manager', function ($app) { - // Extensibility + // @deprecated use mailer.beforeResolve or callBeforeResolving $this->app['events']->dispatch('mailer.beforeRegister', [$this]); // Inheritance $manager = new MailManager($app); - // Extensibility + // @deprecated use mailer.resolve or callAfterResolving $this->app['events']->dispatch('mailer.register', [$this, $manager]); return $manager; diff --git a/src/Mail/Mailable.php b/src/Mail/Mailable.php index 4cf7b1cfa..1a97eaa25 100644 --- a/src/Mail/Mailable.php +++ b/src/Mail/Mailable.php @@ -1,6 +1,7 @@ viewData['_current_locale'] = $this->locale ?: App::getLocale(); + $this->viewData['_current_site'] = $this->siteContext ?: Site::getSiteIdFromContext(); + foreach ($data as $param => $value) { $this->viewData[$param] = $this->getSerializedPropertyValue($value); } @@ -71,4 +84,60 @@ protected function buildSubject($message) return $this; } + + /** + * siteContext sets the site context of the message. + * + * @param string $siteId + * @return $this + */ + public function siteContext($siteId) + { + $this->siteContext = $siteId; + + return $this; + } + + /** + * withLocale acts as a hook to also apply the site context + * + * @param string $locale + * @param \Closure $callback + * @return mixed + */ + public function withLocale($locale, $callback) + { + if (!$this->siteContext) { + return parent::withLocale($locale, $callback); + } + + return Site::withContext($this->siteContext, function() use ($locale, $callback) { + return parent::withLocale($locale, $callback); + }); + } + + /** + * forceMailer forces sending using a different mail driver, useful if lazy loading + * the mail driver configuration for multisite. + * @param string $mailer + * @return $this + */ + public function forceMailer($mailer) + { + $this->forceMailer = $mailer; + + return $this; + } + + /** + * mailer sets the name of the mailer that should send the message. + * @param string $mailer + * @return $this + */ + public function mailer($mailer) + { + $this->mailer = $this->forceMailer ?: $mailer; + + return $this; + } } diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php index 519caf838..19c3908bb 100644 --- a/src/Mail/Mailer.php +++ b/src/Mail/Mailer.php @@ -1,5 +1,7 @@ setGlobalToAndRemoveCcAndBcc($message); } - /** - * @event mailer.prepareSend - * Fires before the mailer processes the sending action - * - * Parameters: - * - $view: View code as a string - * - $message: Illuminate\Mail\Message object, check Swift_Mime_SimpleMessage for useful functions. - * - * Example usage (stops the sending process): - * - * Event::listen('mailer.prepareSend', function ((\October\Rain\Mail\Mailer) $mailerInstance, (string) $view, (\Illuminate\Mail\Message) $message) { - * return false; - * }); - * - * Or - * - * $mailerInstance->bindEvent('mailer.prepareSend', function ((string) $view, (\Illuminate\Mail\Message) $message) { - * return false; - * }); - * - */ + /** + * @event mailer.prepareSend + * Fires before the mailer processes the sending action + * + * Parameters: + * - $view: View code as a string + * - $message: Illuminate\Mail\Message object, check Swift_Mime_SimpleMessage for useful functions. + * + * Example usage (stops the sending process): + * + * Event::listen('mailer.prepareSend', function ((\October\Rain\Mail\Mailer) $mailerInstance, (string) $view, (\Illuminate\Mail\Message) $message) { + * return false; + * }); + * + * Or + * + * $mailerInstance->bindEvent('mailer.prepareSend', function ((string) $view, (\Illuminate\Mail\Message) $message) { + * return false; + * }); + * + */ if ( ($this->fireEvent('mailer.prepareSend', [$view, $message], true) === false) || (Event::fire('mailer.prepareSend', [$this, $view, $message], true) === false) @@ -284,6 +286,10 @@ protected function buildQueueMailable($view, $data, $callback, $queue) { $mailable = new Mailable; + $mailable->locale(App::getLocale()); + + $mailable->siteContext(Site::getSiteIdFromContext()); + $mailable->view($view)->withSerializedData($data); if ($queue !== null) { @@ -294,6 +300,20 @@ protected function buildQueueMailable($view, $data, $callback, $queue) call_user_func($callback, $mailable); } + /** + * @event mailer.buildQueueMailable + * Process the mailable object used when adding mail to the queue + * + * Example usage: + * + * Event::listen('mailer.buildQueueMailable', function ((\October\Rain\Mail\Mailer) $mailerInstance, (\October\Rain\Mail\Mailable) $mailable) { + * $mailable->mailer('smtp'); + * }); + * + */ + $this->fireEvent('mailer.buildQueueMailable', [$mailable]); + Event::fire('mailer.buildQueueMailable', [$this, $mailable]); + return $mailable; } diff --git a/src/Scaffold/Console/CreateSeeder.php b/src/Scaffold/Console/CreateSeeder.php index d66e21888..a4cba9b32 100644 --- a/src/Scaffold/Console/CreateSeeder.php +++ b/src/Scaffold/Console/CreateSeeder.php @@ -31,10 +31,9 @@ class CreateSeeder extends GeneratorCommandBase public function makeStubs() { if ($this->isAppNamespace()) { - $this->makeStub('seeder/create_app_seeder.stub', 'database/seeds/{{studly_name}}.php'); - } - else { - $this->makeStub('seeder/create_seeder.stub', 'updates/{{snake_name}}.php'); + $this->makeStub('seeder/create_app_seeder.stub', 'database/seeders/{{studly_name}}.php'); + } else { + $this->makeStub('seeder/create_seeder.stub', 'updates/seeders/{{studly_name}}.php'); } } diff --git a/src/Scaffold/Console/controller/_list_toolbar.stub b/src/Scaffold/Console/controller/_list_toolbar.stub index b5dfc04e8..b89b55f87 100644 --- a/src/Scaffold/Console/controller/_list_toolbar.stub +++ b/src/Scaffold/Console/controller/_list_toolbar.stub @@ -10,7 +10,8 @@ data-request="onDelete" data-list-checked-trigger data-list-checked-request - data-stripe-load-indicator> + data-stripe-load-indicator + disabled> diff --git a/src/Scaffold/Console/seeder/create_seeder.stub b/src/Scaffold/Console/seeder/create_seeder.stub index 3f0a51197..77b076b16 100644 --- a/src/Scaffold/Console/seeder/create_seeder.stub +++ b/src/Scaffold/Console/seeder/create_seeder.stub @@ -1,4 +1,4 @@ -app->beforeResolving($name, $callback); + + if ($this->app->resolved($name)) { + $callback($this->app->make($name), $this->app); + } + } } diff --git a/src/Translation/Translator.php b/src/Translation/Translator.php index ea288ebbc..6dbca496d 100644 --- a/src/Translation/Translator.php +++ b/src/Translation/Translator.php @@ -24,6 +24,16 @@ public function get($key, array $replace = [], $locale = null, $fallback = true) return $line; } + // This is debug code to determine if language keys are + // migrated to JSON or translated in the first place + // + // $locale = $locale ?: $this->locale; + // $val = parent::get($key, $replace, $locale, $fallback); + // if (!isset($this->loaded['*']['*'][$locale][$key])) { + // return is_string($val) ? '→'.$val.'←' : $val; + // } + // return $val; + return parent::get($key, $replace, $locale, $fallback); }