PHP - - By Younes Rafie

Extending OctoberCMS – Building a Soft-Delete Plugin

Developers usually stick with a new CMS for its simplicity and extensibility. OctoberCMS presents itself as a back to basics CMS, and provides an enjoyable experience for both developers and users. In this article, I’m going to demonstrate some aspects of the CMS that make it extensible, and we’ll also try a simple plugin to extend another plugin functionality.

OctoberCMS LOGO

Introduction

Every CMS has a plugin system for extending the platform’s functionality, and we measure the extensibility by how deep into the CMS’ inner workings we can go. However, we’re not only talking about the CMS here, we’re talking about plugins!

If you build a plugin, you need to make sure that other developers can change bits of your functionality. For example, we have a blog plugin and the user can publish a post by selecting it on a list. It would be a good idea to fire an event saying that a new post has been published, and another developer may hook into this event and notify subscribed users via email about this!

class Posts extends Controller
{
    public function index_onPublish()
    {
        if (($checkedIds = post('checked')) && is_array($checkedIds) && count($checkedIds)) {

            foreach ($checkedIds as $postId) {
                if ((!$post = Post::find($postId)) || !$post->canEdit($this->user))
                    continue;

                $post->publish();
                Event::fire('rainlab.blog.posts.published', [$post]);
            }

            Flash::success('Successfully published those posts.');
        }

        return $this->listRefresh();
    }
}

The other developer can listen for this event to do something with the published post.

Event::listen('rainlab.blog.posts.published', function($post) {
    User::subscribedTo($post).each(function($user) use($post) {
        Mail::send('emails.notifications.post-published', ['user' => $user, 'post' => $post], function($message) use($user, $post) {
            $message->from('us@example.com', 'New post by ' . $user->name);

            $message->to($user->email);
        });
    });
});

We will mainly use events to hook into different parts of the request cycle. Let’s start with a concrete example to clear things up.

The Rainlab Blog Plugin

If you’ve used OctoberCMS for a while, you must know the Rainlab Blog plugin. It lets you add posts and attach them to categories in the backend, and you can display them in the front-end using components.

On the posts listing page, we have the possibility to delete posts. But, what if we wanted to soft delete them? Let’s see if we can do that and learn more about OctoberCMS extensibility.

Creating a New Plugin

Go ahead and create a new plugin for our demo using the scaffolding helper command, and update the plugin details inside the Plugin.php file.

php artisan create:plugin rafie.blogplus

Extending the Database Schema

The first thing that comes to mind when talking about soft deletion is the deleted_at field column that need to be present in the DB.

Create a new file called create_posts_deleted_at_field.php under the blogplus/updates folder and update the version.yaml file.

// updates/version.yaml

1.0.1:
    - First version of blogplus.
    - create_posts_deleted_at_field.php
// updates/create_posts_deleted_at_field.php

class CreatePostsDeletedAtField extends Migration
{

    public function up()
    {
        Schema::table('rainlab_blog_posts', function ($table) {
            $table->timestamp('deleted_at')
                  ->default(null)
                  ->nullable();
        });
    }


    public function down()
    {
        Schema::table('rainlab_blog_posts', function ($table) {
            $table->dropColumn('deleted_at');
        });
    }
}

The migration class will alter the rainlab_blog_posts table and add our deleted_at column with a default null value. Don’t forget to run the php artisan plugin:refresh rafie.blogplus command for the changes to take effect.

Extending Posts List

Next, we have to add our field as a column to be displayed in the list. OctoberCMS provides an event for us to hook into, and alter the currently displayed widget (the backend list is considered a widget).

Event::listen('backend.list.extendColumns', function ($widget) {
    // Only for the Posts controller
    if (( ! $widget->getController() instanceof \Rainlab\Blog\Controllers\Posts)) {
        return;
    }

    $widget->addColumns([
                            'deleted_at' => [
                                'label' => 'Deleted',
                                'type'  => 'date'
                            ]
                        ]);
});

Note: The above code should be placed inside the Plugin@boot method.

We have an if statement to prevent our code from being executed on every page, then we add a new column to the list widget, and we can also remove any existing ones using the removeColumn method. Check the documentation for a list of available column options.

Posts list

Extending the Filter

The bar on top of the posts list lets the user filter the list using a date, category, etc. In our case, we need a filter to show/hide the trashed posts.

// plugin.php

Event::listen('backend.filter.extendScopes', function ($widget) {
    // Only for the Posts controller
    if (( ! $widget->getController() instanceof \Rainlab\Blog\Controllers\Posts)) {
        return;
    }

    $widget->addScopes([
                           'Trashed' => [
                               'label'      => 'Hide trashed',
                               'type'       => 'checkbox',
                               'scope'      => 'trashed'
                           ],
                       ]);
});

You can read more about the list filters in the documentation. The above code is fairly simple, and only contains a few options. However, the scope attribute should be the name of a query scope method defined inside the related (Models\Post) model instance.

Extendable Classes

The October\Rain\Extension\ExtendableTrait trait provides a magical way to dynamically extend an existing class by adding new methods, properties, behavior, etc. In our example we need to add a new method to the posts model to handle our scope filter.

// plugin.php

\Rainlab\Blog\Models\Post::extend(function ($model) {
    $model->addDynamicMethod('scopeTrashed', function ($query) {
        return $query->where('deleted_at', '=', null);
    });
});

We can do the same with addDynamicProperty, asExtension, etc. Let’s refresh our posts list to see if our changes are working.

Show trashed
Hide trashed

Of course, we don’t have any trashed posts yet because we need to finish the last part: intercepting the deletion of the post and only updating the deleted_at column.

Tip: Instead of using the scope attribute, you can use conditions to specify a simple where condition. The code below gives the same result as using model scopes.

$widget->addScopes([
   'Trashed' => [
       'label'      => 'Hide trashed',
       'type'       => 'checkbox',
       'conditions' => 'deleted_at IS NULL',
   ],
]);

Eloquent Events

Eloquent fires a list of events on every action (create, update, delete, etc). In this case, we need to hook into the delete event and prevent the record’s deletion.

When deleting a record, the deleting event is fired before performing the actual delete action, and the deleted one is fired afterwards. If you return false on the deleting event, the action will abort.

// plugin.php

Event::listen('eloquent.deleting: RainLab\Blog\Models\Post', function ($record) {
    $record->deleted_at = Carbon::now();
    $record->save();

    return false;
});

Now we are ready to test the final result! Go ahead and delete some records, and then go to the post listing page to see if you can toggle the trashed items in the list.

Conclusion

This article was a quick overview of how you can extend different parts of the OctoberCMS platform. You can read more about this in the extending plugins section of the documentation. If you have any questions or comments, be sure to post them below!

Sponsors
Login or Create Account to Comment
Login Create Account