This powerful plugin provides the base framework that enables you to build a multi-tenant SaaS application within October by using additional plugins from the Cumulus family as you require them.
Examples where Cumulus may help include:
You can install Cumulus in the following ways:
/plugins/initbiz/cumuluscore
directory,composer require initbiz/oc-cumuluscore-plugin
,If you want to play with Cumulus right away, visit cumulus.init.biz or cumulus.init.biz/backend for backend view.
In frontend there's a user
demo@init.biz
with passworddemo@init.biz
In backend there's a user
demo
with passworddemo
Frontend users are managed by RainLab.User
plugin. They can log in to your application and in most cases, they will be your clients' accounts. See RainLab.User documentation for more info about this.
By design, your clients' accounts have to have restricted access to the application functionality and data so that they do not see other clients' data (of course if you do not want to).
Backend users / admins are developers or application owners. By design backend users have access to application panel and places like registered users, their subscriptions, invoices, usage stats and so on.
Clusters are groups of users who share some data between them and can be described as one entity. The most common example is the company but it also applies to offices, office branches, classes in school, schools, etc.
You can understand clusters as tenants in multi-tenant architecture but remember that, in Cumulus, a user can have access to more than one cluster.
Clusters are not usergroup
s from RainLab.User
plugin (like guest
, registered
and so on)
Clusters add one more abstraction layer between user roles and permissions in the application. This means you can give a cluster a part of the functionality in your system. In Cumulus we use features to do it.
Plans are used to organize sets of Cumulus features that are given to clusters. A cluster can have only one plan assigned at a time, but a lot of clusters can have the same plan.
The easiest way to get the idea is to imagine an example pricing table with plans like "Free", "Plus" and "Pro".
Cumulus Features (or just features) are parts of the whole functionality of our application. Access to them is given to clusters by assigning them to plans. Every plugin of yours can register its own features.
Clusters' usernames are unique strings to be used in URLs so that URLs can be changed by the client the way they want to. The same feature on Facebook and Twitter is called username
so we decided to use the name username
as well.
Using usernames has to be enabled in general Cumulus settings (using usernames in URLs
). By default, Cumulus will use the cluster's slug.
Before you proceed, ensure you understand the terms explained before.
In Cumulus we specify four groups of pages that according to content we provide are:
Accordingly, we will create the following pages:
Session
component from RainLab.User
embedded and configured,CumulusGuard
component embedded,FeatureGuard
component embedded and configured.As a consequence, if you want to check if the user is:
you have to embed all the components (Session
, CumulusGuard
and FeatureGuard
) on one page (or its layout):
You can register Cumulus features using the registerCumulusFeatures
method in your Plugin.php
:
public function registerCumulusFeatures()
{
return [
'initbiz.cumulusinvoices.read_invoices' => [
'name' => 'initbiz.cumulusinvoices::lang.feature.manage_invoices',
'description' => 'initbiz.cumulusinvoices::lang.feature.manage_invoices_desc',
]
];
}
If you want to build an application menu for your users, use Rainlab.Pages menu builder feature. Cumulus extends the plugin and adds it's own menu item type: Cumulus page.
If you select the type then the cluster's username or slug will be automatically injected in the URL.
What is more, you can select features that will be required to show the entry in the menu. Remember that only one feature is enough to show the menu (more like logical "or" than "and"). If no feature is selected then everybody will see the menu entry.
UserClustersList
The component is rendering a view for a user to select the cluster he/she wants to enter. If the user is assigned to only one cluster then the component will transparently redirect browser as it was clicked.
CumulusGuard
The CumulusGuard
component is checking if a logged-in user has access to the cluster that he/she tries to visit.
What is more, the component pushes the current cluster
to the page object and sets active cluster's slug in session variable and cookie as cumulus_clusterslug
.
FeatureGuard
The feature guard is checking if the current cluster can see the page based on features it has access to.
Remember that only one of the checked features is enough to let the user see the page
If you want to filter content on one page based on features, use
canEnterFeature Twig function
.
ClusterFiltrable
traitYou can easily filter the data returned by the model using the ClusterFiltrable
trait.
If you have cluster_id
as a relation column for your model, you can easily filter the data by using clusterIdFiltered()
method:
Invoices::clusterIdFiltered()->get();
If you have cluster_slug
in your model as a relation column than you can alternatively use clusterFiltered()
method:
Invoices::clusterFiltered()->get();
You can also customize the attribute and the column by specifying other parameters like:
ExampleModel::clusterFiltered($attributeValue, 'attribute_column')->get();
If you want to check if a parameter is unique in the cluster scope (for example invoice number that can safely collide with other clusters but cannot be the same for one cluster) then you can use the clusterUnique
method from the trait.
The method returns validation rule for October's validator which you can use in the model's constructor.
For example, to check if invoice_number
is unique in the cluster scope we can use the following snippet:
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->rules['invoice_number'] = $this->clusterUnique('invoice_number');
}
If you want to customize the table name or column name to build a unique rule then you have to use parameters in the method. By default it will use $this->table
attribute and cluster_slug
as a column name, for example:
$this->rules['invoice_number'] = $this->clusterUnique('invoice_number', 'invoices', 'slug');
You can alternatively use the clusterIdUnique
method if your data is to be filtered by the cluster's id.
Auto assigning is a Cumulus function that automatically assigns users and clusters during their registration. Go to Settings -> Cumulus -> Auto-assign
and you will find two tabs: "Auto-assign users" and "Auto-assign clusters".
While auto assigning users to clusters you can decide whether you want to:
companyName
),companyName
),You can also decide whether you want to add a user to a group (RainLab.UserGroup
) after registering or not.
While auto assigning clusters to plans you can decide if you want to:
Free
or Trial
) orRemember:
Registering Cumulus features differs from registering cluster's features. Registering Cumulus features in the system is described here.
Every time a cluster obtains access to a feature (for the first time, once) we call it registering cluster's feature. Registering clusters' features is the process of running some 'registering' code when a cluster gets access to a feature whether by changes of the cluster's plan or plan's features.
To register a cluster's feature you have to bind to the initbiz.cumuluscore.registerClusterFeature
event like that:
Event::listen('initbiz.cumuluscore.registerClusterFeature', function ($cluster, $featureCode) {
if ($featureCode === "initbiz.cumulusinvoices.manage_invoices") {
// perform some registering code, for example, seed tables for the cluster with sample data
}
});
The event is blocking so if you decide to stop the process of registration then return false and an exception will be thrown.
If you use the usernames feature then you have to ensure that they are unique.
The Helpers::usernameUnique
method ensures that the username is unique in the cluster's table, but you can extend its logic by using the initbiz.cumuluscore.usernameUnique
event.
Using the UpdateCluster
component from Cumulus plus it will automatically check if the username is unique.
canEnterFeature()
Twig functionIf you want to check in views if the current cluster has access to a feature than use canEnterFeature('feature.code')
Twig function.
For example:
{% if canEnterFeature('initbiz.cumulusdemo.paid_feature') %}
Something visible only to those who have access to initbiz.cumulusdemo.paid_feature.
{% endif %}
You can also use canEnterAnyFeature
if you want to check if the cluster has access to any of the supplied features in the array. What is more, the method accepts feature codes that end with the *
.
If you want to get in touch, write to contact@init.biz or directly to @tomaszstrojny on OctoberCMS's official Slack.
Every contribution is very welcomed, thank you for your time in advance.
Post your issues or pull requests via GitHub or Product support page.
The repositories were moved to the models.
General:
Allow registration
on plans that you automatically assign clusters to.prepareClusterSlug
from ClusterFiltrable
trait has changed to prepareClusterToFilter
Events:
initbiz.cumuluscore.addClusterToPlan
became initbiz.cumuluscore.autoAssignClusterToPlan
and is run only on auto assigning cluster to planinitbiz.cumuluscore.addUserToCluster
became initbiz.cumuluscore.autoAssignUserToCluster
and is run only on auto assigning user to clusterinitbiz.cumuluscore.beforeAutoAssignNewCluster
is removed, instead use initbiz.cumuluscore.beforeAutoAssignClusterToPlan
initbiz.cumuluscore.beforeAutoAssignUserToCluster
and initbiz.cumuluscore.beforeAutoAssignClusterToPlan
addedregisterClusterFeature(string $clusterSlug, string $feature)
event now looks like that registerClusterFeature(Cluster $cluster, string $feature)
initbiz.cumuluscore.beforeClusterSave
was removed. Use typical model binding and bind to beforeSave
instead.Helpers:
getCluster
now returns Cluster
objectclusterId
, clusterUsername
, getClusterUsernameFromUrlParam
and getClusterSlugFromUrlParam
were removed, use getClusterFromUrlParam
insteadAll repositories:
getByRelationPropertiesArray()
-> $model->relation()->get()
and foreach with unique()
UserRepository
:
getUserClusterList($userId)
-> $user->clusters()->get()
PlanRepository
:
getPlansUsers($plansSlugs)
-> $plan->users
+ foreach and unique()
.ClusterRepository
:
getClustersUsers($clustersSlugs)
-> $cluster->users()->get()
+ foreach and unique()
.getClustersPlans($clustersSlugs)
-> $plans->push($cluster->plan()->first())
+ foreach and unique()
.canEnterFeature($clusterSlug, $featureCode)
-> $cluster->canEnterFeature($featureCode)
getClusterFeatures($clusterSlug)
-> $cluster->features
usernameUnique($username, $clusterSlug)
-> Helpers::usernameUnique($username, $clusterSlug
addClusterToPlan($clusterSlug, $planSlug)
-> $cluster->plan()->associate($plan)
ClusterFeatureLogRepository
:
clusterRegisteredFeatures(string $clusterSlug)
-> $cluster->registered_features
registerClusterFeature(string $clusterSlug, string $feature)
-> $cluster->registerFeature(string $feature)
registerClusterFeatures(string $clusterSlug, array $features)
-> $cluster->registerFeatures(array $features)
Cluster
Model:
clusterRegisteredFeatures
renamed to featureLogs
Clusters' usernames is a new feature of Cumulus, where your users can specify their "username" in URL. Click here for more info about the feature.
While installing this version Cumulus will by default copy Clusters' slugs to their usernames so by default usernames will be seeded and everything should work out of the box if you enable using usernames.
ClusterSlug
becomes ClusterUniq
ClusterSlug
property from Cumulus components becomes ClusterUniq
. That is because it can be either a slug or a username. It depends on the setting in the General settings tab in Backend Settings.
The only thing you have to change is ClusterSlug
to ClusterUniq
in all the places you have been using it directly. Those places are:
clusterSlug
param,clusterSlug
param.As a consequence method defineClusterSlug
becomes defineClusterUniq
.
cluster
and clusterData
variables injected to the page by CumulusGuard
have changedthe cluster
variable so far has been actually cluster's slug. This was a misleading convention that had to be changed. Right now cluster
is the object of the current cluster model, while the clusterData
variable is removed.
It is big. I know. It is funny in technology that after you create something it does not make sense after you work with it for some time. This is what happened to modules and some conventions we used in versions 1.x.x. Sorry about the number of changes, but we hope our plugin will be much better and usable after the upgrade.
Make backup before proceeding.
At the beginning of Cumulus, we did not know some of October's and Laravel's conventions. While designing and developing Cumulus we used our own experience and ideas. During this time we get familiar with October's naming suggestions. As a consequence in version 2.0.0, we decided to change a few names.
Full_name
from clusters
table becomes name.
In version 1.x.x we were using cluster_id
, module_id
and plan_id
as a primary keys. From now all of them will become id
.
initbiz_cumuluscore_modules
and initbiz_cumuluscore_plan_module
tables will be dropped during upgrade to 2.0.0. Because of that, the relation between your plans and modules will be lost. You should create a backup of initbiz_cumuluscore_plan_modules
and initbiz_cumuluscore_modules
if you want to review them after the upgrade.
In most cases, it should be easy to restore them as modules were whole plugins. Only plans and their relations with modules have to be restored in feature convention.
The biggest change in Cumulus v.2 concerns modules. We noticed, that it was not enough for the plugin to register only one feature (since modules were actually features of the system). This led us to the plugin registration file, where now plugins can register as many features as they want to (more info in the documentation).
Methods from ClusterRepository
that concerns modules will right now use features. It applies to almost every "module" word in methods and attributes names. What is more, modules used slugs while features use codes. So every time where we were talking about module slug, right now it is about feature code.
Before updating to v.2 you will have to ensure you register features as described in the documentation for all of your modules.
What is more, you have to remove the initial migration previously created by create:module
command:
register_initbiz_cumulus_module.php
version.yaml
file (at the beginning)ModuleGuard
becomes FeatureGuard
The responsibility of the ModuleGuard
component was to ensure that the plan has access to the specified module and return 403 (Forbidden access) if it does not. The responsibility of FeatureGuard
is the same but it checks if plan has access to any of the features specified in component configuration.
Access to only one feature is enough to enter the page.
create:module
removedAs a consequence the command create:module
is removed. If you want to create something similar then create a normal OctoberCMS plugin using create:plugin
command and by adding registerCumulusFeatures
method (details in documentation).
Settings
model becomes AutoAssignSettings
If you have used the Settings
model somewhere in your code then you will have to change its name to AutoAssignSettings
.
Because of that, you will have to reconfigure auto-assign in settings or update initbiz_cumuluscore_settings
row code to initbiz_cumuluscore_autoassignsettings
in system_settings
table.
Menu
and MenuItem
components removedFrom version 2.0.0 we decided to use RainLab.Pages to build menus. It is a powerful, supported and extendable way to build menus.
If you are using the Cumulus Plus extension make sure you change permissions from module name to feature code in "permissions".
inIT has been consistently providing a discreet and professional development service for October CMS users since 2015. We have successfully played major and minor development roles in over 100 October based projects supporting and partnering with clients around the world.
Why not make us a part of your next October development crew.
inIT.biz is a trading name of inIT.biz sp. z o.o., a company registered in Poland (REGON: 367829790, VAT: 8661738221)