chore(phpstan): enable phpstan in bundled extensions (#3667)

* feat(phpstan): pick up extended model relations typings
* feat(phpstan): pick up extended model date attributes
* feat(core): introduce `castAttribute` extender
Stops using `dates` as it's deprecated in laravel 8
* feat(phpstan): pick up extended model attributes through casts
* fix: extenders not resolved when declared namespace
* fix(phpstan): new model attributes are always nullable
* chore(phpstan): add helpful cache clearing command
* Apply fixes from StyleCI
* chore: improve extend files provider logic
* chore: rename `castAttribute` to just `cast`
* chore: update phpstan package to detect `cast` method
* chore: enable phpstan in bundled extensions
* chore: rebasing conflicts
* chore: rebasing conflicts
* chore: typings for latest 1.7 changes

Signed-off-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
Sami Mazouz 2023-01-19 21:49:38 +01:00 committed by GitHub
parent ccf9442d79
commit da1bf8da21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 215 additions and 138 deletions

View File

@ -13,6 +13,7 @@ use Flarum\Approval\Event\PostWasApproved;
use Flarum\Extend; use Flarum\Extend;
use Flarum\Post\Event\Hidden; use Flarum\Post\Event\Hidden;
use Flarum\Post\Event\Saving; use Flarum\Post\Event\Saving;
use Flarum\Post\Post;
return [ return [
(new Extend\Frontend('forum')) (new Extend\Frontend('forum'))
@ -30,4 +31,7 @@ return [
(new Extend\ServiceProvider()) (new Extend\ServiceProvider())
->register(AkismetProvider::class), ->register(AkismetProvider::class),
(new Extend\Model(Post::class))
->cast('is_spam', 'bool'),
]; ];

View File

@ -50,7 +50,7 @@ class ValidatePost
->withContent($post->content) ->withContent($post->content)
->withAuthorName($post->user->username) ->withAuthorName($post->user->username)
->withAuthorEmail($post->user->email) ->withAuthorEmail($post->user->email)
->withType($post->number == 1 ? 'forum-post' : 'reply') ->withType($post->number === 1 ? 'forum-post' : 'reply')
->withIp($post->ip_address) ->withIp($post->ip_address)
->withUserAgent($_SERVER['HTTP_USER_AGENT']) ->withUserAgent($_SERVER['HTTP_USER_AGENT'])
->checkSpam(); ->checkSpam();

View File

@ -28,15 +28,17 @@ return [
// Discussions should be approved by default // Discussions should be approved by default
(new Extend\Model(Discussion::class)) (new Extend\Model(Discussion::class))
->default('is_approved', true), ->default('is_approved', true)
->cast('is_approved', 'bool'),
// Posts should be approved by default // Posts should be approved by default
(new Extend\Model(Post::class)) (new Extend\Model(Post::class))
->default('is_approved', true), ->default('is_approved', true)
->cast('is_approved', 'bool'),
(new Extend\ApiSerializer(BasicDiscussionSerializer::class)) (new Extend\ApiSerializer(BasicDiscussionSerializer::class))
->attribute('isApproved', function ($serializer, Discussion $discussion) { ->attribute('isApproved', function ($serializer, Discussion $discussion) {
return (bool) $discussion->is_approved; return $discussion->is_approved;
}), }),
(new Extend\ApiSerializer(PostSerializer::class)) (new Extend\ApiSerializer(PostSerializer::class))

View File

@ -9,6 +9,7 @@
namespace Flarum\Approval\Listener; namespace Flarum\Approval\Listener;
use Carbon\Carbon;
use Flarum\Discussion\Discussion; use Flarum\Discussion\Discussion;
use Flarum\Flags\Flag; use Flarum\Flags\Flag;
use Flarum\Post\CommentPost; use Flarum\Post\CommentPost;
@ -55,7 +56,7 @@ class UnapproveNewContent
$flag->post_id = $post->id; $flag->post_id = $post->id;
$flag->type = 'approval'; $flag->type = 'approval';
$flag->created_at = time(); $flag->created_at = Carbon::now();
$flag->save(); $flag->save();
}); });

View File

