You are here

realistic_dummy_content_api.module in Realistic Dummy Content 7

API code allowing other modules to generate realistic dummy content. See the Realistic Dummy Content module for an example of how to use.

File

api/realistic_dummy_content_api.module
View source
<?php

/**
 * @file
 *
 * API code allowing other modules to generate realistic dummy content.
 * See the Realistic Dummy Content module for an example of how to use.
 */

/**
 * Type of number generation.
 */
define('REALISTIC_DUMMY_CONTENT_SEQUENTIAL', FALSE);
define('REALISTIC_DUMMY_CONTENT_RANDOM', TRUE);

/**
 * Interface for a log class
 */
interface RealisticDummyContentLog {
  public function log($text, $vars = array());
  public function error($text, $vars = array());

}

/**
 * This log class can be used whenever you need a RealisticDummyContentLog
 */
class RealisticDummyContentDebugLog implements RealisticDummyContentLog {
  public function log($text, $vars = array()) {
    debug(t($text, $vars));
  }
  public function error($text, $vars = array()) {
    debug(t($text, $vars));
  }

}

/**
 * Implements hook_entity_insert().
 */
function realistic_dummy_content_api_entity_presave($entity, $type) {
  if ($type != 'user') {
    _realistic_dummy_content_api_entity_presave($entity, $type);
  }
}

/**
 * Implements hook_user_insert().
 */
function realistic_dummy_content_api_user_insert(&$edit, $account, $category) {

  // This hook is invoked only once when the user is first created, whether
  // by the administrator or by devel_generate. The hook is not invoked
  // thereafter.
  $filter = array(
    'exclude' => array(
      'picture',
    ),
  );
  _realistic_dummy_content_api_entity_presave($account, 'user', $filter);
}

/**
 * Implements hook_realistic_dummy_content_attribute_manipulator_alter().
 */
function realistic_dummy_content_api_realistic_dummy_content_attribute_manipulator_alter(&$class, &$type, &$machine_name) {

  // If you want to implement a particular manipulator class for a field or property
  // you can do so by implementing this hook and reproducing what's below for your
  // own field or property type.
  switch ($machine_name) {
    case 'picture':

      // the user picture
      $class = 'RealisticDummyContentUserPicture';
      break;
    case 'text_with_summary':

      // e.g. body
      $class = 'RealisticDummyContentTextWithSummaryField';
      break;
    case 'taxonomy_term_reference':

      // e.g. tags on articles
      $class = 'RealisticDummyContentTermReferenceField';
      break;
    case 'image':

      // e.g. images on articles
      $class = 'RealisticDummyContentImageField';
      break;
    default:
      break;
  }
}

/**
 * Implements hook_user_presave().
 */
function realistic_dummy_content_api_user_presave(&$edit, $account, $category) {

  // This hook is called when content is updated, in which case we don't want
  // to tamper with it. When content is first created, the $account's is_new
  // property is set to FALSE, se we can't depend on that to determine whether
  // the user is new or not. However, $edit['picture_delete'] is _only_ set when
  // users are updated, so we can check for that to determine whether or not to
  // continue modifying the account.
  if (isset($edit['picture_delete'])) {

    // not a new account, don't mess with it.
    return;
  }

  // At this point we know we are dealing with a new user.
  // $edit['uid'] can have several values:
  // This hook is invoked twice when content is created via devel_generate,
  // once with $edit['uid'] set to NULL (which causes us to do nothing) and
  // once with $edit['uid'] set to the UID of newly-created user object.
  // When the user is changed via the admin interface, this hook is invoked
  // but $edit['uid'] is not set. $edit['uid'] is never set during testing,
  // so we use $account->uid instead. $account->uid is set whether we are
  // creating the user in our test code or it's created via devel_generate.
  if (isset($account->uid) && $account->uid) {
    $filter = array(
      'include' => array(
        'picture',
      ),
    );
    $user = (object) $edit;
    _realistic_dummy_content_api_entity_presave($user, 'user', $filter);
    $edit = (array) $user;
  }
}

/**
 * Generic function called by various hooks in Drupal.
 *
 * hook_entity_insert(), hook_user_insert() and hook_user_presave() have subtle
 * differences. This function aims to be more abstract and uses the concept of
 * a filter, see below.
 *
 * @param $entity
 *   The object for a given type, for example this can be a user object
 *   or a node object.
 * @param $type
 *   The entity type of the information to change, for example 'user' or 'node'.
 * @param $filter
 *   If set, only certain fields will be considered when manipulating
 *   the object. This can be useful, for example for users, because
 *   two separate manipulations need to be performed, depending on whether
 *   hook_user_insert() or hook_user_presave(). Both hooks need to modify
 *   only certain properties and fields, but taken together the entire
 *   object can be manipulated.
 *   The filter is an associative array which can contain no key (all
 *   fields and properties should be manipulated), the include key (fields
 *   included are the only ones to be manipulated, or the exclude key (all
 *   fields except those included are the ones to be manipulated).
 *
 *   realistic_dummy_content_api_user_insert() defines the array
 *   ('exclude' => array(picture)) whereas
 *   realistic_dummy_content_api_user_presave() defines the array
 *   ('include' => array(picture)). Therefore taken together these two
 *   hooks manipulate the entire user object, but in two phases.
 *
 *   This allows hook implementations to return a different class based on
 *   the type of filter.
 */
