Tuesday 27 August 2024

Drupal 8/9/10 Multisite Setup

 Create Drupal 10 fresh instance with composer command

Below composer command will download latest Drupal version.

composer create-project drupal/recommended-project d10multisite

OR

Below composer command will download Drupal specific version.

composer create-project drupal/recommended-project:10.3.2 d10multisite


Create virtual hosts in xampp

c:\xampp\apache\conf\extra\httpd-vhosts.conf

<VirtualHost *:80>

  ServerName d10multisite.localhost

  ServerAlias www.d10multisite.localhost

  DocumentRoot "C:/xampp/htdocs/d10multisite/web"

  <Directory "C:/xampp/htdocs/d10multisite/web">

    Require all granted

  </Directory>

</VirtualHost>


<VirtualHost *:80>

  ServerName d10multisite-site1.localhost

  ServerAlias www.d10multisite-site1.localhost

  DocumentRoot "C:/xampp/htdocs/d10multisite/web"

  <Directory "C:/xampp/htdocs/d10multisite/web">

    Require all granted

  </Directory>

</VirtualHost>


<VirtualHost *:80>

  ServerName d10multisite-site2.localhost

  ServerAlias www.d10multisite-site2.localhost

  DocumentRoot "C:/xampp/htdocs/d10multisite/web"

  <Directory "C:/xampp/htdocs/d10multisite/web">

    Require all granted

  </Directory>

</VirtualHost>


Add below lines in hosts file

C:\Windows\System32\drivers\etc\hosts

    127.0.0.1 d10multisite.localhost

  127.0.0.1 d10multisite-site1.localhost

  127.0.0.1 d10multisite-site2.localhost


cd sites/

ls

cp example.sites.php sites.php

sites.php :- is a file where we are going to configure how many sites are there and mapped to which domain and mapped to which folder.

edit sites.php

go to the bottom of the file

  $sites["d10multisite.localhost"] = 'default';

  $sites["d10multisite-site1.localhost"] = 'site1';

  $sites["d10multisite-site2.localhost"] = 'site2';

every site is having different settings.php

cd default

    cp example.settings.php settings.php

cd site1

    cp default/default.settings.php site1/settings.php

cd site2

    cp default/default.settings.php site2/settings.php

every site is having different services.yml

// default

    cp default/default.services.yml default/services.yml

// site1

    cp default/default.services.yml site1/services.yml

// site2

    cp default/default.services.yml site2/services.yml

Restart xampp server

check in browser with below urls

    d10multisite.localhost

    d10multisite-site1.localhost

    d10multisite-site2.localhost

Now install Drupal instances with standard profile installation.

Now I have to install module with composer, then run below command

    composer require drupal/devel

It will download module from Drupal.org

Now I have to enable this module on any specific site, run below drush command

drush en devel --uri="d10multisite.localhost"

Tuesday 16 July 2024

Upgrade CKEditor 4 to CKEditor 5

Upgrade CKEditor 4 to CKEditor 5 

The old CKEditor module (which integrates with CKEditor 4) has been removed from core in Drupal 10 and is replaced by a new "CKEditor 5" module that integrates with CKEditor 5. The older version still appears as 'CKEditor' when displayed, such as on the extension page.


Most Drupal sites use CKEditor 4, and must do one of the following:

  1. Switch to CKEditor 5 (strongly recommended), or
  2. Install the CKEditor 4 contributed module, which will be supported until late 2023. Choose this option only if the site has additional Drupal modules installed that provide additional CKEditor 4 plugins with no CKEditor 5 equivalents yet. (Consult the Upgrade coordination for modules providing CKEditor 4 plugins wiki page for the current status.)
            #How to upgrade from CKEditor 4 to CKEditor 5

                1. Upgrade to Drupal 9.4.4 or higher

2. Update modules providing additional CKEditor 4 plugins to the latest release of the major version you're on. Modules that have added CKEditor 5 support should provide support for both CKEditor 4 and 5, and provide an automated upgrade path (for toolbar items and plugin settings) from 4 to 5.

3. Enable the CKEditor 5 module (leave "CKEditor" module enabled) 

4. Converting text formats to use CKEditor 5

1. Go to Text Formats ⮕ Editors in the Drupal Configuration user interface and click "Configure" for each text format.

2. Under Text Editor change selection to "CKEditor 5" and click "Save configuration"

3. Text formats must be updated one at a time. This will automatically migrate your text format configuration to CKEditor 5.

4. Disable (old) "CKEditor" module.

5. Note: This will change your configuration so make sure your export it.