@ -22,7 +22,7 @@ class UpdateDiscussionAfterPostApproval
$discussion->refreshCommentCount(); $discussion->refreshCommentCount();
$discussion->refreshLastPost(); $discussion->refreshLastPost();
if ($post->number == 1) { if ($post->number === 1) {
$discussion->is_approved = true; $discussion->is_approved = true;
$discussion->afterSave(function () use ($user) { $discussion->afterSave(function () use ($user) {

View File

@ -46,7 +46,7 @@ return [
->delete('/posts/{id}/flags', 'flags.delete', DeleteFlagsController::class), ->delete('/posts/{id}/flags', 'flags.delete', DeleteFlagsController::class),
(new Extend\Model(User::class)) (new Extend\Model(User::class))
->dateAttribute('read_flags_at'), ->cast('read_flags_at', 'datetime'),
(new Extend\Model(Post::class)) (new Extend\Model(Post::class))
->hasMany('flags', Flag::class, 'post_id'), ->hasMany('flags', Flag::class, 'post_id'),

View File

@ -9,6 +9,7 @@
namespace Flarum\Flags\Api\Controller; namespace Flarum\Flags\Api\Controller;
use Carbon\Carbon;
use Flarum\Api\Controller\AbstractListController; use Flarum\Api\Controller\AbstractListController;
use Flarum\Flags\Api\Serializer\FlagSerializer; use Flarum\Flags\Api\Serializer\FlagSerializer;
use Flarum\Flags\Flag; use Flarum\Flags\Flag;
@ -43,7 +44,7 @@ class ListFlagsController extends AbstractListController
$actor->assertRegistered(); $actor->assertRegistered();
$actor->read_flags_at = time(); $actor->read_flags_at = Carbon::now();
$actor->save(); $actor->save();
$flags = Flag::whereVisibleTo($actor) $flags = Flag::whereVisibleTo($actor)

View File

@ -12,6 +12,8 @@ namespace Flarum\Flags\Api\Serializer;
use Flarum\Api\Serializer\AbstractSerializer; use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\BasicUserSerializer; use Flarum\Api\Serializer\BasicUserSerializer;
use Flarum\Api\Serializer\PostSerializer; use Flarum\Api\Serializer\PostSerializer;
use Flarum\Flags\Flag;
use InvalidArgumentException;
class FlagSerializer extends AbstractSerializer class FlagSerializer extends AbstractSerializer
{ {
@ -20,11 +22,14 @@ class FlagSerializer extends AbstractSerializer
*/ */
protected $type = 'flags'; protected $type = 'flags';
/**
* {@inheritdoc}
*/
protected function getDefaultAttributes($flag) protected function getDefaultAttributes($flag)
{ {
if (! ($flag instanceof Flag)) {
throw new InvalidArgumentException(
get_class($this).' can only serialize instances of '.Flag::class
);
}
return [ return [
'type' => $flag->type, 'type' => $flag->type,
'reason' => $flag->reason, 'reason' => $flag->reason,

View File

@ -9,6 +9,7 @@
namespace Flarum\Flags\Command; namespace Flarum\Flags\Command;
use Carbon\Carbon;
use Flarum\Flags\Event\Created; use Flarum\Flags\Event\Created;
use Flarum\Flags\Flag; use Flarum\Flags\Flag;
use Flarum\Foundation\ValidationException; use Flarum\Foundation\ValidationException;
@ -99,7 +100,7 @@ class CreateFlagHandler
$flag->type = 'user'; $flag->type = 'user';
$flag->reason = Arr::get($data, 'attributes.reason'); $flag->reason = Arr::get($data, 'attributes.reason');
$flag->reason_detail = Arr::get($data, 'attributes.reasonDetail'); $flag->reason_detail = Arr::get($data, 'attributes.reasonDetail');
$flag->created_at = time(); $flag->created_at = Carbon::now();
$flag->save(); $flag->save();

View File

@ -11,7 +11,7 @@ namespace Flarum\Flags\Command;
use Flarum\Flags\Event\Deleting; use Flarum\Flags\Event\Deleting;
use Flarum\Flags\Event\FlagsWillBeDeleted; use Flarum\Flags\Event\FlagsWillBeDeleted;
use Flarum\Flags\Flag; use Flarum\Post\Post;
use Flarum\Post\PostRepository; use Flarum\Post\PostRepository;
use Illuminate\Events\Dispatcher; use Illuminate\Events\Dispatcher;
@ -39,7 +39,7 @@ class DeleteFlagsHandler
/** /**
* @param DeleteFlags $command * @param DeleteFlags $command
* @return Flag * @return Post
*/ */
public function handle(DeleteFlags $command) public function handle(DeleteFlags $command)
{ {

View File

@ -9,11 +9,23 @@
namespace Flarum\Flags; namespace Flarum\Flags;
use Carbon\Carbon;
use Flarum\Database\AbstractModel; use Flarum\Database\AbstractModel;
use Flarum\Database\ScopeVisibilityTrait; use Flarum\Database\ScopeVisibilityTrait;
use Flarum\Post\Post; use Flarum\Post\Post;
use Flarum\User\User; use Flarum\User\User;
/**
* @property int $post_id
* @property int $user_id
* @property string $type
* @property string $reason
* @property string $reason_detail
* @property Carbon $created_at
*
* @property-read Post $post
* @property-read User $user
*/
class Flag extends AbstractModel class Flag extends AbstractModel
{ {
use ScopeVisibilityTrait; use ScopeVisibilityTrait;

View File

@ -35,12 +35,15 @@ return [
(new Extend\Notification()) (new Extend\Notification())
->type(DiscussionLockedBlueprint::class, BasicDiscussionSerializer::class, ['alert']), ->type(DiscussionLockedBlueprint::class, BasicDiscussionSerializer::class, ['alert']),
(new Extend\Model(Discussion::class))
->cast('is_locked', 'bool'),
(new Extend\ApiSerializer(DiscussionSerializer::class)) (new Extend\ApiSerializer(DiscussionSerializer::class))
->attribute('isLocked', function (DiscussionSerializer $serializer, Discussion $discussion) { ->attribute('isLocked', function (DiscussionSerializer $serializer, Discussion $discussion) {
return (bool) $discussion->is_locked; return $discussion->is_locked;
}) })
->attribute('canLock', function (DiscussionSerializer $serializer, Discussion $discussion) { ->attribute('canLock', function (DiscussionSerializer $serializer, Discussion $discussion) {
return (bool) $serializer->getActor()->can('lock', $discussion); return $serializer->getActor()->can('lock', $discussion);
}), }),
(new Extend\Post()) (new Extend\Post())

View File

@ -18,7 +18,7 @@ class DiscussionPolicy extends AbstractPolicy
/** /**
* @param User $actor * @param User $actor
* @param Discussion $discussion * @param Discussion $discussion
* @return bool * @return string|void
*/ */
public function reply(User $actor, Discussion $discussion) public function reply(User $actor, Discussion $discussion)
{ {

View File

@ -9,6 +9,7 @@
namespace Flarum\Lock\Post; namespace Flarum\Lock\Post;
use Carbon\Carbon;
use Flarum\Post\AbstractEventPost; use Flarum\Post\AbstractEventPost;
use Flarum\Post\MergeableInterface; use Flarum\Post\MergeableInterface;
use Flarum\Post\Post; use Flarum\Post\Post;
@ -59,7 +60,7 @@ class DiscussionLockedPost extends AbstractEventPost implements MergeableInterfa
$post = new static; $post = new static;
$post->content = static::buildContent($isLocked); $post->content = static::buildContent($isLocked);
$post->created_at = time(); $post->created_at = Carbon::now();
$post->discussion_id = $discussionId; $post->discussion_id = $discussionId;
$post->user_id = $userId; $post->user_id = $userId;

View File

@ -16,6 +16,7 @@ use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use s9e\TextFormatter\Configurator; use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Parser\Tag;
class ConfigureMentions class ConfigureMentions
{ {
@ -39,7 +40,7 @@ class ConfigureMentions
$this->configureGroupMentions($config); $this->configureGroupMentions($config);
} }
private function configureUserMentions(Configurator $config) private function configureUserMentions(Configurator $config): void
{ {
$config->rendering->parameters['PROFILE_URL'] = $this->url->to('forum')->route('user', ['username' => '']); $config->rendering->parameters['PROFILE_URL'] = $this->url->to('forum')->route('user', ['username' => '']);
@ -66,9 +67,8 @@ class ConfigureMentions
} }
/** /**
* @param $tag * @param Tag $tag
* * @return bool|void
* @return bool
*/ */
public static function addUserId($tag) public static function addUserId($tag)
{ {
@ -81,7 +81,7 @@ class ConfigureMentions
} }
if (isset($user)) { if (isset($user)) {
$tag->setAttribute('id', $user->id); $tag->setAttribute('id', (string) $user->id);
$tag->setAttribute('displayname', $user->display_name); $tag->setAttribute('displayname', $user->display_name);
return true; return true;
@ -90,7 +90,7 @@ class ConfigureMentions
$tag->invalidate(); $tag->invalidate();
} }
private function configurePostMentions(Configurator $config) private function configurePostMentions(Configurator $config): void
{ {
$config->rendering->parameters['DISCUSSION_URL'] = $this->url->to('forum')->route('discussion', ['id' => '']); $config->rendering->parameters['DISCUSSION_URL'] = $this->url->to('forum')->route('discussion', ['id' => '']);
@ -122,8 +122,8 @@ class ConfigureMentions
} }
/** /**
* @param $tag * @param Tag $tag
* @return bool * @return bool|void
*/ */
public static function addPostId($tag, User $actor) public static function addPostId($tag, User $actor)
{ {
@ -132,8 +132,8 @@ class ConfigureMentions
->find($tag->getAttribute('id')); ->find($tag->getAttribute('id'));
if ($post) { if ($post) {
$tag->setAttribute('discussionid', (int) $post->discussion_id); $tag->setAttribute('discussionid', (string) $post->discussion_id);
$tag->setAttribute('number', (int) $post->number); $tag->setAttribute('number', (string) $post->number);
if ($post->user) { if ($post->user) {
$tag->setAttribute('displayname', $post->user->display_name); $tag->setAttribute('displayname', $post->user->display_name);
@ -171,7 +171,7 @@ class ConfigureMentions
/** /**
* @param $tag * @param $tag
* @return bool * @return bool|void
*/ */
public static function addGroupId($tag) public static function addGroupId($tag)
{ {
@ -208,7 +208,7 @@ class ConfigureMentions
$hexNumbers = Str::replace('#', '', $hexColor); $hexNumbers = Str::replace('#', '', $hexColor);
if (Str::length($hexNumbers) === 3) { if (Str::length($hexNumbers) === 3) {
$hexNumbers += $hexNumbers; $hexNumbers .= $hexNumbers;
} }
$r = hexdec(Str::substr($hexNumbers, 0, 2)); $r = hexdec(Str::substr($hexNumbers, 0, 2));

View File

@ -29,11 +29,11 @@ class FormatPostMentions
/** /**
* Configure rendering for post mentions. * Configure rendering for post mentions.
* *
* @param s9e\TextFormatter\Renderer $renderer * @param \s9e\TextFormatter\Renderer $renderer
* @param mixed $context * @param mixed $context
* @param string|null $xml * @param string|null $xml
* @param Psr\Http\Message\ServerRequestInterface $request * @param \Psr\Http\Message\ServerRequestInterface $request
* @return void * @return string
*/ */
public function __invoke(Renderer $renderer, $context, $xml, Request $request = null) public function __invoke(Renderer $renderer, $context, $xml, Request $request = null)
{ {

View File

@ -37,9 +37,9 @@ class FormatUserMentions
/** /**
* Configure rendering for user mentions. * Configure rendering for user mentions.
* *
* @param s9e\TextFormatter\Renderer $renderer * @param \s9e\TextFormatter\Renderer $renderer
* @param mixed $context * @param mixed $context
* @param string|null $xml * @param string $xml
* @return string $xml to be rendered * @return string $xml to be rendered
*/ */
public function __invoke(Renderer $renderer, $context, string $xml) public function __invoke(Renderer $renderer, $context, string $xml)

View File

@ -40,7 +40,7 @@ class UpdateMentionsMetadataWhenVisible
*/ */
public function handle($event) public function handle($event)
{ {
$content = $event->post->parsedContent; $content = $event->post->parsed_content;
$this->syncUserMentions( $this->syncUserMentions(
$event->post, $event->post,

View File

@ -26,6 +26,9 @@ return [
new Extend\Locales(__DIR__.'/locale'), new Extend\Locales(__DIR__.'/locale'),
(new Extend\Model(User::class))
->cast('nickname', 'string'),
(new Extend\User()) (new Extend\User())
->displayNameDriver('nickname', NicknameDriver::class), ->displayNameDriver('nickname', NicknameDriver::class),

View File

@ -58,5 +58,7 @@ class NicknameFullTextGambit implements GambitInterface
'id', 'id',
$this->getUserSearchSubQuery($searchValue) $this->getUserSearchSubQuery($searchValue)
); );
return true;
} }
} }

View File

@ -58,7 +58,7 @@ class ListTasksController extends AbstractListController
$total = $this->repository->query()->count(); $total = $this->repository->query()->count();
$document->addMeta('total', $total); $document->addMeta('total', (string) $total);
$document->addPaginationLinks( $document->addPaginationLinks(
$this->url->to('api')->route('package-manager.tasks.index'), $this->url->to('api')->route('package-manager.tasks.index'),

View File

@ -9,7 +9,14 @@
namespace Flarum\PackageManager\Command; namespace Flarum\PackageManager\Command;
interface BusinessCommandInterface use Flarum\PackageManager\Task\Task;
abstract class AbstractActionCommand
{ {
public function getOperationName(): string; /**
* @var Task|null
*/
public $task = null;
abstract public function getOperationName(): string;
} }

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class CheckForUpdates implements BusinessCommandInterface class CheckForUpdates extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var \Flarum\User\User * @var \Flarum\User\User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class GlobalUpdate implements BusinessCommandInterface class GlobalUpdate extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var \Flarum\User\User * @var \Flarum\User\User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class MajorUpdate implements BusinessCommandInterface class MajorUpdate extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var \Flarum\User\User * @var \Flarum\User\User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class MinorUpdate implements BusinessCommandInterface class MinorUpdate extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var \Flarum\User\User * @var \Flarum\User\User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class RemoveExtension implements BusinessCommandInterface class RemoveExtension extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var User * @var User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class RequireExtension implements BusinessCommandInterface class RequireExtension extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var User * @var User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class UpdateExtension implements BusinessCommandInterface class UpdateExtension extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var User * @var User
*/ */

View File

@ -12,13 +12,8 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\User\User; use Flarum\User\User;
class WhyNot implements BusinessCommandInterface class WhyNot extends AbstractActionCommand
{ {
/**
* @var Task
*/
public $task = null;
/** /**
* @var User * @var User
*/ */

View File

@ -60,6 +60,7 @@ class ComposerAdapter
$exitCode = $this->application->run($input, $this->output); $exitCode = $this->application->run($input, $this->output);
chdir($currDir); chdir($currDir);
// @phpstan-ignore-next-line
$command = $input->__toString(); $command = $input->__toString();
$output = $this->output->fetch(); $output = $this->output->fetch();

View File

@ -10,7 +10,7 @@
namespace Flarum\PackageManager\Job; namespace Flarum\PackageManager\Job;
use Flarum\Bus\Dispatcher; use Flarum\Bus\Dispatcher;
use Flarum\PackageManager\Command\BusinessCommandInterface; use Flarum\PackageManager\Command\AbstractActionCommand;
use Flarum\PackageManager\Composer\ComposerAdapter; use Flarum\PackageManager\Composer\ComposerAdapter;
use Flarum\Queue\AbstractJob; use Flarum\Queue\AbstractJob;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
@ -19,7 +19,7 @@ use Throwable;
class ComposerCommandJob extends AbstractJob class ComposerCommandJob extends AbstractJob
{ {
/** /**
* @var BusinessCommandInterface * @var AbstractActionCommand
*/ */
protected $command; protected $command;
@ -28,7 +28,7 @@ class ComposerCommandJob extends AbstractJob
*/ */
protected $phpVersion; protected $phpVersion;
public function __construct(BusinessCommandInterface $command, string $phpVersion) public function __construct(AbstractActionCommand $command, string $phpVersion)
{ {
$this->command = $command; $this->command = $command;
$this->phpVersion = $phpVersion; $this->phpVersion = $phpVersion;

View File

@ -10,7 +10,7 @@
namespace Flarum\PackageManager\Job; namespace Flarum\PackageManager\Job;
use Flarum\Bus\Dispatcher as Bus; use Flarum\Bus\Dispatcher as Bus;
use Flarum\PackageManager\Command\BusinessCommandInterface; use Flarum\PackageManager\Command\AbstractActionCommand;
use Flarum\PackageManager\Task\Task; use Flarum\PackageManager\Task\Task;
use Flarum\Settings\SettingsRepositoryInterface; use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Queue\Queue; use Illuminate\Contracts\Queue\Queue;
@ -63,7 +63,7 @@ class Dispatcher
return $this; return $this;
} }
public function dispatch(BusinessCommandInterface $command): DispatcherResponse public function dispatch(AbstractActionCommand $command): DispatcherResponse
{ {
$queueJobs = ($this->runSyncOverride === false) || ($this->runSyncOverride !== true && $this->settings->get('flarum-package-manager.queue_jobs')); $queueJobs = ($this->runSyncOverride === false) || ($this->runSyncOverride !== true && $this->settings->get('flarum-package-manager.queue_jobs'));

View File

@ -26,7 +26,7 @@ class LastUpdateRun implements JsonSetting
protected $data; protected $data;
/** /**
* @var {'major'|'minor'|'global'} * @var string|null
*/ */
protected $activeUpdate; protected $activeUpdate;

View File

@ -13,16 +13,16 @@ use Carbon\Carbon;
use Flarum\Database\AbstractModel; use Flarum\Database\AbstractModel;
/** /**
* @property int id * @property int $id
* @property int status * @property int $status
* @property string operation * @property string $operation
* @property string command * @property string $command
* @property string package * @property string $package
* @property string output * @property string $output
* @property Carbon created_at * @property Carbon $created_at
* @property Carbon started_at * @property Carbon $started_at
* @property Carbon finished_at * @property Carbon $finished_at
* @property int peak_memory_used * @property float $peak_memory_used
*/ */
class Task extends AbstractModel class Task extends AbstractModel
{ {

View File

@ -48,6 +48,7 @@ class AuthController implements RequestHandlerInterface
$this->settings->get('flarum-pusher.app_key'), $this->settings->get('flarum-pusher.app_key'),
$this->settings->get('flarum-pusher.app_secret'), $this->settings->get('flarum-pusher.app_secret'),
$this->settings->get('flarum-pusher.app_id'), $this->settings->get('flarum-pusher.app_id'),
// @phpstan-ignore-next-line
['cluster' => $this->settings->get('flarum-pusher.app_cluster')] ['cluster' => $this->settings->get('flarum-pusher.app_cluster')]
); );

View File

@ -9,6 +9,7 @@
namespace Flarum\Pusher\Listener; namespace Flarum\Pusher\Listener;
use Flarum\Extension\ExtensionManager;
use Flarum\Post\Event\Posted; use Flarum\Post\Event\Posted;
use Flarum\User\Guest; use Flarum\User\Guest;
use Flarum\User\User; use Flarum\User\User;
@ -22,9 +23,15 @@ class PushNewPost
*/ */
protected $pusher; protected $pusher;
public function __construct(Pusher $pusher) /**
* @var ExtensionManager
*/
protected $extensions;
public function __construct(Pusher $pusher, ExtensionManager $extensions)
{ {
$this->pusher = $pusher; $this->pusher = $pusher;
$this->extensions = $extensions;
} }
public function handle(Posted $event) public function handle(Posted $event)
@ -43,6 +50,7 @@ class PushNewPost
return; return;
} }
// @phpstan-ignore-next-line
foreach ($response->channels as $name => $channel) { foreach ($response->channels as $name => $channel) {
$userId = Str::after($name, 'private-user'); $userId = Str::after($name, 'private-user');
@ -53,7 +61,7 @@ class PushNewPost
} }
if (count($channels)) { if (count($channels)) {
$tags = $event->post->discussion->tags; $tags = $this->extensions->isEnabled('flarum-tags') ? $event->post->discussion->tags : null;
$this->pusher->trigger($channels, 'newPost', [ $this->pusher->trigger($channels, 'newPost', [
'postId' => $event->post->id, 'postId' => $event->post->id,

View File

@ -29,6 +29,7 @@ class PusherProvider extends AbstractServiceProvider
$settings->get('flarum-pusher.app_key'), $settings->get('flarum-pusher.app_key'),
$settings->get('flarum-pusher.app_secret'), $settings->get('flarum-pusher.app_secret'),
$settings->get('flarum-pusher.app_id'), $settings->get('flarum-pusher.app_id'),
// @phpstan-ignore-next-line
$options $options
); );
}); });

View File

@ -9,6 +9,7 @@
use Flarum\Api\Controller\ListDiscussionsController; use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Api\Serializer\DiscussionSerializer; use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving; use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer; use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher; use Flarum\Discussion\Search\DiscussionSearcher;
@ -26,6 +27,9 @@ return [
->js(__DIR__.'/js/dist/forum.js') ->js(__DIR__.'/js/dist/forum.js')
->css(__DIR__.'/less/forum.less'), ->css(__DIR__.'/less/forum.less'),
(new Extend\Model(Discussion::class))
->cast('is_sticky', 'bool'),
(new Extend\Post()) (new Extend\Post())
->type(DiscussionStickiedPost::class), ->type(DiscussionStickiedPost::class),

View File

@ -43,12 +43,12 @@ class PinStickiedDiscussionsToTop
// reorder the unread ones up to the top. // reorder the unread ones up to the top.
$sticky = clone $query; $sticky = clone $query;
$sticky->where('is_sticky', true); $sticky->where('is_sticky', true);
$sticky->orders = null; unset($sticky->orders);
$query->union($sticky); $query->union($sticky);
$read = $query->newQuery() $read = $query->newQuery()
->selectRaw(1) ->selectRaw('1')
->from('discussion_user as sticky') ->from('discussion_user as sticky')
->whereColumn('sticky.discussion_id', 'id') ->whereColumn('sticky.discussion_id', 'id')
->where('sticky.user_id', '=', $filterState->getActor()->id) ->where('sticky.user_id', '=', $filterState->getActor()->id)
@ -58,14 +58,14 @@ class PinStickiedDiscussionsToTop
// argument in orderByRaw) for now due to a bug in Laravel which // argument in orderByRaw) for now due to a bug in Laravel which
// would add the bindings in the wrong order. // would add the bindings in the wrong order.
$query->orderByRaw('is_sticky and not exists ('.$read->toSql().') and last_posted_at > ? desc') $query->orderByRaw('is_sticky and not exists ('.$read->toSql().') and last_posted_at > ? desc')
->addBinding(array_merge($read->getBindings(), [$filterState->getActor()->read_time ?: 0]), 'union'); ->addBinding(array_merge($read->getBindings(), [$filterState->getActor()->marked_all_as_read_at ?: 0]), 'union');
$query->unionOrders = array_merge($query->unionOrders, $query->orders); $query->unionOrders = array_merge($query->unionOrders, $query->orders);
$query->unionLimit = $query->limit; $query->unionLimit = $query->limit;
$query->unionOffset = $query->offset; $query->unionOffset = $query->offset;
$query->limit = $sticky->limit = $query->offset + $query->limit; $query->limit = $sticky->limit = $query->offset + $query->limit;
$query->offset = $sticky->offset = null; unset($query->offset, $sticky->offset);
} }
} }
} }

View File

@ -9,6 +9,7 @@
namespace Flarum\Sticky\Post; namespace Flarum\Sticky\Post;
use Carbon\Carbon;
use Flarum\Post\AbstractEventPost; use Flarum\Post\AbstractEventPost;
use Flarum\Post\MergeableInterface; use Flarum\Post\MergeableInterface;
use Flarum\Post\Post; use Flarum\Post\Post;
@ -59,7 +60,7 @@ class DiscussionStickiedPost extends AbstractEventPost implements MergeableInter
$post = new static; $post = new static;
$post->content = static::buildContent($isSticky); $post->content = static::buildContent($isSticky);
$post->created_at = time(); $post->created_at = Carbon::now();
$post->discussion_id = $discussionId; $post->discussion_id = $discussionId;
$post->user_id = $userId; $post->user_id = $userId;

View File

@ -14,6 +14,7 @@ use Flarum\Discussion\Discussion;
use Flarum\Discussion\Event\Saving; use Flarum\Discussion\Event\Saving;
use Flarum\Discussion\Filter\DiscussionFilterer; use Flarum\Discussion\Filter\DiscussionFilterer;
use Flarum\Discussion\Search\DiscussionSearcher; use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Discussion\UserState;
use Flarum\Extend; use Flarum\Extend;
use Flarum\Post\Event\Deleted; use Flarum\Post\Event\Deleted;
use Flarum\Post\Event\Hidden; use Flarum\Post\Event\Hidden;
@ -24,6 +25,7 @@ use Flarum\Subscriptions\Listener;
use Flarum\Subscriptions\Notification\FilterVisiblePostsBeforeSending; use Flarum\Subscriptions\Notification\FilterVisiblePostsBeforeSending;
use Flarum\Subscriptions\Notification\NewPostBlueprint; use Flarum\Subscriptions\Notification\NewPostBlueprint;
use Flarum\Subscriptions\Query\SubscriptionFilterGambit; use Flarum\Subscriptions\Query\SubscriptionFilterGambit;
use Flarum\User\User;
return [ return [
(new Extend\Frontend('forum')) (new Extend\Frontend('forum'))
@ -33,6 +35,12 @@ return [
new Extend\Locales(__DIR__.'/locale'), new Extend\Locales(__DIR__.'/locale'),
(new Extend\Model(User::class))
->cast('last_read_post_number', 'integer'),
(new Extend\Model(UserState::class))
->cast('subscription', 'string'),
(new Extend\View) (new Extend\View)
->namespace('flarum-subscriptions', __DIR__.'/views'), ->namespace('flarum-subscriptions', __DIR__.'/views'),

View File

@ -34,7 +34,9 @@ return [
->css(__DIR__.'/less/admin.less'), ->css(__DIR__.'/less/admin.less'),
(new Extend\Model(User::class)) (new Extend\Model(User::class))
->dateAttribute('suspended_until'), ->cast('suspended_until', 'datetime')
->cast('suspend_reason', 'string')
->cast('suspend_message', 'string'),
(new Extend\ApiSerializer(UserSerializer::class)) (new Extend\ApiSerializer(UserSerializer::class))
->attributes(AddUserSuspendAttributes::class), ->attributes(AddUserSuspendAttributes::class),

View File

@ -9,6 +9,7 @@
namespace Flarum\Suspend\Listener; namespace Flarum\Suspend\Listener;
use Carbon\Carbon;
use DateTime; use DateTime;
use Flarum\Suspend\Event\Suspended; use Flarum\Suspend\Event\Suspended;
use Flarum\Suspend\Event\Unsuspended; use Flarum\Suspend\Event\Unsuspended;
@ -54,7 +55,7 @@ class SaveSuspensionToDatabase
$actor->assertCan('suspend', $user); $actor->assertCan('suspend', $user);
if ($attributes['suspendedUntil']) { if ($attributes['suspendedUntil']) {
$user->suspended_until = new DateTime($attributes['suspendedUntil']); $user->suspended_until = Carbon::createFromTimestamp((new DateTime($attributes['suspendedUntil']))->getTimestamp());
$user->suspend_reason = empty($attributes['suspendReason']) ? null : $attributes['suspendReason']; $user->suspend_reason = empty($attributes['suspendReason']) ? null : $attributes['suspendReason'];
$user->suspend_message = empty($attributes['suspendMessage']) ? null : $attributes['suspendMessage']; $user->suspend_message = empty($attributes['suspendMessage']) ? null : $attributes['suspendMessage'];
} else { } else {

View File

@ -34,7 +34,7 @@ class DiscussionPolicy extends AbstractPolicy
* @param User $actor * @param User $actor
* @param string $ability * @param string $ability
* @param Discussion $discussion * @param Discussion $discussion
* @return bool * @return string|void
*/ */
public function can(User $actor, $ability, Discussion $discussion) public function can(User $actor, $ability, Discussion $discussion)
{ {
@ -68,7 +68,7 @@ class DiscussionPolicy extends AbstractPolicy
* *
* @param User $actor * @param User $actor
* @param Discussion $discussion * @param Discussion $discussion
* @return bool * @return string|void
*/ */
public function tag(User $actor, Discussion $discussion) public function tag(User $actor, Discussion $discussion)
{ {

View File

@ -27,9 +27,9 @@ class GlobalPolicy extends AbstractPolicy
} }
/** /**
* @param Flarum\User\User $actor * @param User $actor
* @param string $ability * @param string $ability
* @return bool|void * @return string|void
*/ */
public function can(User $actor, string $ability) public function can(User $actor, string $ability)
{ {

View File

@ -13,6 +13,7 @@ use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\DiscussionSerializer; use Flarum\Api\Serializer\DiscussionSerializer;
use Flarum\Http\SlugManager; use Flarum\Http\SlugManager;
use Flarum\Tags\Tag; use Flarum\Tags\Tag;
use InvalidArgumentException;
class TagSerializer extends AbstractSerializer class TagSerializer extends AbstractSerializer
{ {
@ -39,6 +40,12 @@ class TagSerializer extends AbstractSerializer
*/ */
protected function getDefaultAttributes($tag) protected function getDefaultAttributes($tag)
{ {
if (! ($tag instanceof Tag)) {
throw new InvalidArgumentException(
get_class($this).' can only serialize instances of '.Tag::class
);
}
$attributes = [ $attributes = [
'name' => $tag->name, 'name' => $tag->name,
'description' => $tag->description, 'description' => $tag->description,

View File

@ -18,9 +18,11 @@ use Flarum\Post\Event\Deleted as PostDeleted;
use Flarum\Post\Event\Hidden as PostHidden; use Flarum\Post\Event\Hidden as PostHidden;
use Flarum\Post\Event\Posted; use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored as PostRestored; use Flarum\Post\Event\Restored as PostRestored;
use Flarum\Post\Post;
use Flarum\Tags\Event\DiscussionWasTagged; use Flarum\Tags\Event\DiscussionWasTagged;
use Flarum\Tags\Tag; use Flarum\Tags\Tag;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
class UpdateTagMetadata class UpdateTagMetadata
@ -124,9 +126,9 @@ class UpdateTagMetadata
} }
/** /**
* @param \Flarum\Discussion\Discussion $discussion * @param Discussion $discussion
* @param int $delta * @param int $delta
* @param Tag[]|null $tags * @param Collection<Tag>|null $tags
* @param Post $post: This is only used when a post has been hidden * @param Post $post: This is only used when a post has been hidden
*/ */
protected function updateTags(Discussion $discussion, $delta = 0, $tags = null, $post = null) protected function updateTags(Discussion $discussion, $delta = 0, $tags = null, $post = null)

View File

@ -9,6 +9,7 @@
namespace Flarum\Tags\Post; namespace Flarum\Tags\Post;
use Carbon\Carbon;
use Flarum\Post\AbstractEventPost; use Flarum\Post\AbstractEventPost;
use Flarum\Post\MergeableInterface; use Flarum\Post\MergeableInterface;
use Flarum\Post\Post; use Flarum\Post\Post;
@ -61,7 +62,7 @@ class DiscussionTaggedPost extends AbstractEventPost implements MergeableInterfa
$post = new static; $post = new static;
$post->content = static::buildContent($oldTagIds, $newTagIds); $post->content = static::buildContent($oldTagIds, $newTagIds);
$post->created_at = time(); $post->created_at = Carbon::now();
$post->discussion_id = $discussionId; $post->discussion_id = $discussionId;
$post->user_id = $userId; $post->user_id = $userId;

View File

@ -38,7 +38,7 @@ class TagFilterGambit extends AbstractRegexGambit implements FilterInterface
protected function conditions(SearchState $search, array $matches, $negate) protected function conditions(SearchState $search, array $matches, $negate)
{ {
$this->constrain($search->getQuery(), $matches[1], $negate); $this->constrain($search->getQuery(), $matches[1], $negate, $search->getActor());
} }
public function getFilterKey(): string public function getFilterKey(): string

View File

@ -15,6 +15,7 @@ use Flarum\Discussion\Discussion;
use Flarum\Group\Permission; use Flarum\Group\Permission;
use Flarum\User\User; use Flarum\User\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
/** /**
* @property int $id * @property int $id
@ -34,7 +35,13 @@ use Illuminate\Database\Eloquent\Builder;
* @property int $last_posted_discussion_id * @property int $last_posted_discussion_id
* @property int $last_posted_user_id * @property int $last_posted_user_id
* @property string $icon * @property string $icon
* @property TagState *
* @property TagState $state
* @property Tag|null $parent
* @property-read Collection<Tag> $children
* @property-read Collection<Discussion> $discussions
* @property Discussion|null $lastPostedDiscussion
* @property User|null $lastPostedUser
*/ */
class Tag extends AbstractModel class Tag extends AbstractModel
{ {

View File

@ -29,7 +29,7 @@ class TagRepository
/** /**
* @param array|string $relations * @param array|string $relations
* @param User $actor * @param User $actor
* @return Builder * @return Builder<Tag>
*/ */
public function with($relations, User $actor): Builder public function with($relations, User $actor): Builder
{ {
@ -64,9 +64,8 @@ class TagRepository
* user, or throw an exception. * user, or throw an exception.
* *
* @param int $id * @param int $id
* @param User $actor * @param User|null $actor
* @return Tag * @return Tag
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/ */
public function findOrFail($id, User $actor = null) public function findOrFail($id, User $actor = null)
{ {
@ -80,7 +79,7 @@ class TagRepository
* certain user. * certain user.
* *
* @param User|null $user * @param User|null $user
* @return \Illuminate\Database\Eloquent\Collection * @return \Illuminate\Database\Eloquent\Collection<Tag>
*/ */
public function all(User $user = null) public function all(User $user = null)
{ {
@ -106,9 +105,9 @@ class TagRepository
/** /**
* Scope a query to only include records that are visible to a user. * Scope a query to only include records that are visible to a user.
* *
* @param Builder $query * @param Builder<Tag> $query
* @param User $user * @param User|null $user
* @return Builder * @return Builder<Tag>
*/ */
protected function scopeVisibleTo(Builder $query, User $user = null) protected function scopeVisibleTo(Builder $query, User $user = null)
{ {

View File

@ -44,10 +44,13 @@ class Utf8SlugDriver implements SlugDriverInterface
*/ */
public function fromSlug(string $slug, User $actor): AbstractModel public function fromSlug(string $slug, User $actor): AbstractModel
{ {
return $this->repository /** @var Tag $tag */
$tag = $this->repository
->query() ->query()
->where('slug', urldecode($slug)) ->where('slug', urldecode($slug))
->whereVisibleTo($actor) ->whereVisibleTo($actor)
->firstOrFail(); ->firstOrFail();
return $tag;
} }
} }

View File

@ -31,7 +31,7 @@ class Validator implements ExtenderInterface
* Configure the validator. This is often used to adjust validation rules, but can be * Configure the validator. This is often used to adjust validation rules, but can be
* used to make other changes to the validator as well. * used to make other changes to the validator as well.
* *
* @param callable $callback * @param callable|class-string $callback
* *
* The callback can be a closure or invokable class, and should accept: * The callback can be a closure or invokable class, and should accept:
* - \Flarum\Foundation\AbstractValidator $flarumValidator: The Flarum validator wrapper * - \Flarum\Foundation\AbstractValidator $flarumValidator: The Flarum validator wrapper

View File

@ -236,7 +236,7 @@ class Extension implements Arrayable
} }
/** /**
* @return string * @return string|null
*/ */
public function getVersion() public function getVersion()
{ {

View File

@ -36,7 +36,7 @@ interface BlueprintInterface
/** /**
* Get the data to be stored in the notification. * Get the data to be stored in the notification.
* *
* @return array|null * @return mixed
*/ */
public function getData(); public function getData();

View File

@ -16,7 +16,7 @@ interface MailableInterface
/** /**
* Get the name of the view to construct a notification email with. * Get the name of the view to construct a notification email with.
* *
* @return string * @return string|array
*/ */
public function getEmailView(); public function getEmailView();

View File

@ -9,25 +9,26 @@
namespace Flarum\Post\Event; namespace Flarum\Post\Event;
use Flarum\Post\Post; use Flarum\Post\CommentPost;
use Flarum\User\User; use Flarum\User\User;
class Posted class Posted
{ {
/** /**
* @var \Flarum\Post\Post * @var CommentPost
*/ */
public $post; public $post;
/** /**
* @var User * @var User|null
*/ */
public $actor; public $actor;
/** /**
* @param \Flarum\Post\Post $post * @param CommentPost $post
* @param User|null $actor
*/ */
public function __construct(Post $post, User $actor = null) public function __construct(CommentPost $post, User $actor = null)
{ {
$this->post = $post; $this->post = $post;
$this->actor = $actor; $this->actor = $actor;

View File

@ -5,6 +5,34 @@ parameters:
level: 5 level: 5
paths: paths:
- framework/core/src - framework/core/src
- extensions/akismet/src
- extensions/akismet/extend.php
- extensions/approval/src
- extensions/approval/extend.php
- extensions/flags/src
- extensions/flags/extend.php
- extensions/likes/src
- extensions/likes/extend.php
- extensions/lock/src
- extensions/lock/extend.php
- extensions/mentions/src
- extensions/mentions/extend.php
- extensions/nicknames/src
- extensions/nicknames/extend.php
- extensions/package-manager/src
- extensions/package-manager/extend.php
- extensions/pusher/src
- extensions/pusher/extend.php
- extensions/statistics/src
- extensions/statistics/extend.php
- extensions/sticky/src
- extensions/sticky/extend.php
- extensions/subscriptions/src
- extensions/subscriptions/extend.php
- extensions/suspend/src
- extensions/suspend/extend.php
- extensions/tags/src
- extensions/tags/extend.php
excludePaths: excludePaths:
- *.blade.php - *.blade.php
checkMissingIterableValueType: false checkMissingIterableValueType: false