realistic_dummy_content_api.module in Realistic Dummy Content 7
Same filename and directory in other branches
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.moduleView 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
Name | Description |
---|---|
REALISTIC_DUMMY_CONTENT_RANDOM | |
REALISTIC_DUMMY_CONTENT_SEQUENTIAL | Type of number generation. |
Classes
Name | Description |
---|---|
RealisticDummyContentDebugLog | This log class can be used whenever you need a RealisticDummyContentLog |
Interfaces
Name | Description |
---|---|
RealisticDummyContentLog | Interface for a log class |