A contributed CKEditor 4 module is available. However, CKEditor 4 is reaching its end-of-life at the end of 2023. The contributed module will receive coordinated security releases alongside Drupal 9 core, and no other maintenance or bug fixes. Once Drupal 9 is end-of-life, core maintainers will no longer provide security coverage for the module.


Therefore, sites should only use the CKEditor 4 module if they use contributed modules extending CKEditor that have not yet been updated to work with CKEditor 5.

Wednesday 28 June 2023

Modify a field config and storage with existing data in Drupal

 As a developer, we have faced such situations where we have to change the type of field in content type. If that field is already having data against the field then this is not possible through Drupal interface. And we cannot delete and create that field again as there will be data loss.

To meet this requirement, the proper way is to write hook_update

In my one of account, I faced such scenario where anyhow initially field was created as type "List(Text)" then we have to change it to "Boolean". We have already created a lot of contents on Production site. We don't want to update the all contents again after recreating this field. So I followed the hook_update to change that field config and storage with existing data.

I have created a custom module named mymodule and write down .install file to add hook_update. Code is as per below.


<?php

/**
 * @file
 * Update function to change field with existing data.
 */

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

/**
 * Recreate field_content_access storage so that "drush cim" can run.
 */
function connect_access_update_9001() {
  $fields = [
    'field_content_access' => [
      'table' => 'node__field_content_access',
      'revision_table' => 'node_revision__field_content_access',
    ],
  ];

  $database = \Drupal::database();

  foreach ($fields as $field_name => $f) {
    // Table name from $fields array.
    $table = $f['table'];
    // Revision table from $field array.
    $revision_table = $f['revision_table'];
    // Entity type like node.
    $entity_type = 'node';

    // Step 1.
// Get field storage.
    $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);

    // Then check if field is not found.
    if (is_null($field_storage)) {
      continue;
    }

    // Step 2.
// Store existing data in $rows and $revision_rows variables.
    $rows = NULL;
    $revision_rows = NULL;
    if ($database->schema()->tableExists($table)) {
      // The table data will be restored from $rows and $revision_rows variable
// after the update is completed.
      $rows = $database->select($table, 'n')->fields('n')->execute()
        ->fetchAll();
      $revision_rows = $database->select($revision_table, 'n')->fields('n')->execute()
        ->fetchAll();
    }

    // Step 3.
// Save new field configs in new variable and delete existing fields.
    $new_fields = [];
    // Traverse every field to assign those values to new field.
    foreach ($field_storage->getBundles() as $bundle => $label) {
      $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
      $new_field = $field->toArray();
      $new_field['field_type'] = 'boolean';
      $new_fields[] = $new_field;
      // Delete existing field.
      $field->delete();
    }

    // Step 4.
// Create new storage configs from existing configs.
    $new_field_storage = $field_storage->toArray();
    $new_field_storage['type'] = 'boolean';
    $new_field_storage['module'] = 'core';
    $new_field_storage['settings'] = [];

    // Step 5.
// Purge deleted fields data.
// Required to create new fields.
    field_purge_batch(200);

    // Step 6.
// Create new fieldstorage.
    FieldStorageConfig::create($new_field_storage)->save();

    // Step 7.
// Now create new fields for all bundles.
    foreach ($new_fields as $new_field) {
      $new_field = FieldConfig::create($new_field);
      $new_field->save();
    }

    // Step 8.
// Restore existing data in created fields & revision tables.
    if (!is_null($rows)) {
      foreach ($rows as $row) {
        $row = (array) $row;
        $database->insert($table)->fields($row)->execute();
      }
    }
    if (!is_null($revision_rows)) {
      foreach ($revision_rows as $row) {
        $row = (array) $row;
        $database->insert($revision_table)->fields($row)->execute();
      }
    }
  }
}

Follow below steps. 1. Machine name of the field must be same to new field. 2. On `admin/reports/fields` and search field to be changed. List down all the content types using this field. 3. Check table in the DB related with this field and check revision tables also. List down all the records here. Check with below queries either run by drush or from phpmyadmin. drush sql:query "SELECT * FROM node__field_content_access;" drush sql:query "SELECT * FROM node_revision__field_content_access;" 4. Add the above code in mymodule.install. 5. Replace the $entityType and $fieldName variables according to the entity and field have to change. 6. Take DB backup. 7. Run update either by drush updb or by /update.php on your browser. 8. Check if the update was successfully performed. 9. Check field in all content type. 10. Check data in existing content type.

Tuesday 22 November 2022

Install Drush Globally

 To install Drush globally follow below steps.

1. Run below command, To install Drush 8.x required for Drupal 8+.

    composer global require drush/drush:dev-master

2. Now run below command to add Drush to your system path.

         export PATH="$HOME/.composer/vendor/bin:$PATH"

3. Run below command to update to a newer version.

         composer global update