function _realistic_dummy_content_api_entity_presave($entity, $type, $filter = array()) {
  try {
    if (realistic_dummy_content_api_is_dummy($entity, $type)) {
      $candidate = $entity;
      realistic_dummy_content_api_improve_dummy_content($candidate, $type, $filter);
      realistic_dummy_content_api_validate($candidate, $type);

      //$entity = $candidate;
    }
  } catch (Exception $e) {
    drupal_set_message(t('realistic_dummy_content_api failed to modify dummy objects: message: @m', array(
      '@m' => $e
        ->getMessage(),
    )), 'error', FALSE);
  }
}

/**
 * Checks if a given entity is dummy content.
 *
 * @param $entity
 *   The object for a given entity type, for example this can be a user object
 *   or a node object.
 * @param $type
 *   The type of the information to change, for example 'user' or 'node'.
 *
 * @return
 *   TRUE if at least one module implemented hook_realistic_dummy_content_api_dummy
 *   and thinks the entity is a dummy objects; FALSE otherwise.
 */
function realistic_dummy_content_api_is_dummy($entity, $type) {
  foreach (module_invoke_all('realistic_dummy_content_api_dummy', $entity, $type) as $dummy) {
    if ($dummy) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Insert or improve dummy data in an entity of a given type.
 *
 * @param $entity
 *   The object for a given type, for example this can be a user object
 *   or a node object.
 * @param $type
 *   The type of the information to change, for example 'user' or 'node'.
 * @param $filter
 *   If set, only certain fields will be considered when manipulating
 *   the object. This can be useful, for example for users, because
 *   two separate manipulations need to be performed, depending on whether
 *   hook_user_insert() or hook_user_presave(). Both hooks need to modify
 *   only certain properties and fields, but taken together the entire
 *   object can be manipulated.
 *   The filter is an associative array which can contain no key (all
 *   fields and properties should be manipulated), the include key (fields
 *   included are the only ones to be manipulated, or the exclude key (all
 *   fields except those included are the ones to be manipulated).
 *
 *   realistic_dummy_content_api_user_insert() defines the array
 *   ('exclude' => array(picture)) whereas
 *   realistic_dummy_content_api_user_presave() defines the array
 *   ('include' => array(picture)). Therefore taken together these two
 *   hooks manipulate the entire user object, but in two phases.
 *
 *   This allows hook implementations to return a different class based on
 *   the type of filter.
 *
 * @throws
 *   Exception
 */
function realistic_dummy_content_api_improve_dummy_content(&$entity, $type, $filter = array()) {
  $modifiers = module_invoke_all('realistic_dummy_content_api_class', $entity, $type, $filter);
  foreach ($modifiers as $modifier_class) {
    realistic_dummy_content_api_validate_class($modifier_class);
    $modifier = new $modifier_class($entity, $type, $filter);
    $modifier
      ->Modify();
    $entity = $modifier
      ->GetEntity();
  }
}

/**
 * Throw an exception if an entity is not valid
 */
function realistic_dummy_content_api_validate($entity, $type) {

  // Throw an exception here if an entity is not valid, for example if two users
  // have the same email address or name, or anything else.
  // @TODO provide a hook for third-party modules to manage this.
}

/**
 * Validate that a class is a valid subclasss of RealisticDummyContentBase
 *
 * @param $class
 *   A class name
 *
 * @throws
 *   Exception
 */
function realistic_dummy_content_api_validate_class($class) {
  if (!class_exists($class)) {
    throw new Exception(t('@class is not a valid class; make sure you include its file or use Drupal\'s autoload mechanism: name your include file with the same name as the class, and add it to the .info file, then clear your cache.', array(
      '@class' => $class,
    )));
  }
  if (!is_subclass_of($class, 'RealisticDummyContentBase')) {
    throw new Exception(t('@class is a valid class but it is not a subclass of RealisticDummyContentBase.', array(
      '@class' => $class,
    )));
  }
}

/**
 * Implements hook_realistic_dummy_content_api_class().
 */
function realistic_dummy_content_api_realistic_dummy_content_api_class($entity, $type, $filter = array()) {
  return array(
    // Insert class names for all classes which can modify entities for the
    // given type. These classes must exist, either through Drupal's
    // autoload system or be included explictely, and they must be
    // subclasses of RealisticDummyContentBase
    'RealisticDummyContentFieldModifier',
  );
}

/**
 * Implements hook_realistic_dummy_content_api_dummy().
 */
function realistic_dummy_content_api_realistic_dummy_content_api_dummy($entity, $type) {
  $return = FALSE;

  // Any entity with the devel_generate property set should be considered
  // dummy content. although not all dummy content has this flag set.
  // See https://drupal.org/node/2252965
  // See https://drupal.org/node/2257271
  if (isset($entity->devel_generate)) {
    return TRUE;
  }
  switch ($type) {
    case 'user':

      // devel_generate puts .invalid at the end of the generated user's
      // email address. This module should not be activated on a production
      // site, or else anyone can put ".invalid" at the end of their email
      // address and their profile's content will be overridden.
      $suffix = '.invalid';
      if (isset($entity->mail) && drupal_substr($entity->mail, strlen($entity->mail) - strlen($suffix)) == $suffix) {
        return TRUE;
      }
      break;
    default:
      break;
  }
  return $return;
}

/**
 * Generate a random, or sequential, number
 *
 * By default, this function will return a random number between $start and $end
 * inclusively. If you set the realistic_dummy_content_api_rand variable to
 * REALISTIC_DUMMY_CONTENT_SEQUENTIAL, for example for automated tested or in a recipe
 * (an example can be found at realistic_dummy_content/recipe/realistic_dummy_content.recipe.inc),
 * then this will call realistic_dummy_content_api_sequential().
 *
 * See the documentation for realistic_dummy_content_api_sequential() for details.
 *
 * @param $start
 *   The first possible number in the range.
 * @param $end
 *   The last possible number in the range.
 * @param $hash = NULL
 *   Ignored for random numbers; for sequential numbers, please se the documentation for
 *   realistic_dummy_content_api_sequential() for details.
 *
 * @return
 *   A random number by default, or a sequential number if you set the
 *   realistic_dummy_content_api_rand variable to REALISTIC_DUMMY_CONTENT_SEQUENTIAL.
 *   Please see the description of realistic_dummy_content_api_sequential() for details.
 */
function realistic_dummy_content_api_rand($start, $end, $hash = NULL) {
  if (variable_get('realistic_dummy_content_api_rand', REALISTIC_DUMMY_CONTENT_RANDOM)) {
    return rand($start, $end);
  }
  else {
    return realistic_dummy_content_api_sequential($start, $end, $hash);
  }
}

/**
 * Generate sequential number based on a hash
 *
 * Returns the starting number on every call until the hash is changed, at which case it
 * returns the second number, and so on.
 *
 * The idea behind this is that for a single node, we might want to retrieve the
 * 3rd file for each field (they go together).
 *
 * In the above example, if the 3rd file does not exist, we will return the first file,
 * in order to never return a number which is outside the range of start to end.
 *
 * @param $start
 *   The first possible number in the range.
 * @param $end
 *   The last possible number in the range.
 * @param $hash
 *   The number returned by this function will be in sequence: each call to
 *   realistic_dummy_content_api_sequential()'s return is incremented by
 *   one, unless $hash is the same as in the last call, in which case the return will the
 *   same as in the last call.
 *
 * @return
 *   A sequential number based on the $hash.
 *   Please see the description of the $hash parameter, above.
 */
function realistic_dummy_content_api_sequential($start, $end, $hash) {
  static $static_hash = NULL;
  if (!$static_hash) {
    $static_hash = $hash;
  }
  static $current = NULL;
  if (!$current) {
    $current = $start;
  }
  if ($static_hash != $hash) {
    $static_hash = $hash;
    $current -= $start;
    $current++;
    $current %= $end - $start + 1;
    $current += $start;
  }
  if ($current > $end) {
    $return = $end;
  }
  elseif ($current < $start) {
    $return = $start;
  }
  else {
    $return = $current;
  }
  return $return;
}

/**
 * Attempts to generate all realistic content for the current site.
 *
 * @param $log
 *   A class which implements the interface RealisticDummyContentLog. Logging
 *   will be different if you are using drush or in the context of an automated
 *   test, for example.
 */
function realistic_dummy_content_api_apply_recipe($log) {
  try {
    if (!class_exists('RealisticDummyContentRecipe')) {

      // When upgrading from beta3, class names may have changed, so rebuild the registry
      // to avoid presenting a "class not found" fatal error.
      registry_rebuild();
    }
    RealisticDummyContentRecipe::Run($log);
  } catch (Exception $e) {
    $log
      ->log('An exception occurred while trying to apply a recipe');
    $log
      ->error($e
      ->getMessage());
  }
}

Functions

Constants

Classes

Namesort descending Description
RealisticDummyContentDebugLog This log class can be used whenever you need a RealisticDummyContentLog

Interfaces

Namesort descending Description
RealisticDummyContentLog Interface for a log class