In this tutorial we will create a module for Drupal, the focus here will be on understanding the structure of the module itself, not something super complex.
What is Drupal?
Drupal is an open source CMS (Content management system) through which you can build modern websites. Be they Personal blogs or an e-commerce.
What are modules?
A module is code that changes or adds functionality to Drupal. You can use community-created devices or create your own.
Drupal Vs. WordPress
It is impossible to name a CMS without also mentioning WordPress, the process of choosing the right CMS is an extremely important step. Choose the wrong platform and you’ll have an uphill battle right at the start of your project. Overall, Drupal is a pretty solid CMS, has a lot of features, and is optimized for top-notch performance and security from day one. it is very flexible, but has a steeper learning curve. If you don’t have time to learn to code and want to build a website as quickly as possible, consider using WordPress.
Starting the project
For this Project we will use we will need Lando and Composer.
Let’s Start a new Drupal project with composer:
composer create-project drupal/recommended-project drupal_project
We open the project directory:
cd drupal_project
And we create the Lando configuration:
lando init
In the following screens we select the options:
# From where should we get your app's codebase?
❯ current working directory
# From where should we get your app's codebase? current working directory
# What recipe do you want to use?
❯ drupal9
# Where is your webroot relative to the init destination?
web
# What do you want to call this app?
drupal_project
And we can start the project with:
lando start
Take your time, this process may take a while depending on the docker images that will be downloaded and your internet connection.
Once finished, you will see the project information on the screen, but you can view it at any time with:
lando info
To speed up the installation process we will use drush, which is a command line tool for Drupal.
First, we install drush:
lando composer require drush/drush
And install Drupal:
lando drush site:install --db-url=mysql://drupal9:drupal9@database/drupal9 -y
The admin password will appear on the screen, don’t forget to write it down.
Like magic we have Drupal installed, you can access it at https://drupal_project.lndo.site.
And with that we have a development environment for Drupal configured.
Creating the module
First of all
Before starting here is an important tip, Drupal creates page caches and sometimes our changes seem to have no effect, it is very important to clear this cache to be sure, just use the command:
lando drush cr
The module idea
Remembering that the focus here is for you to get a sense of how the structure of a module works, we will not create anything complex for now, but it will be a great start.
With this we are going to create a module to add a usage policy in the user registration form.
The module structure
This will be the structure of our module:
Let’s go through each of these files.
Starting the module
module.info.yml
For Drupal to understand a directory as a module we need to create .info.yml
.
Let’s create the module folder in web/modules/custom/policy
and, important to know, the name of the directory and .yml files must follow the same pattern, in our case the policy
folder will contain the policy.info.yml
file.
With this information, let’s create the web/modules/custom/policy/policy.info.yml
file with the following content:
name: Policy
description: "Custom module to manage the Policy terms"
package: Custom
core_version_requirement: ^8 || ^9
type: module
Only with that if we go to https://drupal_project.lndo.site/admin/modules we will already see the Policy
module.
Saving our policy settings in the database
module.schema.yml
Let’s create a place where we will use to store our terms, where it will be easier to change in the future, for that we will create a configuration schema, where it will be possible to change whenever necessary.
Let’s create the web/modules/custom/policy/config/schema/policy.schema.yml
file with the following content:
policy.settings:
type: config_object
label: "Policy Content Type Settings"
mapping:
title:
type: text
label: "Title"
message:
type: text
label: "Policy terms"
module.settings.yml
The .settings.yml
file will be the default value of these settings, created the moment the module is installed, so we create the web/modules/custom/policy/config/install/policy.settings.yml
file with the following contents:
title: "Policy terms"
message:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam a facilisis dolor, aliquam ullamcorper odio. Ut sollicitudin imperdiet ante, quis placerat sapien. Fusce sed vestibulum nisi. Morbi vel rhoncus dui, eu semper dolor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Sed pulvinar lorem eros, et fringilla arcu blandit nec. Praesent sem nunc, hendrerit vitae arcu at, rhoncus sagittis est. Fusce ultrices venenatis rhoncus.
Duis euismod, elit semper ultrices molestie, eros elit cursus diam, eu cursus justo urna vel lacus. Donec ornare efficitur pulvinar. Suspendisse pulvinar ipsum ipsum. Vestibulum eu fermentum felis, ut accumsan nibh. Proin blandit urna arcu, at posuere nunc hendrerit at. Sed finibus diam vel vulputate mollis. Quisque feugiat iaculis aliquet. Duis auctor enim a risus tincidunt bibendum. Proin augue tellus, finibus quis lorem interdum, mattis condimentum nunc. Nam et condimentum nibh. Sed eleifend sapien a massa sollicitudin vestibulum. Donec fringilla elit nisi, et pretium dolor dapibus in.
Aliquam ac est vestibulum, tempus odio quis, tempor leo. Quisque dui eros, laoreet id diam ac, consectetur ultrices lorem. Duis augue ante, venenatis ac mollis sed, posuere in purus. Vivamus magna tortor, fringilla eget felis sed, mattis finibus magna. Proin mauris dui, tempor ut ante id, aliquam ultrices nisi. Aenean pulvinar sapien at enim sagittis vehicula. Donec tristique sapien eget felis tincidunt, ut commodo magna pulvinar. Quisque vel tempor dui.
Phasellus luctus dolor sed odio pharetra, et interdum libero sollicitudin. Proin facilisis justo ac felis bibendum, ut commodo tellus lacinia. Fusce viverra eget augue nec aliquam. Quisque leo tortor, volutpat a facilisis tincidunt, molestie ut ante. Maecenas ornare commodo lacus, nec lobortis eros finibus eget. Nulla at nulla nec orci vehicula egestas semper viverra tellus. In scelerisque neque ac urna vulputate aliquet. Maecenas ultrices imperdiet ligula, eu commodo nisi. Praesent sodales felis metus, sed ultrices ipsum sodales ut. Pellentesque faucibus ligula risus, at ornare metus varius sit amet. Donec mollis non urna ut auctor. In imperdiet arcu ex, non vulputate tortor bibendum rutrum. Phasellus convallis metus in imperdiet finibus. Aenean maximus eget nibh quis interdum.
Fusce dictum, metus at tempus tincidunt, tortor tortor porta leo, non aliquam metus arcu in sem. Pellentesque vel nulla orci. Aliquam dolor elit, accumsan vel libero et, viverra auctor augue. Sed vulputate volutpat leo, nec euismod mauris congue ullamcorper. Curabitur quis diam vestibulum, aliquam nisi nec, vehicula lacus. Sed id luctus lorem. Praesent et ante vel arcu hendrerit efficitur vitae eu augue. Phasellus vulputate metus ac sapien cursus mattis. Donec nisl enim, ornare id tortor vitae, fermentum sodales sem. Nam et ligula nec purus bibendum scelerisque. Aliquam quis enim ut nibh placerat facilisis vestibulum sed arcu. Nunc eu orci eu nunc ultricies blandit eget a lorem."
Creating our policy page
To display this content on a page we will need to create a controller and a route for it to be displayed in /policy
of our site.
Creating the controller
We create the web/modules/custom/policy/src/Controler/PolicyController.php
file with:
<?php
namespace Drupal\policy\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Controller for display a Policy terms.
*/
class PolicyController extends ControllerBase {
/**
* Config object for PolicyController configuration.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* PolicyController constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Configuration object factory.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->config = $config_factory->get('policy.settings');
}
/**
* {@inheritdoc}
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The Drupal service container.
*
* @return static
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory')
);
}
/**
* {@inheritDoc}
*/
public function content() {
$message = $this->config->get('message');
return [
'#type' => 'markup',
'#markup' => $message,
];
}
}
Creating the route
Let’s also turn into a route file where we will call our controller displaying the policies in web/modules/custom/policy/policy.routing.yml
with:
policy.content:
path: "/policy"
defaults:
_controller: '\Drupal\policy\Controller\PolicyController::content'
_title: "Policy terms"
requirements:
_permission: "access content"
And accessing /policy
we can already see our policies page.
Meet the Hooks
Hooks are methods that manage to change a default behavior of Drupal, as well as the names of the files they must follow a name pattern to work correctly.
In our case we will create a hook to change the functioning of the new user registration page, located in /user/register
to add a checkbox containing a link to the site’s policies.
module.module
The .module
file is where we will create our hooks, it is treated as a .php
file but we must keep the name pattern, for that we will create the web/modules/custom/policy/policy.module
file with:
<?php
use Drupal\Core\Url;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_user_register_form_alter().
*/
function policy_form_user_register_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form['policy_terms'] = [
'#type' => 'checkbox',
'#default_value' => FALSE,
'#title' => t('I have read and accept <a href="@policy_terms" target="_blank" }" >the policy terms</a>.', ['@policy_terms' => Url::fromRoute('policy.content')->toString()]),
'#required' => TRUE,
];
}
Notice that we are not exactly using pure HTML, we are using the Drupal API to add an element and it takes care of generating the code on the page, if we go to our registration page we will see the checkbox already appearing.
Opening the policy in a modal
As much as our checkbox is already working, I don’t see the need to open a new page by clicking on the the policy terms
link, but I still think it’s important to have the /policy
page to use elsewhere, and this is where Drupal shines again, let’s modify our policy.module
file so that the page is opened in a modal, and just make the following change:
- '#title' => t('I have read and accept <a href="@policy_terms" target="_blank" }" >the policy terms</a>.', ['@policy_terms' => Url::fromRoute('policy.content')->toString()]),
+ '#title' => t('I have read and accept <a href="@policy_terms" target="_blank" class="use-ajax" data-dialog-options="{"width":"60vw" }" data-toggle="modal" data-dialog-type="modal">the policy terms</a>.', ['@policy_terms' => Url::fromRoute('policy.content')->toString()]),
Remember to clear cache before testing.
And so, like magic, Drupal opens the page in a modal, and still keeps our route.
Updating our settings
With everything created and working, we still have to create a way to change our policies when necessary, for that we create a file web/modules/custom/policy/src/Form/PolicySettingsForm.php
with:
<?php
namespace Drupal\policy\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Defines a form to update the Policy message config.
*/
class PolicySettingsForm extends ConfigFormBase {
/**
* {@inheritDoc}
*/
public function getFormId() {
return 'policy_form_update';
}
/**
* {@inheritDoc}
*/
protected function getEditableConfigNames() {
return [
'policy.settings',
];
}
/**
* {@inheritDoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Form Constructor.
$form = parent::buildForm($form, $form_state);
$config = $this->config('policy.settings');
$form['message'] = [
'#type' => 'textarea',
'#title' => $this->t('Update Policy Terms'),
'#default_value' => $config->get('message'),
'#description' => $this->t('Write policy terms'),
];
return $form;
}
/**
* {@inheritDoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('policy.settings');
$config->set('message', $form_state->getValue('message'));
$config->save();
return parent::submitForm($form, $form_state);
}
}
And we go back to the web/modules/custom/policy/policy.routing.yml
file and at the end of it we add:
...
policy.form_update:
path: "/admin/config/content/policy"
defaults:
_form: '\Drupal\policy\Form\PolicySettingsForm'
_title: "Update Policy Terms"
requirements:
_role: "administrator"
So when we access the address /admin/config/content/policy
with an admin user we will see the following page:
Adding the link to the admin panel
To make it even easier, let’s create a link inside the administrative panel so we can access these settings, so we don’t always need to remember this address when we change it.
And like everything so far, just create the web/modules/custom/policy/policy.link.menu.yml
file with:
policy.form_update:
title: "Policy Terms Settings"
description: "Update policy terms"
route_name: policy.form_update
parent: system.admin_config_content
weight: 10
So if we click on Configuration
in the admin menu we will see the Policy Terms Settings
link.
With that we have our module finished.
The code for this tutorial is in this GitHub repository.