XF 2.2 How to use the attachment system

Taylor J

Well-known member
I've been trying to reverse engineer how to get the attachment system working with my blog addon for the past couple of days to no avail which is due to me taking extended breaks in creating this due to work and prepping for a out of state move.

So far I've added this to my controller
PHP:
/** @var \XF\Repository\Attachment $attachmentRepo */
            $attachmentRepo = $this->repository('XF:Attachment');
            $attachmentData = $attachmentRepo->getEditorData(
                'blogPost',
                $blogPost
            );
(I'm also assuming I have the $params->blog_id wrong and that needs to most likely be post_id for a blogs individual posting)

Which gives me an error on the "editor" page of
InvalidArgumentException: No attachment handler found for content type 'blogPost' in src/XF/Repository/Attachment.php at line 18

Now for one I am not at all sure how to even create a new content type or the actual handler for it, nor am I even sure I am headed in the right direction with this. I'm thinking I need to extend a class but can't determine which one to even do that to to create a new content type / handler.
 
I didn't want to make another thread about the same topic but I've since made some leeway on this but am currently still at the same error.

Error:

Code:
InvalidArgumentException: No attachment handler found for content type 'blog_post' in src/XF/Repository/Attachment.php at line 18
XF\Repository\Attachment->getEditorData() in src/addons/TaylorJ/UserBlogs/Pub/Controller/Blog.php at line 41
TaylorJ\UserBlogs\Pub\Controller\Blog->blogAddEdit() in src/addons/TaylorJ/UserBlogs/Pub/Controller/Blog.php at line 27
TaylorJ\UserBlogs\Pub\Controller\Blog->actionBlogAdd() in src/XF/Mvc/Dispatcher.php at line 352
XF\Mvc\Dispatcher->dispatchClass() in src/XF/Mvc/Dispatcher.php at line 259
XF\Mvc\Dispatcher->dispatchFromMatch() in src/XF/Mvc/Dispatcher.php at line 115
XF\Mvc\Dispatcher->dispatchLoop() in src/XF/Mvc/Dispatcher.php at line 57
XF\Mvc\Dispatcher->run() in src/XF/App.php at line 2487
XF\App->run() in src/XF.php at line 524
XF::runApp() in index.php at line 20
require() in /Users/taylorjones/.composer/vendor/laravel/valet/server.php at line 110

This is after following how the RM has setup a new content type and attachment handler.

Here is my entity

PHP:
    public static function getStructure(Structure $structure): Structure
    {
        $structure->table = 'xf_taylorj_userblogs_blog_post';
        $structure->shortName = 'TaylorJ\UserBlogs:BlogPost';
        $structure->contentType = 'taylorj_userblogs_blog_post';
        $structure->primaryKey = 'blog_post_id';
        $structure->columns = [
            'blog_post_id' => ['type' => self::UINT, 'autoIncrement' => true],
            'user_id' => ['type' => self::UINT, 'default' => \XF::visitor()->user_id],
            'blog_id' => ['type' => self::UINT],
            'blog_post_title' => ['type' => self::STR, 'maxLength' => 50, 'required' => true, 'censor' => true],
            'blog_post_content' => ['type' => self::STR, 'required' => true, 'censor' => true],
            'blog_post_creation_date' => ['type' => self::UINT, 'default' => \XF::$time],
            'blog_post_last_edit_date' => ['type' => self::UINT, 'default' => 0],
        ];
        $structure->relations = [
            'User' => [
                'entity'     => 'XF:User',
                'type'       => self::TO_ONE,
                'conditions' => 'user_id',
                'primary'    => true
            ],
            'Blog' => [
                'entity'    => 'TaylorJ\UserBlogs:Blog',
                'type'      => self::TO_ONE,
                'conditions'=> 'blog_id',
                'primary'   => true
            ],
            'Attachments' => [
                'entity' => 'XF:Attachment',
                'type' => self::TO_MANY,
                'conditions' => [
                    ['content_type', '=', 'taylorj_usersblogs_blog_post'],
                    ['content_id', '=', '$blog_post_id']
                ],
                'with' => 'Data',
                'order' => 'attach_date'
            ]
        ];

And my Attachment Handler (TaylorJ/UserBlogs/Attachment/BlogPost.php):

PHP:
<?php

namespace TaylorJ\UserBlogs\Attachment;

use XF\Entity\Attachment;
use XF\Mvc\Entity\Entity;

class BlogPost extends \XF\Attachment\AbstractHandler
{
    public function canView(Attachment $attachment, Entity $container, &$error = null)
    {
        return $container->canViewAttachments();
    }

    public function canManageAttachments(array $context, &$error = null)
    {
        $em = \XF::em();

        if (!empty($context['blog_post_id']))
        {
            $blogPost = $em->find('TaylorJ\UserBlogs:BlogPost', intval($context['blog_post_id']));
            if (!$blogPost || !$blogPost->canEdit())
            {
                return false;
            }

            return $blogPost->canUploadAndManageAttachments();
        }
        else
        {
            $blogPost = $em->create('TaylorJ\UserBlogs:BlogPost');
            return $blogPost->canUploadAndManageAttachments();
        }
    }

    public function onAttachmentDelete(Attachment $attachment, Entity $container = null)
    {
        return;
    }

    public function getConstraints(array $context)
    {
        return \XF::repository('XF:Attachment')->getDefaultAttachmentConstraints();
    }

    public function getContainerIdFromContext(array $context)
    {
        return isset($context['blog_post_id']) ? intval($context['blog_post_id']) : null;
    }

    public function getContainerLink(Entity $container, array $extraParams = [])
    {
        return \XF::app()->router('public')->buildLink('taylorj-userblogs', $container, $extraParams);
    }

    public function getContext(Entity $entity = null, array $extraContext = [])
    {
        if ($entity instanceof \TaylorJ\UserBlogs\Entity\BlogPost)
        {
            $extraContext['blog_post_id'] = $entity->blog_post_id;
        }
        else
        {
            throw new \InvalidArgumentException("Entity must be a blog post");
        }

        return $extraContext;
    }
}

Is there something else I'm missing here @Jeremy P / @Chris D ?
 
I did realize that I forgot to add the attachmentData as a view param in my controller but with that added the error hasn't changed.
 
I am blind, very very blind. I swore I looked for a Content Type route under the Development section in the AdminCP and didn't see anything but going through that process worked!
 
Is there a certain way for posts/blogPosts/what have you to be rendered to show attachments in the said post?

I am currently using {{ bb_code($blogPost.blog_post_content, 'taylor_userblogs_post', $blogPost) }} in my view template to be able to render any bb code actions a user took on their text, which works for that, but if the post has an attachment inserted in, happens on both thumbnail and full image, I am getting back the below image returned instead
1722280020106.webp
 
Is there a certain way for posts/blogPosts/what have you to be rendered to show attachments in the said post?

I am currently using {{ bb_code($blogPost.blog_post_content, 'taylor_userblogs_post', $blogPost) }} in my view template to be able to render any bb code actions a user took on their text, which works for that, but if the post has an attachment inserted in, happens on both thumbnail and full image, I am getting back the below image returned instead
View attachment 307478
I realized I had 'taylor_userblogs_post' rather than 'taylorj_userblogs_post' in the bb_code snippet but changing that didn't change anything unfortunately.
 
I noticed that XFRM and a few other addons that did use the attachment system that their entity contained the following
PHP:
    public function getBbCodeRenderOptions($context, $type)
    {
        return [
            'entity' => $this,
            'user' => $this->BlogPost->User,
            'attachments' => $this->Attachments,
            'viewAttachments' => $this->BlogPost->canViewAttachments()
        ];
    }

After adding that to mine (i took out the attach_count logic as I don't have that tracked for blog posts yet) it didn't change anything on already created posts nor new ones. The attachments are still appearing as in https://xenforo.com/community/threads/how-to-use-the-attachment-system.214171/post-1696489

Besides permissions this is one of the last things I need to fix for an alpha test before release and it's boggled my mind more than anything so far.
 
1722373939287.webp
edit: the picture above is showing entities as an empty array, sorry about the hover box

I think I'm making my way through this...??

I made sure my entity implemented RenderableContentInteface, but now I need to figure out why my attachments aren't being associated to their blogPosts, when they should be based on my database picture below?
1722374143908.webp
 
I know where I messed up but need some assistance on the fix @Jeremy P / anyone else.

The issue is due to how I am saving a blog post currently with attachements, the content id is being set as the blog_id rather than the blog_post_id:

PHP:
 protected function blogPostSaveProcess(\TaylorJ\UserBlogs\Entity\BlogPost $blogPost, ParameterBag $params)
    {
        $input = $this->filter([
            'blog_post_title' => 'str',
            'blog_id' => 'int'
        ]);
        $blog = $this->assertBlogExists($input['blog_id']);
        $message = $this->plugin('XF:Editor')->fromInput('message');
        $input['blog_post_content'] = $message;
        $input['blog_post_last_edit_date'] = 0;

        $form = $this->formAction();
        $form->basicEntitySave($blogPost, $input);

        $hash = $this->filter('attachment_hash', 'str');
        if ($hash && $blogPost->canUploadAndManageAttachments()) {
            $inserter = $this->service('XF:Attachment\Preparer');
            $associated = $inserter->associateAttachmentsWithContent($hash, 'taylorj_userblogs_post', $blog->blog_id);
        }

        return $form;
    }
With this instance though, since the blogPost hasn't been actually created yet (in the case of a new post), how would I grab that blogPosts id?

Edit: Looking into this more this won't fix my render issue but does fix the issue attachments being associated to the correct entity so that they then can hopefully be rendered properly.
 
Form actions have distinct steps (setup/validate/apply/complete). If you do the association in a complete callback then the blog post will have been saved and you can grab its ID as normal.
 
With this instance though, since the blogPost hasn't been actually created yet (in the case of a new post), how would I grab that blogPosts id?
Take a look at how \XF\Mvc\FormAction::complete() does work and associate your attachments to the content in this step.
 
Form actions have distinct steps (setup/validate/apply/complete). If you do the association in a complete callback then the blog post will have been saved and you can grab its ID as normal.
Take a look at how \XF\Mvc\FormAction::complete() does work and associate your attachments to the content in this step.
I was able to get this figured out thanks to you both!

Take a look at how AttachmentRepository::addAttachmentsToContent() is used.
When attempting to utilize addAttachmentsToContent() I am getting a weird interaction...

When using a debugger it's skipping over the first foreach loop and the if(ids) clause (due to the foreach not running and filling in the ids array). I am making sure to pass in my content which is the singular blogPost entity and it's correct content type and the blogPost that I am testing with has an attach_count field that is currently at 1.

Edit: By skipping over the loops/clause I mean I can't even step into the loops when it's their turn, it acts as if I'm stepping over them all together.

Edit 2: I was able to figure out the skipping over loops issue but even when running through addAttachmentsToContent() it's still not working on the blog post view.
 
Last edited:
Got it to work after a nights sleep and turning myself into a tomato while mowing the lawn in the Texas heat while thinking about the issue lmao.
 
Sorry for bumping my old thread, but am I doing something completly wrong when it comes to my attachment relation @Jeremy P ?

In my blogPost view my blogPost entity has the correct Attachment relation with an entity in it. But when in my blog view when attempting to render cover images of the first attachment in a blogPost it's showing my blogPost entities Attachments relation as not having anything in it entity wise.

blogPost view:
1725051156539.webp

vs

blog view:
1725051193551.webp

sorry for the weird debugger variable views, finally got dap working with neovim so I'm back to that.
 
I don't know how or when but

PHP:
        /** @var \XF\Repository\Attachment $attachmentRepo */
        $attachmentRepo = $this->repository(AttachmentRepository::class);
        $attachmentRepo->addAttachmentsToContent($blogPostContent->fetch(), 'taylorj_blogs_blog_post');

for some reason had 'post' instead of 'taylorj_blogs_blog_post' and I don't remember changing that to post. I need to stop programming when I'm way too inebriated.
 
That was when I came up with some of my best code :LOL:
I mean I get a lot of good work done on in that state as well but sometimes it bites me in the ass when I forget something that was changed. Found out I accidentally used my undo tree extension in neovim to go back to when it was ‘post’ somehow probably on accident.
 
Back
Top Bottom