Sunday, 20 December 2015

CKEDITOR AUTOCOMPLETE PLUGIN IN DRUPAL

DRUPAL CKEDITOR AUTOCOMPLETE PLUGIN



CKEDITOR

Ckeditor is a contributed module in drupal providing more html markups to the existing text editor in drupal. It has more plugins/styles compared to the drupal default rich text editor.

Ckeditor allows us to create any number of profiles which can be used for a text editor. The plugins/buttons for the profile can be customized inorder to differentiate the profiles.


CKEDITOR PLUGINS


Ckeditor allows to create plugins for extending the functionality of ckeditor and helping the editors for easy insertion of codes into the editor.


I am elaborating with an example.

I have a field "Description" field for a content type "Article" and i want the ability to embed videos in the description. Video is a content type in drupal and contents of video can be embedded into the description field of article. The videos should populate while user is typing anything on the ckeditor plugin (autocomplete functionality).


Imagine my video is present in a text field "field_video" in video content which stores the youtube video paths.

I am writing a module "mymodule" for this functionality.

Step 1: 

We need to hook  for registering the plugin in the ckeditor using  hook_ckeditor_plugin().


/**
 * Implements hook_ckeditor_plugin().
 */
function mymodule_ckeditor_plugin() {
  return array(
    'videoembed' => array(
      'name' => 'video_embed',
      'path' => drupal_get_path('module', 'mymodule') . '/plugins/video_embed',
      'desc' => 'Plugin to insert videos in drupal',
      'load' => TRUE,
    )
   )
}

Step 2:

Register a path for autcomplete in drupal.

/**
 * Implementation of hook_menu().
 */
