readmorecontrol.module in Read More Control 7
Defines options to control how the Read more link is displayed on teasers.
File
readmorecontrol.moduleView source
<?php
/**
* @file
* Defines options to control how the Read more link is displayed on teasers.
*/
/**
* Implements hook_help().
*/
function readmorecontrol_help($path, $arg) {
switch ($path) {
case 'admin/help#readmorecontrol':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Read More Control module can add and control the read more link in many different content types, including nodes, terms and even users.') . '</p>';
return $output;
}
}
/**
* Implements hook_theme().
*/
function readmorecontrol_theme($existing, $type, $theme, $path) {
return array(
'readmorecontrol_link' => array(
'variables' => array(
'entity_type' => NULL,
'entity' => NULL,
'bundle' => NULL,
'link' => NULL,
),
),
);
}
/**
* Implements hook_menu().
*/
function readmorecontrol_menu() {
$items['admin/config/content/read-more-control'] = array(
'title' => 'Read more settings',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'readmorecontrol_admin_settings_form',
'node',
),
'description' => 'Configure the Read more link settings.',
'access arguments' => array(
'administer site configuration',
),
'file' => 'readmorecontrol.admin.inc',
);
$entity_info = entity_get_info();
foreach ($entity_info as $entity_type => $info) {
if (readmorecontrol_supported_entity($entity_type, $info)) {
if ($entity_type == 'node') {
$items['admin/config/content/read-more-control/' . $entity_type] = array(
'title' => $info['label'],
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
}
else {
$items['admin/config/content/read-more-control/' . $entity_type] = array(
'title' => $info['label'],
'page callback' => 'drupal_get_form',
'page arguments' => array(
'readmorecontrol_admin_settings_form',
$entity_type,
),
'description' => 'Configure the Read more link settings.',
'access arguments' => array(
'administer site configuration',
),
'file' => 'readmorecontrol.admin.inc',
'type' => MENU_LOCAL_TASK,
);
}
}
}
return $items;
}
/**
* Implements hook_node_type_update().
*/
function readmorecontrol_node_type_update($info) {
if (!empty($info->old_type) && $info->old_type != $info->type) {
variable_set('readmorecontrol_behaviour__node__' . $info->type, variable_get('readmorecontrol_behaviour__node__' . $info->old_type, 'always'));
variable_del('readmorecontrol_behaviour__node__' . $info->old_type);
variable_set('readmorecontrol_format__node__' . $info->type, variable_get('readmorecontrol_format__node__' . $info->old_type, array()));
variable_del('readmorecontrol_format__node__' . $info->old_type);
// Update all of the view modes if required.
$entity_info = entity_get_info('node');
foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) {
if ($old_settings = variable_get('readmorecontrol_behaviour__node__' . $info->old_type . '__' . $view_mode, FALSE)) {
variable_set('readmorecontrol_behaviour__node__' . $info->type . '__' . $view_mode, $old_settings);
variable_del('readmorecontrol_behaviour__node__' . $info->old_type);
}
if ($old_settings = variable_get('readmorecontrol_format__node__' . $info->old_type . '__' . $view_mode, FALSE)) {
variable_set('readmorecontrol_format__node__' . $info->type . '__' . $view_mode, $old_settings);
variable_del('readmorecontrol_format__node__' . $info->old_type);
}
}
}
}
/**
* Implements hook_node_type_delete().
*/
function readmorecontrol_node_type_delete($info) {
variable_del('readmorecontrol_behaviour__node__' . $info->type);
variable_del('readmorecontrol_format__node__' . $info->type);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function readmorecontrol_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
module_load_include('admin.inc', 'readmorecontrol');
_readmorecontrol_form_field_ui_field_edit_form_alter($form, $form_state, $form_id);
}
function readmorecontrol_form_field_ui_display_overview_form_alter_submit(&$form, &$form_state) {
module_load_include('admin.inc', 'readmorecontrol');
_readmorecontrol_form_field_ui_display_overview_form_alter_submit($form, $form_state);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function readmorecontrol_form_field_ui_display_overview_form_alter(&$form, &$form_state, $display_overview = FALSE) {
module_load_include('admin.inc', 'readmorecontrol');
_readmorecontrol_form_field_ui_display_overview_form_alter($form, $form_state, $display_overview);
}
/**
* Preprocessor for search result.
*
* @see search-result.tpl.php
*/
function readmorecontrol_preprocess_search_result(&$variables) {
// Best guess here, e.g. node or user.
$type = $variables['module'];
if (isset($variables['result'][$type]) && readmorecontrol_supported_entity($type)) {
$entity = $variables['result'][$type];
if (is_object($entity) && !empty($entity->readmorelink)) {
if ($entity->readmore_required) {
switch ($entity->readmorelink_placement) {
case 'search_info':
$variables['info_split'][] = $entity->readmorelink;
if (empty($variables['info'])) {
$variables['info'] = $entity->readmorelink;
}
else {
$variables['info'] .= ' - ' . $entity->readmorelink;
}
break;
default:
$elm = strpos($entity->readmorelink_placement, '_inline') ? 'span' : 'div';
$variables['snippet'] .= " <{$elm} class=\"read-more-link\">" . $entity->readmorelink . "</{$elm}>";
break;
}
}
}
}
}
################################################################################
# Helper functions for parsing various settings and options. #
################################################################################
/**
* Helper function to get a list of view modes that should not have a Read more
* link applied to these.
*/
function readmorecontrol_excluded_view_modes() {
return array(
'search_index',
'token',
'diff_standard',
'diff_complete',
);
}
/**
* Determines if the entity can support a Read more link.
*/
function readmorecontrol_supported_entity($entity_type, $info = NULL) {
if (!$info) {
$info = entity_get_info($entity_type);
}
if ((!empty($info['label callback']) || !empty($info['entity keys']['label'])) && !empty($info['uri callback'])) {
if (!empty($info['view modes'])) {
$excluded_modes = variable_get('readmorecontrol_disabled_view_modes', readmorecontrol_excluded_view_modes());
$excluded_modes[] = 'default';
$excluded_modes[] = 'full';
$modes = array_keys($info['view modes']);
$target_modes = array_diff($modes, $excluded_modes);
return !empty($target_modes);
}
}
return FALSE;
}
/**
* Helper function to determine the Read more link behaviour.
*/
function readmorecontrol_entity_behaviour($entity_type, $bundle, $view_mode) {
// This blocks all processing within entity_view().
if (in_array($view_mode, readmorecontrol_excluded_view_modes())) {
// Defaulting to none rather than never to prevent any additional processing.
return 'none';
}
else {
$node_teaser = $entity_type == 'node' && $view_mode == 'teaser';
$behaviour = variable_get("readmorecontrol_behaviour__{$entity_type}__{$bundle}__{$view_mode}", $node_teaser ? 'default' : 'none');
if ($behaviour == 'default') {
$behaviour = variable_get("readmorecontrol_behaviour__{$entity_type}__{$bundle}", 'default');
if ($behaviour == 'default') {
$system_default = $node_teaser ? 'always' : 'none';
$behaviour = variable_get("readmorecontrol_behaviour__{$entity_type}", $system_default);
}
}
return $behaviour;
}
}
/**
* Helper function to get any formatting settings.
*/
function readmorecontrol_format_settings($entity_type, $bundle = NULL, $view_mode = NULL, $is_edit = FALSE) {
$settings = array();
if ($is_edit) {
if ($view_mode && $view_mode != 'default') {
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type . '__' . $bundle . '__' . $view_mode, array(
'enabled' => 'global',
));
}
elseif ($bundle) {
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type . '__' . $bundle, array(
'enabled' => 'global',
));
}
else {
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type, array(
'enabled' => 'none',
));
}
}
else {
// Find the setting with the highest precesion, but discard if the use
// global setting is selected.
if ($view_mode) {
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type . '__' . $bundle . '__' . $view_mode, array());
$settings += array(
'enabled' => 'global',
);
if (!$is_edit && $settings['enabled'] == 'global') {
$settings = array();
}
}
if ($bundle) {
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type . '__' . $bundle, array());
$settings += array(
'enabled' => 'global',
);
if ($settings['enabled'] == 'global') {
$settings = array();
}
}
$settings += (array) variable_get('readmorecontrol_format__' . $entity_type, array(
'enabled' => 'none',
));
}
$settings += array(
'enabled' => 'none',
'text' => '',
'href' => '',
'query' => '',
'fragment' => '',
'title' => '',
'class' => '',
'rel' => '',
'target' => '',
'placement' => readmorecontrol_default_placement($entity_type, $view_mode),
);
return $settings;
}
/**
* Helper function to determine the best placement default value.
*/
function readmorecontrol_default_placement($entity_type, $view_mode) {
switch ($view_mode) {
case 'search_result':
return 'search_snippet';
default:
return 'none';
}
}
/**
* A helper function to help determine if a field should be tested.
*
* Checks the instance settings and field view access rights.
*/
function readmorecontrol_field_requires_processing($entity_type, $entity, $field, $instance) {
if (field_access('view', $field, $entity_type, $entity)) {
// Test the default action to determine if we should use this field.
switch ($entity->readmorebehaviour) {
case 'when_required':
// Check the field settings to see if this field is to be ignored.
if ($behaviour = readmorecontrol_instance_settings($instance)) {
return $behaviour == 'process';
}
return TRUE;
case 'when_required_text':
if (in_array($field['type'], array(
'text_with_summary',
'text_long',
))) {
if ($behaviour = readmorecontrol_instance_settings($instance)) {
return $behaviour == 'process';
}
return TRUE;
}
break;
case 'when_required_body':
if ($field['field_name'] == 'body') {
if ($behaviour = readmorecontrol_instance_settings($instance)) {
return $behaviour == 'process';
}
return TRUE;
}
break;
}
}
return FALSE;
}
/**
* Helper function to determine the instance readmore_behaviour setting.
*/
function readmorecontrol_instance_settings($instance) {
// Check the field settings to see if this field is to be ignored.
if (isset($instance['readmore_behaviour'])) {
if ($instance['readmore_behaviour'] != 'default') {
return $instance['readmore_behaviour'];
}
}
return FALSE;
}
################################################################################
# Core processing functions. #
################################################################################
/**
* Extract, update or construct the read more link.
*/
function readmorecontrol_entity_view($entity, $entity_type, $view_mode, $langcode) {
// The module makes the assumption that these are the only two "full" view
// modes to compare against.
if ($view_mode == 'full' || $view_mode == 'default') {
return;
}
// Ensure that this entity is actually supported.
$entity_info = entity_get_info($entity_type);
if (!readmorecontrol_supported_entity($entity_type, $entity_info)) {
return;
}
// Only process enabled view modes.
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
$behaviour = readmorecontrol_entity_behaviour($entity_type, $bundle, $view_mode);
if ($behaviour == 'none') {
return;
}
$full_mode = empty($entity_info['view modes']['full']) ? 'default' : 'full';
$view_mode_settings = field_view_mode_settings($entity_type, $bundle);
$actual_mode = empty($view_mode_settings[$view_mode]['custom_settings']) ? 'default' : $view_mode;
$actual_full_mode = empty($view_mode_settings[$full_mode]['custom_settings']) ? 'default' : $full_mode;
// Determine the behaviour of the modules processing of the link.
$entity->readmorebehaviour = $behaviour;
switch ($entity->readmorebehaviour) {
case 'always':
$entity->readmore_required = TRUE;
break;
case 'never':
$entity->readmore_required = FALSE;
break;
default:
// Test the view mode to see really different.
if ($actual_mode == $actual_full_mode) {
$entity->readmore_required = FALSE;
}
else {
foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
// Get the field display info for full view mode.
$display = field_get_display($instance, $view_mode, $entity);
$display_full = field_get_display($instance, $full_mode, $entity);
// If the full view mode is hidden, we can ignore this field.
if ($display_full['type'] == 'hidden') {
continue;
}
$field = field_info_field($field_name);
if (readmorecontrol_field_requires_processing($entity_type, $entity, $field, $instance)) {
// Only test fields that have data.
$items = field_get_items($entity_type, $entity, $field_name, $langcode);
if (!empty($items)) {
// If current view is hidden, we can assume that the main view
// has values.
if ($display['type'] == 'hidden') {
$entity->readmore_required = TRUE;
break;
}
$func = "readmorecontrol_{$display['module']}_compare_items";
if (function_exists($func)) {
$context = array(
'display' => $display,
'display_full' => $display_full,
'instance' => $instance,
'field' => $field,
'langcode' => $langcode,
'entity' => $entity,
'entity_type' => $entity_type,
);
if ($func($items, $context)) {
$entity->readmore_required = TRUE;
break;
}
}
else {
if ($display_full['type'] != $display['type'] || $display_full['settings'] !== $display['settings']) {
$entity->readmore_required = TRUE;
break;
}
}
}
}
}
}
break;
}
// No differences were found.
if (!isset($entity->readmore_required)) {
$entity->readmore_required = FALSE;
}
// Apply the settings.
$format = readmorecontrol_format_settings($entity_type, $bundle, $view_mode);
// Create and append a copy of the Read More link to the entity itself.
$has_readmore = !empty($entity->content['links']) && !empty($entity->content['links'][$entity_type]) && !empty($entity->content['links'][$entity_type]['#links'][$entity_type . '-readmore']);
if ($has_readmore) {
$readmorelink = $entity->content['links'][$entity_type]['#links'][$entity_type . '-readmore'];
}
else {
$title = entity_label($entity_type, $entity);
$title_stripped = strip_tags($title);
$uri = entity_uri($entity_type, $entity);
$readmorelink = array(
'title' => t('Read more<span class="element-invisible"> about @title</span>', array(
'@title' => $title_stripped,
)),
'href' => $uri['path'],
'html' => TRUE,
'attributes' => array(
'title' => $title_stripped,
),
);
if (in_array($view_mode, variable_get('readmodecontrol_external_view_modes', array(
'rss',
)))) {
$readmorelink['absolute'] = 1;
}
}
if (!empty($format['text'])) {
if ($title = filter_xss_admin(_rmc_token_replace($format['text'], $entity_type, $entity))) {
$readmorelink['title'] = $title;
$readmorelink['html'] = 1;
}
}
// The generated URL is passed through check_plain() internally.
if (!empty($format['href'])) {
if ($format['href'] == '<none>') {
unset($readmorelink['href']);
}
else {
$readmorelink['href'] = _rmc_token_replace($format['href'], $entity_type, $entity);
}
}
if (!empty($format['query'])) {
if ($query_string = _rmc_token_replace($format['query'], $entity_type, $entity)) {
$query = array();
foreach (explode('&', $query_string) as $query_argument) {
list($key, $value) = explode('=', $query_argument . '=1');
$query[$key] = $value;
}
$readmorelink['query'] = $query;
}
}
if (!empty($format['fragment'])) {
if ($fragment = _rmc_token_replace($format['fragment'], $entity_type, $entity)) {
$readmorelink['fragment'] = $fragment;
}
}
// Append any link attributes. Like the link options, these have are also
// passed through check_plain() internally.
$attributes = empty($readmorelink['attributes']) ? array() : $readmorelink['attributes'];
if (!empty($format['title'])) {
if ($attr_title = _rmc_token_replace($format['title'], $entity_type, $entity)) {
$attributes['title'] = $attr_title;
}
}
if (!empty($attributes['class'])) {
$attributes['class'] = is_array($attributes['class']) ? $attributes['class'] : explode(' ', $attributes['class']);
if (!in_array('read-more', $attributes['class'])) {
$attributes['class'][] = 'read-more';
}
}
else {
$attributes['class'] = array(
'read-more',
);
}
if (!empty($format['class'])) {
if ($classes = _rmc_token_replace($format['class'], $entity_type, $entity)) {
$attributes['class'][] = $classes;
}
}
if (!empty($format['rel'])) {
if ($rel_title = _rmc_token_replace($format['rel'], $entity_type, $entity)) {
$attributes['rel'] = $rel_title;
}
}
if (!empty($format['target'])) {
$attributes['target'] = $format['target'];
}
$readmorelink['attributes'] = $attributes;
$entity->readmorelink = theme('readmorecontrol_link__' . $entity_type, array(
'entity' => $entity,
'entity_type' => $entity_type,
'bundle' => $bundle,
'link' => $readmorelink,
));
$entity->readmorelink_raw = $readmorelink;
$entity->readmorelink_placement = $format['placement'];
if (!$entity->readmore_required || $view_mode == 'search_result') {
if ($has_readmore) {
unset($entity->content['links'][$entity_type]['#links'][$entity_type . '-readmore']);
}
return;
}
// If required, process the placement.
if ($format['placement'] != 'none') {
$field_item = NULL;
if (strpos($format['placement'], 'body_') === 0) {
if (isset($entity->content['body'])) {
$field_item =& $entity->content['body'];
}
}
else {
$rendered_fields = array();
foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
if (isset($entity->content[$field_name])) {
$display = $instance['display'][$actual_mode];
$rendered_fields[$field_name] = $display['weight'];
}
}
// This places the field last
if ($format['placement'] == 'append') {
$extra_fields = field_extra_fields_get_display($entity_type, $bundle, $view_mode);
foreach ($extra_fields as $name => $settings) {
if (isset($entity->content[$name])) {
$rendered_fields[$name] = $settings['weight'];
}
}
}
asort($rendered_fields);
foreach ($rendered_fields as $key => $weight) {
if ($format['placement'] == 'append') {
$field_item =& $entity->content[$key];
}
elseif ($format['placement'] == 'field_append' || $format['placement'] == 'field_inline') {
$field_item =& $entity->content[$key];
}
elseif (isset($entity->content[$key]['#field_type'])) {
if (in_array($entity->content[$key]['#field_type'], array(
'text_with_summary',
'text_long',
))) {
$field_item =& $entity->content[$key];
}
}
}
}
if (isset($field_item)) {
$deltas = element_children($field_item);
$last_delta = array_pop($deltas);
if (isset($last_delta)) {
$item =& $field_item[$last_delta];
if (strpos($format['placement'], '_inline') && isset($item['#markup'])) {
$link = ' <span class="read-more-link">' . $entity->readmorelink . '</span>';
if (preg_match('!</?(?:p|div)[^>]*>\\s*$!i', $item['#markup'], $match, PREG_OFFSET_CAPTURE)) {
$insert_point = strpos('teaser', $item['#markup']) + $match[0][1];
$item['#markup'] = substr_replace($item['#markup'], $link, $insert_point, 0);
}
else {
$item['#markup'] .= $link;
}
}
else {
$field_item[$last_delta]['#suffix'] = '<div class="read-more-link">' . $entity->readmorelink . '</div>';
}
}
else {
$field_item['#suffix'] = '<div class="read-more-link">' . $entity->readmorelink . '</div>';
}
if ($has_readmore) {
unset($entity->content['links'][$entity_type]['#links'][$entity_type . '-readmore']);
}
return;
}
}
// Fallen though, apply as a links element.
if (empty($entity->content['links'])) {
$entity->content['links'] = array(
'#theme' => 'links__' . $entity_type,
'#pre_render' => array(
'drupal_pre_render_links',
),
'#attributes' => array(
'class' => array(
'links',
'inline',
),
),
$entity_type => array(
'#theme' => 'links__' . $entity_type . '__' . $bundle,
'#links' => array(),
'#attributes' => array(
'class' => array(
'links',
'inline',
),
),
),
);
}
$entity->content['links'][$entity_type]['#links'][$entity_type . '-readmore'] = $readmorelink;
}
function theme_readmorecontrol_link($variables) {
$readmore = $variables['link'];
// Also available.
$entity_type = $variables['entity_type'];
$entity = $variables['entity'];
$bundle = $variables['bundle'];
if (isset($readmore['href'])) {
// Pass in $readmore as $options, they share the same keys.
return l($readmore['title'], $readmore['href'], $readmore);
}
elseif (!empty($readmore['title'])) {
// Some links are actually not links, but we wrap these in <span> for adding title and class attributes.
if (empty($readmore['html'])) {
$readmore['title'] = check_plain($readmore['title']);
}
$span_attributes = '';
if (isset($readmore['attributes'])) {
$span_attributes = drupal_attributes($readmore['attributes']);
}
return '<span' . $span_attributes . '>' . $readmore['title'] . '</span>';
}
return '';
}
/**
* Wrapper function for token_replace().
*
* Returned text is not sanitized.
*/
function _rmc_token_replace($text, $entity_type, $entity) {
// Only process if there is a hint that a token present.
if (strpos($text, ']')) {
return token_replace($text, array(
$entity_type => $entity,
array(
'clear' => 1,
'sanitize' => 0,
),
));
}
return $text;
}
################################################################################
# Core field comparison callbacks. #
################################################################################
/**
* Implements the callback readmorecontrol_MODULE_compare_items().
*/
function readmorecontrol_text_compare_items($items, $context) {
extract($context, EXTR_SKIP);
// We can bypass processing of some combinations.
if ($display['type'] == $display_full['type']) {
switch ($display['type']) {
case 'text_default':
case 'text_plain':
return FALSE;
}
}
// Avoid additional processing, do this once for both if required.
$needs_sanitization = $display['type'] != 'text_plain' || $display_full['type'] != 'text_plain';
$has_summary = $display['type'] == 'text_summary_or_trimmed' || $display_full['type'] == 'text_summary_or_trimmed';
foreach ($items as $delta => $item) {
$sanitized_text = $needs_sanitization ? _text_sanitize($instance, $langcode, $item, 'value') : '';
$sanitized_summary = $has_summary ? _text_sanitize($instance, $langcode, $item, 'summary') : '';
$display_text = '';
$full_display_text = '';
foreach (array(
'display_text',
'full_display_text',
) as $var) {
$test_display = $var == 'display_text' ? $display : $display_full;
switch ($test_display['type']) {
case 'text_default':
case 'text_trimmed':
${$var} = $sanitized_text;
if ($test_display['type'] == 'text_trimmed') {
${$var} = text_summary(${$var}, $instance['settings']['text_processing'] ? $item['format'] : NULL, $test_display['settings']['trim_length']);
}
else {
// If <!--break--> was used, we need to strip it out.
${$var} = str_replace('<!--break-->', '', ${$var});
}
break;
case 'text_summary_or_trimmed':
if (!empty($item['summary'])) {
${$var} = $sanitized_summary;
}
else {
${$var} = text_summary($sanitized_text, $instance['settings']['text_processing'] ? $item['format'] : NULL, $test_display['settings']['trim_length']);
}
break;
case 'text_plain':
${$var} = strip_tags($item['value']);
break;
case 'smart_trim_format':
${$var} = '';
if (function_exists('smart_trim_field_formatter_view')) {
$smart_trim = smart_trim_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, array(
$item,
), $display);
if (isset($smart_trim[0]['#markup'])) {
${$var} = $smart_trim[0]['#markup'];
}
}
break;
}
}
if ($display_text != $full_display_text) {
return TRUE;
}
}
return FALSE;
}
function readmorecontrol_smart_trim_compare_items($items, $context) {
return readmorecontrol_text_compare_items($items, $context);
}