Extending OctoberCMS – Building a Soft-Delete Plugin

Share this article

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!

Frequently Asked Questions on Extending OctoberCMS and Building a Soft Delete Plugin

What is the purpose of a soft delete plugin in OctoberCMS?

A soft delete plugin in OctoberCMS is designed to prevent permanent data loss. When you delete a record, it’s not completely removed from the database. Instead, a ‘deleted_at’ timestamp is set for that record. This means the record is considered ‘deleted’ from the application’s perspective, but can still be retrieved if necessary. This is particularly useful in scenarios where data might be accidentally deleted, as it allows for easy recovery.

How does the soft delete functionality differ from a hard delete?

A hard delete permanently removes a record from the database, making it impossible to recover unless you have a backup. On the other hand, a soft delete merely marks a record as deleted without actually removing it from the database. This allows you to restore the record if needed.

How can I implement soft delete functionality in OctoberCMS?

To implement soft delete functionality in OctoberCMS, you need to create a plugin. This involves creating a new plugin, adding a ‘deleted_at’ column to your database table, and updating your model to use the ‘SoftDelete’ trait. You can then use the ‘delete’ method on your model to soft delete a record, and the ‘restore’ method to restore it.

How can I test the soft delete functionality in OctoberCMS?

You can test the soft delete functionality by creating a unit test. This involves creating a new test case, creating a new record in your database, soft deleting it, and then asserting that it’s still present in the database but marked as deleted.

Can I use the soft delete functionality with existing records?

Yes, you can use the soft delete functionality with existing records. You just need to add a ‘deleted_at’ column to your existing database table. Any existing records will have a ‘null’ value for this column, indicating that they haven’t been deleted.

How can I restore a soft deleted record in OctoberCMS?

To restore a soft deleted record, you can use the ‘restore’ method on your model. This will remove the ‘deleted_at’ timestamp from the record, effectively ‘undeleting’ it.

Can I permanently delete a soft deleted record in OctoberCMS?

Yes, you can permanently delete a soft deleted record by using the ‘forceDelete’ method on your model. This will remove the record from the database, just like a hard delete.

How can I view all records, including soft deleted ones, in OctoberCMS?

To view all records, including soft deleted ones, you can use the ‘withTrashed’ method on your model. This will return all records, regardless of whether they’ve been soft deleted or not.

Can I customize the name of the ‘deleted_at’ column in OctoberCMS?

Yes, you can customize the name of the ‘deleted_at’ column by overriding the ‘getDeletedAtColumn’ method in your model. This allows you to use a different column name if ‘deleted_at’ doesn’t suit your needs.

Can I disable the soft delete functionality for certain records in OctoberCMS?

Yes, you can disable the soft delete functionality for certain records by using the ‘withoutGlobalScope’ method on your model. This allows you to exclude certain records from the soft delete functionality.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

BrunoScmseloquentframeworkFrameworkslaraveloctoberoctobercmsOOPHPPHPphp frameworkphp frameworksplugin
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week