Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading $post->content in saving listener throws error if content is empty #3983

Open
clarkwinkelmann opened this issue Apr 29, 2024 · 2 comments
Labels

Comments

@clarkwinkelmann
Copy link
Member

clarkwinkelmann commented Apr 29, 2024

Current Behavior

Trying to read $post->content on a CommentPost inside Flarum\Post\Event\Saving will throw a 500 error if the user submitted an empty content value.

The problem would also happen when trying to read that attribute anytime if an extension allowed a comment post to be saved with an empty post, which Flarum doesn't allow by default.

I did my tests on Flarum 1.8 but I am certain the problem is still present in the 2.x branch because the code hasn't changed.

[2024-04-29 22:15:38] flarum.ERROR: TypeError: Flarum\Mentions\Formatter\UnparsePostMentions::__invoke(): Argument #2 ($xml) must be of type string, null given, called in /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Foundation/ContainerUtil.php on line 30 and defined in /home/clark/Projects/flarum-1.8/vendor/flarum/mentions/src/Formatter/UnparsePostMentions.php:34
Stack trace:
#0 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Foundation/ContainerUtil.php(30): Flarum\Mentions\Formatter\UnparsePostMentions->__invoke()
#1 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Formatter/Formatter.php(139): Flarum\Foundation\ContainerUtil::Flarum\Foundation\{closure}()
#2 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Post/CommentPost.php(134): Flarum\Formatter\Formatter->unparse()
#3 /home/clark/Projects/flarum-1.8/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php(620): Flarum\Post\CommentPost->getContentAttribute()
#4 /home/clark/Projects/flarum-1.8/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php(1991): Illuminate\Database\Eloquent\Model->mutateAttribute()
#5 /home/clark/Projects/flarum-1.8/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php(451): Illuminate\Database\Eloquent\Model->transformModelValue()
#6 /home/clark/Projects/flarum-1.8/vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php(430): Illuminate\Database\Eloquent\Model->getAttributeValue()
#7 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Database/AbstractModel.php(135): Illuminate\Database\Eloquent\Model->getAttribute()
#8 /home/clark/Projects/flarum-1.8/vendor/illuminate/database/Eloquent/Model.php(2029): Flarum\Database\AbstractModel->getAttribute()
#9 /home/clark/Projects/flarum-1.8/extend.php(17): Illuminate\Database\Eloquent\Model->__get()
#10 /home/clark/Projects/flarum-1.8/vendor/illuminate/events/Dispatcher.php(404): Flarum\Foundation\Site::{closure}()
#11 /home/clark/Projects/flarum-1.8/vendor/illuminate/events/Dispatcher.php(249): Illuminate\Events\Dispatcher->Illuminate\Events\{closure}()
#12 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Post/Command/PostReplyHandler.php(97): Illuminate\Events\Dispatcher->dispatch()
#13 /home/clark/Projects/flarum-1.8/vendor/illuminate/bus/Dispatcher.php(122): Flarum\Post\Command\PostReplyHandler->handle()
#14 /home/clark/Projects/flarum-1.8/vendor/illuminate/pipeline/Pipeline.php(128): Illuminate\Bus\Dispatcher->Illuminate\Bus\{closure}()
#15 /home/clark/Projects/flarum-1.8/vendor/illuminate/pipeline/Pipeline.php(103): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}()
#16 /home/clark/Projects/flarum-1.8/vendor/illuminate/bus/Dispatcher.php(132): Illuminate\Pipeline\Pipeline->then()
#17 /home/clark/Projects/flarum-1.8/vendor/illuminate/bus/Dispatcher.php(78): Illuminate\Bus\Dispatcher->dispatchNow()
#18 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Api/Controller/CreatePostController.php(62): Illuminate\Bus\Dispatcher->dispatch()
#19 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Api/Controller/AbstractSerializeController.php(116): Flarum\Api\Controller\CreatePostController->data()
#20 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Api/Controller/AbstractCreateController.php(22): Flarum\Api\Controller\AbstractSerializeController->handle()
#21 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/RouteHandlerFactory.php(41): Flarum\Api\Controller\AbstractCreateController->handle()
#22 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/ExecuteRoute.php(27): Flarum\Http\RouteHandlerFactory->Flarum\Http\{closure}()
#23 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\ExecuteRoute->process()
#24 /home/clark/Projects/flarum-1.8/workbench/flarum-ext-audit-free/src/Middlewares/SetLoggerActor.php(28): Laminas\Stratigility\Next->handle()
#25 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Kilowhat\Audit\Middlewares\SetLoggerActor->process()
#26 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Api/Middleware/ThrottleApi.php(33): Laminas\Stratigility\Next->handle()
#27 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Api\Middleware\ThrottleApi->process()
#28 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/CheckCsrfToken.php(44): Laminas\Stratigility\Next->handle()
#29 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\CheckCsrfToken->process()
#30 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/ResolveRoute.php(69): Laminas\Stratigility\Next->handle()
#31 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\ResolveRoute->process()
#32 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/SetLocale.php(51): Laminas\Stratigility\Next->handle()
#33 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\SetLocale->process()
#34 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/AuthenticateWithHeader.php(58): Laminas\Stratigility\Next->handle()
#35 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\AuthenticateWithHeader->process()
#36 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/AuthenticateWithSession.php(31): Laminas\Stratigility\Next->handle()
#37 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\AuthenticateWithSession->process()
#38 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/RememberFromCookie.php(52): Laminas\Stratigility\Next->handle()
#39 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\RememberFromCookie->process()
#40 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/StartSession.php(61): Laminas\Stratigility\Next->handle()
#41 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\StartSession->process()
#42 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Api/Middleware/FakeHttpMethods.php(29): Laminas\Stratigility\Next->handle()
#43 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Api\Middleware\FakeHttpMethods->process()
#44 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/ParseJsonBody.php(28): Laminas\Stratigility\Next->handle()
#45 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\ParseJsonBody->process()
#46 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/HandleErrors.php(57): Laminas\Stratigility\Next->handle()
#47 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\HandleErrors->process()
#48 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/InjectActorReference.php(25): Laminas\Stratigility\Next->handle()
#49 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\InjectActorReference->process()
#50 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/MiddlewarePipe.php(75): Laminas\Stratigility\Next->handle()
#51 /home/clark/Projects/flarum-1.8/vendor/middlewares/request-handler/src/RequestHandler.php(84): Laminas\Stratigility\MiddlewarePipe->process()
#52 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Middlewares\RequestHandler->process()
#53 /home/clark/Projects/flarum-1.8/vendor/middlewares/base-path-router/src/BasePathRouter.php(101): Laminas\Stratigility\Next->handle()
#54 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Middlewares\BasePathRouter->process()
#55 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Middleware/OriginalMessages.php(36): Laminas\Stratigility\Next->handle()
#56 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Laminas\Stratigility\Middleware\OriginalMessages->process()
#57 /home/clark/Projects/flarum-1.8/vendor/middlewares/base-path/src/BasePath.php(73): Laminas\Stratigility\Next->handle()
#58 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Middlewares\BasePath->process()
#59 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Middleware/ProcessIp.php(24): Laminas\Stratigility\Next->handle()
#60 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/Next.php(49): Flarum\Http\Middleware\ProcessIp->process()
#61 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/MiddlewarePipe.php(75): Laminas\Stratigility\Next->handle()
#62 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-stratigility/src/MiddlewarePipe.php(64): Laminas\Stratigility\MiddlewarePipe->process()
#63 /home/clark/Projects/flarum-1.8/vendor/laminas/laminas-httphandlerrunner/src/RequestHandlerRunner.php(73): Laminas\Stratigility\MiddlewarePipe->handle()
#64 /home/clark/Projects/flarum-1.8/vendor/flarum/core/src/Http/Server.php(45): Laminas\HttpHandlerRunner\RequestHandlerRunner->run()
#65 /home/clark/Projects/flarum-1.8/public/index.php(26): Flarum\Http\Server->listen()
#66 {main}  