To install specific version of Drush, run below command.

        composer global require drush/drush:6.1.0

Some of the below common used Drush commands.

drush cc or cache:clear => To clear cache. When we run this command, will give some options to execute as below

          [0] Cancel

    [1] drush

    [2] theme-registry

    [3] router

    [4] css-js

    [5] render

    [6] plugin

    [7] bin

    [8] views

drush cr or cache:rebuild => Rebuild a Drupal 8 site.
drush updb or updatedb => Apply any database updates required (as with running update.php)
drush updbst or updatedb:status => List any pending database updates.
drush uli or user:login => Display a one time login link for user ID 1, or another user.
drush pml => To list modules
drush en module_name => To enable module
drush pmu => To uninstall module
drush site:install => To install Drupal site with Drush command.
drush sqlc or sql:cli => Open a SQL command-line interface using Drupal's credentials.
drush sql:dump => Exports the Drupal DB as SQL using mysqldump or equivalent.
drush sqlq or sql:query => Execute a query against a database.


Create Drupal 9 Project with Composer

 For Drupal 9, We should use "drupal/recommended-project" composer template. This will ensure that Drupal core dependencies are same as the official Drupal release.

Composer command is as below to create Drupal 9 project

composer create-project drupal/recommended-project first_drupal9_site

This command will create a project in "first_drupal9_site" and automatically will execute composer install to download latest stable version of Drupal and all its dependencies.

Now first_drupal9_site directory will have files as below.

1. composer.json (file)

2. composer.lock (file)

3. vendor (Directory)

4. web (Directory)

The web root will be "web" directory. Except "web" directory, all other files "composer.json, composer.lock, vendor" will not accessible to the web server. 


To install Drupal 9 site, now access your localhost and access web directory. Follow the steps as per installation asks. Once these steps will finish, your first Drupal 9 site will ready.


To install specific version, run below composer command.

composer create-project drupal/recommended-project:9.4.8 first_drupal9_site


To do modified installation

Before composer install execution, if I need to modify some of the properties of the downloaded composer.json then we have to use --no-install flag while running composer create-project

Example, if I want to rename the subdirectory "web" to something else. Follow below steps

  • Run command composer create-project --no-install drupal/recommended-project first_drupal_site 
  • Change directories to first_drupal_site and edit the composer.json file to suit your needs.

    For example, to change the sub-directory from 'web' to something else, the keys to modify are the 'extra' sub-keys 'web-root' (under 'drupal-scaffold') and 'installer-paths'.

  • Run composer install to download Drupal and all its dependencies. 
To download contributed module and contributed theme with composer follow below steps.
  • Run composer require drupal/module_name. eg. composer require drupal/token
  • This needs to be executed at the root of your Drupal install but not at the same level as the core directory.
Composer will then automatically update your composer.json, adding the module to all the other requirements in the list, like this:

{
    "require": {
        "drupal/token": "^1.5"
    }
}
Composer will download the module and all the possible dependencies it may have.

Specifying a version
You can specify the version of the module / theme you want to download as follows:

composer require 'drupal/module_name:version'
(...where version is replaced with a Composer version or version constraint.)

For example:
composer require 'drupal/token:^1.5' 
composer require 'drupal/simple_fb_connect:~3.0' 
composer require 'drupal/ctools:3.0.0-alpha26' 
composer require 'drupal/token:1.x-dev' 

To avoid problems on different terminals/shells, surround the version in quotes as in the examples above.

Note!: On Windows, single quotes actually might break the version specification and lead to failure to install with 'Could not parse version constraint .....': Invalid version string "....'".

Without the quotes, e.g.:
composer require drupal/ctools:^3.7 or with double quotes instead of single quotes, e.g.:
composer require "drupal/ctools:^3.7" works well.

Reference link Drupal.org

Monday 21 November 2022

Enable PHP OPCache in Xampp

To install Drupal 8 and upper version, PHP OPCache extension is required. Untill we will not enable this, Drupal 8+ versions can not be installed.

To enable this extension, Please follow below steps.

1. Open php.ini file which is configuration file for PHP.

2. Search for "Dynamic Extensions" keyword.

3. Under this heading, Either search for ";zend_extension=opcache" and uncomment this line by removing ";" or add new line by adding below code

   zend_extension="C:\xampp\php\ext\php_opcache.dll"

4. Make sure the "zend_extension" should have the exact path to the opcache.dll file.

5. opcache.dll file exists in xampp > php > ext directory.

6. Save the php.ini file.

7. Restart the apache service.

8. Now try to install Drupal 8+, issue of PHP OPCache fixed.


To get good performance, following settings for PHP OPCache are recommended.

opcache.memory_consumption=128

