Apr 08 2019
Apr 08

Drupal 8 ships with a custom CKEditor build. This build is configured with a build-config.js file. We recently ran into a situation in which we wanted to override this configuration in order to disable a plugin. There is some information in this build-config.js file about replacing it with a non-minified build for development purposes, but nothing about overriding it. Here is how we did it.

The plugin we wanted to disable was the Show Table Borders plugin. This is the feature that provides default dotted borders around all table cells while you're editing a table in CKEditor. We wanted to disable that and, instead, just show the table borders as they would be styled on the front-end. Upon inspection of the build-config.js file that Drupal uses, which is located at core/assets/vendor/ckeditor, we found that the plugins key contained showborders: 1. To disable it, we needed to rebuild CKEditor with this line removed.

To do that, we saved a copy of build-config.js to our theme in a similar location: assets/vendor/ckeditor. We removed the line which enables showborders. Then, we went to CKEditor's online Builder and used the Upload build-config.js feature to download a newly generated copy of CKEditor that would exclude the Show Table Borders plugin. We placed the downloaded files in our theme's assets/vendor/ckeditor directory.

The last step is to override Drupal's core CKEditor build from within the theme's info.yml file. Add the following lines (modified for your theme):

libraries-override: core/ckeditor: js: assets/vendor/ckeditor/ckeditor.js: /themes/custom/YOUR_THEME_NAME/assets/vendor/ckeditor/ckeditor.js

Flush the caches and the plugin should be gone!

Oct 19 2017
Oct 19

Salesforce Suite is a group of modules for Drupal that allows for pulling data from Salesforce into Drupal, as well as pushing data from Drupal to Salesforce. The module api provides some very useful hooks, including the _salesforce_pull_entity_presave hook implemented by the Salesforce Pull module. In this blog post, we’ll look at using that hook to pull three Salesforce custom fields (select lists) into Drupal as taxonomy terms in three vocabularies.

Create a custom module to house the hook called <sitename>_salesforce and create a <sitename>_salesforce.module file. In that file, drop in the presave function, as copied from salesforce.api.php in the Salesforce Suite module:

/**
 * Act on an entity just before it is saved by a salesforce pull operation.
 * Implementations should throw a SalesforcePullException to prevent the pull.
 *
 * @param $entity
 *   The Drupal entity object.
 * @param array $sf_object
 *   The Salesforce query result array.
 * @param SalesforceMapping $sf_mapping
 *   The Salesforce Mapping being used to pull this record
 *
 * @throws SalesforcePullException
 */
function hook_salesforce_pull_entity_presave($entity, $sf_object, $sf_mapping) {
  if (!some_entity_validation_mechanism($entity)) {
    throw new SalesforcePullException('Refused to pull invalid entity.');
  }
  // Set a fictional property using a fictional Salesforce result object.
  $entity->example_property = $sf_object['Lookup__r']['Data__c'];
}

Take a look at the example code in the function body but remove it.

The hook gets called during the salesforce_pull_process_records function with this line:

// Allow modules to react just prior to entity save.
module_invoke_all('salesforce_pull_entity_presave', $wrapper->value(), $sf_object, $sf_mapping);

So, that’s where we will intervene with our custom code. With this hook, we have access to the data queried from Salesforce, and the entity that is about to be saved into Drupal, so it's a perfect time to do any translations between the two data sets.

The first problem we have to address is that, by default, the Salesforce Pull module will create a new node as it processes each Salesforce record instead of modifying the existing nodes on your Drupal site. If you don’t want this behavior, add this code:

// first of all, don't create new nodes
if (isset($entity->is_new) && $entity->is_new == TRUE) {
  throw new SalesforcePullException('Tried to create a new node.');
}

You may also want to look at the _salesforce_pull_mapping_object_alter hook to aid in prematching nodes.

Then, we need to define our taxonomy vocabularies:

// lookup table
$names_vids = array(
  'exampleVocabularyA' => array('vid' => 1, 'field' => 'field_example_vocabulary_a'),
  'exampleVocabularyB' => array('vid' => 2, 'field' => 'field_example_vocabulary_b'),
  'exampleVocabularyC' => array('vid' => 3, 'field' => 'field_example_vocabulary_c'),
);

Gather the terms from $sf_object like this:

// gather terms
$incoming = array(
  'exampleVocabularyA' => explode(';', $sf_object['Terms_A__c'] ? $sf_object['Terms_A__c'] : ''),
  'exampleVocabularyB' => explode(';', $sf_object['Terms_B__c'] ? $sf_object['Terms_B__c'] : ''),
  'exampleVocabularyC' => explode(';', $sf_object['Terms_C__c'] ? $sf_object['Terms_C__c'] : ''),
 );

You’ll want to clean up the incoming data:

array_walk_recursive($incoming, 'trim');
$incoming = array_map('array_filter', $incoming);

Then, we need to iterate over the incoming terms and create a new term if it doesn’t already exist in Drupal. Finally, we set the tids on the desired nodes:

foreach($incoming as $vname => $term_names) {
  $tids = array();
  foreach($term_names as $term_name) {
    $tid = taxonomy_get_term_by_name($term_name, $vname);
    if (empty($tid)) {
      // add the term if we don't already have it
      $newterm = new stdClass();
      $newterm->name = $term_name;
      $newterm->vid = $names_vids[$vname]['vid'];
      taxonomy_term_save($newterm);
      $tid = $newterm->tid;
    }
    array_push($tids, $tid);
  }
  // set tids on target nodes
  // first unset all existing tids
  $entity->{$names_vids[$vname]['field']} = array();
  // using $length here because we modify $tids in loop
  $length = count($tids);
  for ($i = 0; $i < $length; $i++) {
    $tid = array_shift($tids);
    $tid = array_keys($tid)[0];
    $entity->{$names_vids[$vname]['field']}[LANGUAGE_NONE][$i]['tid'] = $tid;
  }
}

This will keep your Drupal nodes in sync (on each cron run) with any terms added or deleted on the Salesforce objects.

(If you are having trouble getting Salesforce to complete its whole queue on a single cron run, I recommend this blog post for troubleshooting tips: Drupal Salesforce Not Updating Records. In particular, we recommend the Queue UI module.)

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web