Steps to Reproduce

Enable any extension that registers a formatter unparsing callback, like Mentions.

Add local extender, or use in an extension:

<?php

use Flarum\Extend;

return [
    // Register extenders here to customize your forum!
    (new Extend\Event())
        ->listen(\Flarum\Post\Event\Saving::class, function ($event) {
            // Just need to call the magic getter method, no need to do anything with the result
            $event->post->content;
        }),
];

Try creating discussion or reply without any body. See error.

Expected Behavior

$post->content should return null or an empty string

Screenshots

No response

Environment

  • Flarum version: 1.8.5
  • Webserver: apache
  • Hosting environment: local
  • PHP version: 8.1.28

Output of php flarum info

Output of "php flarum info", run this in terminal in your Flarum directory.

Possible Solution

Just like CommentPost::setContentAttribute() skips the formatter and sets the value to null directly

public function setContentAttribute(string $value, ?User $actor = null): void
{
$this->attributes['content'] = $value ? static::$formatter->parse($value, $this, $actor ?? $this->user) : null;

CommentPost::getContentAttribute() should also skip the formatter if the value is null and return it directly

public function getContentAttribute(string $value): string
{
return static::$formatter->unparse($value, $this);

The error trace will be different between Flarum 1.8 and 2.x, because in 1.8.5, Formatter::unparse() has no type check for $xml.

Because of that, the type error is only thrown when encountering the first unparse callback that type-hints its parameters according to the extender documentation, like Mentions does.

public function unparse($xml, $context = null)

public function __invoke($context, string $xml)

In the 2.x branch, a string $xml has been added, so it would error right here when called with null, even if there are no unparsing callbacks registered.

public function unparse(string $xml, mixed $context = null): string

I'd like to take this opportunity to suggest always parsing content, even empty strings. I don't have any concrete use case, but I feel like it would be more logical to always treat a comment post content as string. It would ensure any logic registered through TextFormatter runs on all posts by default. Extensions that don't want this behavior would have an easier time saving null in the database in the event listener rather than having to call the formatter themselves on the possibly empty content. I think this would require changes to the validator because since it looks at the XML which would now always have a value.

In any case, the getter needs to not error on null values because it's a valid value that can be set in the database.

Additional Context

Reported and discussed at https://discuss.flarum.org/d/34454-every-day-there-are-some-discussions-only-have-title-no-content

FoF Filter issue about its impact FriendsOfFlarum/filter#52

EDIT: adding this as a footnote here as I don't have time to create a separate issue, but the $value ? /*...*/ : null condition also means you can't post 0 as the body, which is definitely not desirable behaviour.

@clarkwinkelmann
Copy link
Member Author

@DavideIadeluca I received an email notification for a reply I suspect you now deleted. I assume you found a way to reproduce?

#4059 looks good

@DavideIadeluca
Copy link
Contributor

@clarkwinkelmann Yes, I was able to reproduce it after all. Shouldn't have deleted the comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants