diff --git a/framework/core/src/Database/InsertsViaSubqueryTrait.php b/framework/core/src/Database/InsertsViaSubqueryTrait.php new file mode 100644 index 000000000..78d57418c --- /dev/null +++ b/framework/core/src/Database/InsertsViaSubqueryTrait.php @@ -0,0 +1,73 @@ + + */ + protected static $subqueryAttributes = []; + + /** + * Overriden so that some fields can be inserted via subquery. + * The goal here is to construct a subquery that returns primitives + * for the provided `attributes`, and uses additional subqueries for + * statically-specified subqueryAttributes. + */ + protected function insertAndSetId(Builder $query, $attributes) + { + $subqueryAttrNames = array_keys(static::$subqueryAttributes); + + $literalAttributes = array_diff_key($attributes, array_flip($subqueryAttrNames)); + + /** @var Builder */ + $insertRowSubquery = static::query()->limit(1); + + foreach ($literalAttributes as $attrName => $value) { + $parameter = $query->getGrammar()->parameter($value); + $insertRowSubquery->addBinding($value, 'select'); + $insertRowSubquery->selectRaw("$parameter as $attrName"); + } + + foreach (static::$subqueryAttributes as $attrName => $callback) { + $insertRowSubquery->selectSub($callback($this), $attrName); + } + + $attrNames = array_merge(array_keys($literalAttributes), $subqueryAttrNames); + $query->insertUsing($attrNames, $insertRowSubquery); + + // This should be accurate, as it's the same mechanism used by Laravel's `insertGetId`. + // See https://github.com/laravel/framework/blob/master/src/Illuminate/Database/Query/Processors/Processor.php#L30-L37. + /** @var MySqlConnection */ + $con = $query->getQuery()->getConnection(); + $idRaw = $con->getPdo()->lastInsertId($keyName = $this->getKeyName()); + $id = is_numeric($idRaw) ? (int) $idRaw : $idRaw; + + $this->setAttribute($keyName, $id); + + // This is necessary to get the computed value of saved attributes. + $this->exists = true; + $this->refresh(); + } +} diff --git a/framework/core/src/Post/Command/PostReplyHandler.php b/framework/core/src/Post/Command/PostReplyHandler.php index 40a52de7f..96de698d0 100644 --- a/framework/core/src/Post/Command/PostReplyHandler.php +++ b/framework/core/src/Post/Command/PostReplyHandler.php @@ -74,7 +74,7 @@ class PostReplyHandler // If this is the first post in the discussion, it's technically not a // "reply", so we won't check for that permission. - if ($discussion->post_number_index > 0) { + if ($discussion->first_post_id !== null) { $actor->assertCan('reply', $discussion); } diff --git a/framework/core/src/Post/Post.php b/framework/core/src/Post/Post.php index f9deff3d2..fae314772 100644 --- a/framework/core/src/Post/Post.php +++ b/framework/core/src/Post/Post.php @@ -10,6 +10,7 @@ namespace Flarum\Post; use Flarum\Database\AbstractModel; +use Flarum\Database\InsertsViaSubqueryTrait; use Flarum\Database\ScopeVisibilityTrait; use Flarum\Discussion\Discussion; use Flarum\Foundation\EventGeneratorTrait; @@ -41,6 +42,7 @@ class Post extends AbstractModel { use EventGeneratorTrait; use ScopeVisibilityTrait; + use InsertsViaSubqueryTrait; protected $table = 'posts'; @@ -102,6 +104,10 @@ class Post extends AbstractModel }); static::addGlobalScope(new RegisteredTypesScope); + + static::$subqueryAttributes['number'] = function (Post $post) { + return static::query()->where('discussion_id', $post->discussion_id)->selectRaw('max(number)+1'); + }; } /**