' . t('About') . ''; $output .= '

' . 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.') . '

'; 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 . ""; 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('enabled' => 'global')); if (!$is_edit && $settings['enabled'] == 'global') { $settings = array(); } } if ($bundle) { $settings += (array) variable_get('readmorecontrol_format__' . $entity_type . '__' . $bundle, 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)) { $content = array( 'display' => $display, 'display_full' => $display_full, 'instance' => $instance, 'field' => $field, 'langcode' => $langcode, ); if ($func($items, $content)) { $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 about @title', 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'] == '') { 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 = ' ' . $entity->readmorelink . ''; if (preg_match('!]*>\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'] = ''; } } else { $field_item['#suffix'] = ''; } 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 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 '' . $readmore['title'] . ''; } 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, $content) { extract($content, 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']); } 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; } } if ($display_text != $full_display_text) { return TRUE; } } return FALSE; }