opcache.interned_strings_buffer=8

opcache.max_accelerated_files=4000

opcache.revalidate_freq=60

opcache.fast_shutdown=1

opcache.enable_cli=1


Sunday 3 May 2020

Services injection in custom Controller

In custom controller, if it is extending ControllerBase class then no need to inject (with service container) some of the services explained in this post. ControllerBase already having some of the services which we can directly access in the custom controller without injecting it.

Below example will show how to use those services.

Custom Controller Class code

<?php

namespace Drupal\custom_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Class ControllerServicesInjectionController.
 *
 * @package Drupal\custom_module\Controller
 */
class ControllerServicesInjectionController extends ControllerBase {

  // Below trait is used to make translatable string in controller class.
  use StringTranslationTrait;

  /**
   * Function having information about services which need not to inject.
   */
  public function useServicesWithoutInjecting() {

// Get current user object without injecting user service.
// Returns the current user.
       $user_object = $this->currentUser();

// Get entityTypeManager service without injecting entityTypeManager service.
// Retrieves the entity type manager.
$entity_type_manager = $this->entityTypeManager();

// Get moduleHandler service without injecting moduleHandler service.
// Returns the module handler.
$module_handler = $this->moduleHandler();

// Get languageManager service without injecting languageManager service.
// Returns the language manager service.
$language_manager = $this->languageManager();

// Get entityFormBuilder service without injecting entityFormBuilder service.
// Retrieves the entity form builder.
$entity_form_builder = $this->entityFormBuilder();

// Get formBuilder service without injecting formBuilder service.
// Returns the form builder service.
$form_builder = $this->formBuilder();

// Get state service without injecting state service.
// Returns the state storage service.
$state_object = $this->state();

// Get keyValue service without injecting keyValue service.
// Returns a key/value storage collection.
$keyvalue_object = $this->keyValue('config_key');

        // Translate string in controller class with StringTranslationTrait.
$this->t('This is translatable string.');

        // To only check the above services output.
        print_r($user_object);
        print_r($entity_type_manager);
        print_r($module_handler);
        print_r($language_manager);
        print_r($entity_form_builder);
        print_r($form_builder);
        print_r($state_object);
        print_r($keyvalue_object);
  }
}

Sunday 26 April 2020

Import Field Configurations Programmatically in Drupal 8

Import Field Configurations Programmatically in Drupal 8


In one of my project, feature module was used to move configurations from one environment to another environment.
With the help of feature module it is easy to move configurations and sync of configurations is easy but when we are moving these configs to production environment we are not having access of it to revert feature changes.

So I decided to write hook_update_n in install file of the module to import configurations of any field. With the help of below code, configurations can be imported to create field.


Create a custom module having below directory structure or place config file in already created custom module in config/install directory and create a modulename.install file

config_import
  config_import.info.yml
  config_import.module
  config_import.install
  config
    install
       // Place all config files here as below
       field.storage.node.field_second_field.yml
       field.field.node.home_stage.field_second_field.yml
       core.entity_form_display.node.home_stage.default.yml
       core.entity_view_display.node.home_stage.default.yml

Code which should available in config_import.install file as below
<?


<?php

/**
 * @file
 * Add search placeholder field.
 */

use Drupal\Core\Config\FileStorage;

/**
 * Add configurations to configs.
 */
function import_config_update_8002() {
  // Get config storage.
  $config_storage = \Drupal::service('config.storage');

  $module = 'import_config';
  // Get config files path.
  $config_path = drupal_get_path('module', 'import_config') . '/config/install';
  // Get file storage for config files.
  $source = new FileStorage($config_path);

  // Create field storage for second field.
  try {
    \Drupal::entityTypeManager()->getStorage('field_storage_config')
      ->create($source->read('field.storage.node.field_second_field'))
      ->save();
  } catch (\Exception $e) {
    Drupal::logger($module)
      ->error('Storage for field field_second_field could not be created. ' . $e->getMessage());
  }

  try {
    // Create field
    \Drupal::entityTypeManager()->getStorage('field_config')
      ->create($source->read('field.field.node.home_stage.field_second_field'))
      ->save();
  } catch (\Exception $e) {
    Drupal::logger($module)
      ->error('Field field_second_field for entity home_stage could not be created. ' . $e->getMessage());
  }

  // Add termscondition field in recipe configurations config pages.
  $config_storage->write('core.entity_form_display.node.home_stage.default', $source->read('core.entity_form_display.node.home_stage.default'));
  $config_storage->write('core.entity_view_display.node.home_stage.default', $source->read('core.entity_view_display.node.home_stage.default'));


}

With above code when you will run drush updb then field "field_second_field" will be created.

For reference :