function mymodule_menu() {
  $items['mymodule/autocomplete'] = array(
    'page callback' => 'mymodule_autocomplete',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

The access callback is set for logged in users. The access callback definition should contain the logic for fetching all the videos. You can use a db_query or entity field query for fetching all the videos. $matches variable should contain the key value pairs for the autocompletion of video informations.


I have written the logic to fetch videos based on titles.

Step 2.1

Define the autcomplete function.

function mymodule_autocomplete($string = '') {
  $matches = array();
  $query = new EntityFieldQuery();
  $query->entityCondition('entity_type', 'node') 
             ->entityCondition('bundle', 'article') 
             ->propertyCondition('status', NODE_PUBLISHED)              
             ->fieldCondition('field_video', 'value', 'NULL', '!=')
             ->propertyCondition('title', "%".$string."%","LIKE");
             ->range(0,10);

  $results = $query->execute();
  if (!empty($results['node'])) {
    $nodes = $results['node'];
    foreach ($nodes as $key => $node) {
      $current_node = node_load($key);
      $headline = $current_node->title;
      $youtube = $current_node->field_video['und'][0]['value'];
      $matches[$youtube] = '<div class="my-autcomplete-class">'. $headline. '-' . $key .'</div>';
    }
  }
  drupal_json_output($matches);
}


Step 3

Implement the hook_element_info_alter for adding the js after the form has rendered.



/**
 * Implementation of hook_element_info_alter().
 */
function mymodule_element_info_alter(&$type) {
  $new_article = FALSE;
  if ((!is_null(arg(0)) && arg(0) == 'node') && (!is_null(arg(2)) && arg(2) == 'edit')) {
    $node = menu_get_object();
  }
  if ((!is_null(arg(1)) && arg(1) == 'add') && (!is_null(arg(2)) && arg(2) == 'article')) {
    $new_article = TRUE;
  }
  if ((isset($node->type) && $node->type == 'article') || ($new_article)) {
    $type['form']['#post_render'][] = 'mymodule_form_post_render';
  }
}


function mymodule_form_post_render($content, $element) {
  static $added;
  if (!isset($added)  && ($js = drupal_add_js()) && isset($js['settings']['data'])) {
    $settings = call_user_func_array('array_merge_recursive', $js['settings']['data']);
    if (isset($settings['ckeditor']) || isset($settings['wysiwyg']['configs']['ckeditor'])) {
      $added = TRUE;
      drupal_add_js('misc/autocomplete.js');
      drupal_add_js(array('ckeditor_properties' => array(
      'module_path' => base_path() . drupal_get_path('module', 'mymodule'),
      'autocomplete_path' => url('mymodule/autocomplete'),
      )), 'setting');
    }
  }
  return $content;
}

Code Explanation for step 3

1. The code is only needed in edit pages.

2. "mymodule_form_post_render" will include the required js. Autcomplete path is passed to the drupal settings so that it can be retrieved in the js.



step4

Implement the plugin.

Now the plugin code should be written in file path /plugins/video_embed/plugin.js .


Plugin.js

(function () {
  // Handle the autocomplete functionality of the ckeditor.
  var initAutocomplete = function(input, uri) {
    input.setAttribute('autocomplete', 'OFF');
    new Drupal.jsAC(jQuery(input), new Drupal.ACDB(uri));
  };

// Plugin for inserting video embed code.
  CKEDITOR.plugins.add('videoembed', {
    init : function(editor) {
      // Register the dialog. The actual dialog definition is below.
      CKEDITOR.dialog.add('videoembed_dialog', videoDialogDefinition);

      // Register the commands.
      editor.addCommand('insertvideo', new CKEDITOR.dialogCommand('videoembed_dialog'));

      // Register the toolbar buttons.
      editor.ui.addButton('Video', {
        label : 'Add Videos into the editor',
        icon : this.path + '/logo.png',
        command : 'insertvideo'
      });
    }
  });

// dialog specifications.
var videoDialogDefinition = function(editor) {
    var dialogDefinition = {
      title : 'Video embed',
      minWidth : 390,
      minHeight : 130,
      contents : [
        {
          id : '',
          label : 'Settings',
          title : 'Settings',
          expand : true,
          padding : 0,
          elements : [
            {
              // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.vbox.html
              type : 'vbox',
              id: 'drupalOptions',
              children : [
                {
                  // Div width.
                  // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.textInput.html
                  type : 'text',
                  id: 'video_embed',
                  label : 'Video Embed (Autocomplete)',
                  style : 'margin-top:5px;',
                  onLoad: function() {
                    this.getInputElement().addClass('form-autocomplete');
                    initAutocomplete(this.getInputElement().$, Drupal.settings.ckeditor_properties.autocomplete_path);
                  },
                  validate : function() {
                  },
                  commit: commitValue
                }
              ]
            }
          ]
        }
      ],

      // Add the standard OK and Cancel Buttons
      buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ],

      // A "submit" handler for when the OK button is clicked.
      onOk : function() {
        var data = {};

        // Commit the field data to our data object
        // This function calls the commit function of each field element
        this.commitContent(data);
        if (data.info) {
          data.info.code = '[Video:' + data.info.video_embed + ']';
          editor.insertHtml(CKEDITOR.tools.trim(data.info.code) + '<p>&nbsp;</p>');
        }
      }
    };
    return dialogDefinition;
  };

  // Little helper function to commit field data to an object that is passed in:
  var commitValue = function(data) {
    var id = this.id;
    if (!data.info)
      data.info = {};
    data.info[id] = this.getValue();
  };
})();


Code explanation for step4


1.  Drupal.jsAC constructs an autocomplete object. The parameter is the input to the dialogue.
Reference : http://drupalapi.attiks.com/api/js-samples/autocomplete.js/class/Drupal.jsAC/7


2.  Drupal.ACDB represents an autocomplete database object. The parameter is the uri of the autocomplete
Reference : http://drupalapi.attiks.com/api/js-samples/autocomplete.js/function/Drupal.ACDB/7

3. on Load event adds a class "form-autocomplete" for populating the results.

4.  initAutocomplete function will be invoked with parameters input and autocomplete path.




Before using this plugin, you need to enable the new plugin in your ckeditor profile.Once enabled, the autcomplete plugin will work for the text editor which is using the ckeditor profile!!!!!!!!

Sunday, 29 March 2015

Creating your own drush commands in drupal



If you are a drupal programmer, you might be aware of drush commands which are very easy to interact with drupal database.

We used to clear the cache everytime in drupal. But it is not very easy to go to cache clear from the ui everytime."drush cc all" is the command which every drupal programmer is  executing in the terminal.

For the execution of the drush commands ,you should have

1.A drush installed in your machine. (clone the drush from git)

2. Go through the readme file provided in the drush folder (drush/README.txt) and do the steps.

3.Once the installation is finished, enjoy the drush commands in drupal.


Why we are using drush commands?

The drush commands can be executed from the terminal and it is a script running from backend of the drupal. Instead of dealing with the admin ui for configuration changes, we can easily handle using drush commands.So it is a shell script interface for drupal.

Eg: I want to enable the new module "My new module"

Steps:
Go to admin/modules and enable the module with the name "My new module". 

In terms of drush command, use the drush command drush en my_new_module 

The module name should be the machine name and it was the human readable name in the UI.The other drush commands are disable (dis), pm-uninstall  etc.


Steps for the generation of drush commands

I am going to elaborate the step by step process for the creation of a drush command in drupal with an example.


 I want to create a drush command to change the value of a field for all the contents of the content type "xyz"

Step1

The drush command needs to be written in a drush.inc file.The drush will search for the drupal directory for all the drush.inc files.If you are implementing your drush command in xyz.module, then drush command can be written in xyz.drush.inc


step2

Implement the hook_drush hook to register the drush command.

/**
* Implements hook_drush_command()
*/
function xyz_drush_command() {
  $items['xyz_field_x_change'] = array(
    'description' => 'Custom drush command to change the value of field_x in the content type xyz'
  'aliases' => array('xyz_z'),
  );
return $items;
}
Here i know the field and content type,so need of arguments. But you can make it generic by giving arguments. So the drush command can be used to change the value of any field in a specific content type.But the implementation will become difficult.
The above hook is modified to
function xyz_drush_command() {
  $items['xyz_field_x_change'] = array(
    'description' => 'Custom drush command to change the value of field_x in the content type xyz',
'arguments' => array(
'type' => 'content type',
'field_value' => 'field value'
),
  'aliases' => array('xyz_z'),
  );
return $items;
}
I am putting this point here because of the changes in the requirement day by day.If you want to change the field value of another content type tommorow, then you need to write another command. So try to make the drush commands more generic so that anybody can use it.

step3


Now the command is declared ,but no call back.The call back can be specified in the hook_drush_command or appending drush_ to the drush command.

function drush_xyz_field_x_change($ctype, $field_value) {
//logic goes here.The value should be changed in the database for the field.
}

Use the function "field_attach_update" for changing the value of a field.

If your logic contains more than 1000 entities or more, there is a chance of PHP timeout. Imagine you have 50000 contents of a specific type in your site and you want to modify field value of all. After processing 48000 contents , php timeout happened and what will be your reaction?

So always use batch process if your contents is more.I will cover the batch process basics in a seperate blog.




step4


Now we need to execute our drush command in the terminal.

Run the drush command as 

drush xyx_field_x_change pq pq_field_d

or drush xyz_z pq pq_field_d in the terminal. The script will execute and will do the required changes.


**********************************************************

How to the show the status of the backend process to the user?


Yes , drush command is ready and this will do the job. But how to know whether the process is finished successfully because there is no message printing from our end. So it is a good practise to show the message to the user.

Suppose this is my code at line number 130

L129   : $node->field_x = $new_value;
L130   : field_attach_update($node);
L131   :drush_print("field for". $node->nid. "updated successfully");
L132   : else {
L133   :  drush_print("The updation of field is failed/not happened for ". $node->nid);

$new_value - new field value.
$node - my current processing node
This will print the status for every node and people who is using your drush command will get an idea what s going on.


Simple Example


This example prints the message "Hello User"

/**
* Implements hook_drush_command()
*/
function myname_drush_command() {
  $items['hello_drupal'] = array(
    'description' => 'print Hello',
    'aliases' => array('h_d_m'),
  );
  return $items;
}

function drush_hello_drupal() {
  drush_print('Hello User');
}