Index: field/field.info.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v
retrieving revision 1.4
diff -u -r1.4 field.info.inc
--- field/field.info.inc 10 Feb 2009 16:09:00 -0000 1.4
+++ field/field.info.inc 21 Mar 2009 15:47:53 -0000
@@ -142,7 +142,7 @@
cache_set('field_info_types', $info, 'cache_field');
}
}
-
+
return $info;
}
Index: field/modules/list/list.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.info,v
retrieving revision 1.2
diff -u -r1.2 list.info
--- field/modules/list/list.info 5 Feb 2009 03:42:57 -0000 1.2
+++ field/modules/list/list.info 21 Mar 2009 15:16:35 -0000
@@ -4,3 +4,4 @@
package = Core - fields
core = 7.x
files[]=list.module
+required = TRUE
Index: field/modules/number/number.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/number/number.info,v
retrieving revision 1.2
diff -u -r1.2 number.info
--- field/modules/number/number.info 5 Feb 2009 03:42:57 -0000 1.2
+++ field/modules/number/number.info 21 Mar 2009 15:16:35 -0000
@@ -4,3 +4,4 @@
package = Core - fields
core = 7.x
files[]=number.module
+required = TRUE
Index: field/modules/options/options.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.info,v
retrieving revision 1.1
diff -u -r1.1 options.info
--- field/modules/options/options.info 3 Feb 2009 17:30:11 -0000 1.1
+++ field/modules/options/options.info 21 Mar 2009 15:16:36 -0000
@@ -4,3 +4,4 @@
package = Core - fields
core = 7.x
files[]=options.module
+required = TRUE
Index: field/modules/text/text.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.info,v
retrieving revision 1.2
diff -u -r1.2 text.info
--- field/modules/text/text.info 5 Feb 2009 03:42:57 -0000 1.2
+++ field/modules/text/text.info 21 Mar 2009 15:16:36 -0000
@@ -4,3 +4,4 @@
package = Core - fields
core = 7.x
files[]=text.module
+required = TRUE
Index: field/modules/text/text.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/text/text.module,v
retrieving revision 1.4
diff -u -r1.4 text.module
--- field/modules/text/text.module 10 Mar 2009 09:45:31 -0000 1.4
+++ field/modules/text/text.module 21 Mar 2009 21:51:40 -0000
@@ -14,6 +14,9 @@
'text_textarea' => array(
'arguments' => array('element' => NULL),
),
+ 'text_textarea_with_summary' => array(
+ 'arguments' => array('element' => NULL),
+ ),
'text_textfield' => array(
'arguments' => array('element' => NULL),
),
@@ -26,11 +29,26 @@
'field_formatter_text_trimmed' => array(
'arguments' => array('element' => NULL),
),
+ 'field_formatter_text_summary_or_trimmed' => array(
+ 'arguments' => array('element' => NULL),
+ ),
+ 'field_formatter_text_trimmed_if_teaser_empty' => array(
+ 'arguments' => array('element' => NULL),
+ ),
);
}
/**
* Implementation of hook_field_info().
+ *
+ * @param $max_length
+ * The maximum length for a varchar field.
+ * @param $text_processing
+ * Whether text input filters should be used.
+ * @param $display_summary
+ * Whether the summary field should be displayed. When empty
+ * and not displayed the summary will take its value from the trimmed
+ * value of the main text field.
*/
function text_field_info() {
return array(
@@ -38,19 +56,24 @@
'label' => t('Text'),
'description' => t('This field stores varchar text in the database.'),
'settings' => array('max_length' => 255),
- 'instance_settings' => array('text_processing' => 0),
- 'widget_settings' => array('size' => 60),
+ 'instance_settings' => array('text_processing' => 0, 'summary_field' => ''),
'default_widget' => 'text_textfield',
'default_formatter' => 'text_default',
),
'text_long' => array(
'label' => t('Long text'),
'description' => t('This field stores long text in the database.'),
- 'instance_settings' => array('text_processing' => 0),
- 'widget_settings' => array('rows' => 5),
+ 'instance_settings' => array('text_processing' => 0, 'summary_field' => ''),
'default_widget' => 'text_textarea',
'default_formatter' => 'text_default',
),
+ 'text_with_summary' => array(
+ 'label' => t('Long text with a summary field'),
+ 'description' => t('This field stores long text in the database along with optional summary/teaser text.'),
+ 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
+ 'default_widget' => 'text_textarea_with_summary',
+ 'default_formatter' => 'text_summary_or_trimmed',
+ ),
);
}
@@ -65,6 +88,30 @@
'size' => 'big',
'not null' => FALSE,
),
+ 'value_format' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
+ );
+ }
+ else if ($field['type'] == 'text_with_summary') {
+ $columns = array(
+ 'value' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'summary' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'value_format' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
);
}
else {
@@ -74,15 +121,13 @@
'length' => $field['settings']['max_length'],
'not null' => FALSE,
),
+ 'value_format' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ),
);
}
- $columns += array(
- 'format' => array(
- 'type' => 'int',
- 'unsigned' => TRUE,
- 'not null' => FALSE,
- ),
- );
return $columns;
}
@@ -99,6 +144,11 @@
form_set_error($error_element, t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
}
}
+ if (!empty($item['summary'])) {
+ if (!empty($field['settings']['max_length']) && drupal_strlen($item['summary']) > $field['settings']['max_length']) {
+ form_set_error($error_element, t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])));
+ }
+ }
}
}
}
@@ -107,14 +157,22 @@
global $language;
foreach ($items as $delta => $item) {
// TODO D7 : this code is really node-related.
+ $format = $item['value_format'];
if (!empty($instance['settings']['text_processing'])) {
$check = is_null($object) || (isset($object->build_mode) && $object->build_mode == NODE_BUILD_PREVIEW);
- $text = isset($item['value']) ? check_markup($item['value'], $item['format'], isset($object->language) ? $object->language : $language, $check) : '';
+ $text = isset($item['value']) ? check_markup($item['value'], $format, isset($object->language) ? $object->language : $language, $check) : '';
+ if ($field['type'] == 'text_with_summary') {
+ $summary = isset($item['summary']) ? check_markup($item['summary'], $format, isset($object->language) ? $object->language : $language, $check) : '';
+ }
}
else {
$text = check_plain($item['value']);
+ if ($field['type'] == 'text_with_summary') {
+ $summary = check_plain($item['summary']);
+ }
}
$items[$delta]['safe'] = $text;
+ $items[$delta]['safe_summary'] = isset($summary) ? $summary : '';
}
}
@@ -123,7 +181,12 @@
*/
function text_field_is_empty($item, $field) {
if (empty($item['value']) && (string)$item['value'] !== '0') {
- return TRUE;
+ if ($field['type'] == 'text_with_summary') {
+ return (empty($item['summary']) && (string)$item['summary'] !== '0');
+ }
+ else {
+ return TRUE;
+ }
}
return FALSE;
}
@@ -153,6 +216,20 @@
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
+ ),
+ 'text_trimmed_if_teaser_empty' => array(
+ 'label' => t('Trimmed, if teaser is empty'),
+ 'field types' => array('text', 'text_long'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+ 'text_summary_or_trimmed' => array(
+ 'label' => t('Summary or Trimmed'),
+ 'field types' => array('text_with_summary'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
),
);
}
@@ -177,7 +254,155 @@
function theme_field_formatter_text_trimmed($element) {
$field = field_info_field($element['#field_name']);
$instance = field_info_instance($element['#field_name'], $element['#bundle']);
- return $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL;
+ return text_teaser($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['value_format'] : NULL);
+}
+
+/**
+ * Theme function for 'trimmed' text field formatter if the referenced
+ * teaser field is empty.
+ */
+function theme_field_formatter_text_trimmed_if_teaser_empty($element) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+
+ $object = $element['#object'];
+ if (!empty($instance['settings']['summary_field'])) {
+ $teaser = $instance['settings']['summary_field'];
+ if (!empty($object->{$teaser}[$element['#item']['#delta']]['safe'])) {
+ return '';
+ }
+ }
+
+ return text_teaser($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['value_format'] : NULL);
+}
+
+/**
+ * Theme function for 'summary or trimmed' text field formatter.
+ */
+function theme_field_formatter_text_summary_or_trimmed($element) {
+ $field = field_info_field($element['#field_name']);
+ $instance = field_info_instance($element['#field_name'], $element['#bundle']);
+
+ if (!empty($element['#item']['safe_summary'])) {
+ return $element['#item']['safe_summary'];
+ }
+ else {
+ return text_teaser($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['value_format'] : NULL);
+ }
+}
+
+/**
+ * Generate a trimmed, formatted version of a text field value.
+ *
+ * If the end of the teaser is not indicated using the delimiter
+ * then we generate the teaser automatically, trying to end it at a sensible
+ * place such as the end of a paragraph, a line break, or the end of a
+ * sentence (in that order of preference).
+ *
+ * @param $body
+ * The content for which a teaser will be generated.
+ * @param $format
+ * The format of the content. If the content contains PHP code, we do not
+ * split it up to prevent parse errors. If the line break filter is present
+ * then we treat newlines embedded in $body as line breaks.
+ * @param $size
+ * The desired character length of the teaser. If omitted, the default
+ * value will be used. Ignored if the special delimiter is present
+ * in $body.
+ * @return
+ * The generated teaser.
+ */
+function text_teaser($body, $format = NULL, $size = NULL) {
+
+ if (!isset($size)) {
+ $size = variable_get('teaser_length', 600);
+ }
+
+ // Find where the delimiter is in the body
+ $delimiter = strpos($body, '');
+
+ // If the size is zero, and there is no delimiter, the entire body is the teaser.
+ if ($size == 0 && $delimiter === FALSE) {
+ return $body;
+ }
+
+ // If a valid delimiter has been specified, use it to chop off the teaser.
+ if ($delimiter !== FALSE) {
+ return substr($body, 0, $delimiter);
+ }
+
+ // We check for the presence of the PHP evaluator filter in the current
+ // format. If the body contains PHP code, we do not split it up to prevent
+ // parse errors.
+ if (isset($format)) {
+ $filters = filter_list_format($format);
+ if (isset($filters['php/0']) && strpos($body, '') !== FALSE) {
+ return $body;
+ }
+ }
+
+ // If we have a short body, the entire body is the teaser.
+ if (drupal_strlen($body) <= $size) {
+ return $body;
+ }
+
+ // If the delimiter has not been specified, try to split at paragraph or
+ // sentence boundaries.
+
+ // The teaser may not be longer than maximum length specified. Initial slice.
+ $teaser = truncate_utf8($body, $size);
+
+ // Store the actual length of the UTF8 string -- which might not be the same
+ // as $size.
+ $max_rpos = strlen($teaser);
+
+ // How much to cut off the end of the teaser so that it doesn't end in the
+ // middle of a paragraph, sentence, or word.
+ // Initialize it to maximum in order to find the minimum.
+ $min_rpos = $max_rpos;
+
+ // Store the reverse of the teaser. We use strpos on the reversed needle and
+ // haystack for speed and convenience.
+ $reversed = strrev($teaser);
+
+ // Build an array of arrays of break points grouped by preference.
+ $break_points = array();
+
+ // A paragraph near the end of sliced teaser is most preferable.
+ $break_points[] = array('
' => 0);
+
+ // If no complete paragraph then treat line breaks as paragraphs.
+ $line_breaks = array('
' => 6, '
' => 4);
+ // Newline only indicates a line break if line break converter
+ // filter is present.
+ if (isset($filters['filter/1'])) {
+ $line_breaks["\n"] = 1;
+ }
+ $break_points[] = $line_breaks;
+
+ // If the first paragraph is too long, split at the end of a sentence.
+ $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
+
+ // Iterate over the groups of break points until a break point is found.
+ foreach ($break_points as $points) {
+ // Look for each break point, starting at the end of the teaser.
+ foreach ($points as $point => $offset) {
+ // The teaser is already reversed, but the break point isn't.
+ $rpos = strpos($reversed, strrev($point));
+ if ($rpos !== FALSE) {
+ $min_rpos = min($rpos + $offset, $min_rpos);
+ }
+ }
+
+ // If a break point was found in this group, slice and return the teaser.
+ if ($min_rpos !== $max_rpos) {
+ // Don't slice with length 0. Length must be <0 to slice from RHS.
+ return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
+ }
+ }
+
+ // If a break point was not found, still return a teaser.
+ return $teaser;
}
/**
@@ -211,6 +436,15 @@
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
),
+ 'text_textarea_with_summary' => array(
+ 'label' => t('Text area with a summary'),
+ 'field types' => array('text_with_summary'),
+ 'settings' => array('rows' => 20, 'summary_rows' => 5),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ 'default value' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
);
}
@@ -232,12 +466,21 @@
'#input' => TRUE,
'#columns' => array('value'), '#delta' => 0,
'#process' => array('text_textfield_process'),
+ '#theme_wrapper' => 'text_textfield',
'#autocomplete_path' => FALSE,
),
'text_textarea' => array(
'#input' => TRUE,
'#columns' => array('value', 'format'), '#delta' => 0,
'#process' => array('text_textarea_process'),
+ '#theme_wrapper' => 'text_textarea',
+ '#filter_value' => FILTER_FORMAT_DEFAULT,
+ ),
+ 'text_textarea_with_summary' => array(
+ '#input' => TRUE,
+ '#columns' => array('value', 'format', 'summary'), '#delta' => 0,
+ '#process' => array('text_textarea_with_summary_process'),
+ '#theme_wrapper' => 'text_textarea_with_summary',
'#filter_value' => FILTER_FORMAT_DEFAULT,
),
);
@@ -301,7 +544,8 @@
$instance = $form['#fields'][$element['#field_name']]['instance'];
$field_key = $element['#columns'][0];
$delta = $element['#delta'];
-
+ $filter_key = 'value_format';
+
$element[$field_key] = array(
'#type' => 'textfield',
'#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
@@ -317,17 +561,14 @@
'#bundle' => $element['#bundle'],
'#delta' => $element['#delta'],
'#columns' => $element['#columns'],
- );
+ );
$element[$field_key]['#maxlength'] = !empty($field['settings']['max_length']) ? $field['settings']['max_length'] : NULL;
if (!empty($instance['settings']['text_processing'])) {
- $filter_key = $element['#columns'][1];
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
- $parents = array_merge($element['#parents'] , array($filter_key));
- $element[$filter_key] = filter_form($format, 1, $parents);
+ $element[$field_key]['#text_format'] = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
}
-
+
// Used so that hook_field('validate') knows where to flag an error.
// TODO: rework that. See http://groups.drupal.org/node/18019.
$element['_error_element'] = array(
@@ -351,6 +592,8 @@
$instance = $form['#fields'][$element['#field_name']]['instance'];
$field_key = $element['#columns'][0];
$delta = $element['#delta'];
+ $filter_key = $filter_key = 'value_format';
+
$element[$field_key] = array(
'#type' => 'textarea',
'#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
@@ -365,20 +608,79 @@
'#bundle' => $element['#bundle'],
'#delta' => $element['#delta'],
'#columns' => $element['#columns'],
+ '#text_format' => isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT,
+ );
+
+ // Used so that hook_field('validate') knows where to flag an error.
+ $element['_error_element'] = array(
+ '#type' => 'value',
+ '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
);
- if (!empty($instance['settings']['text_processing'])) {
- $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
- $parents = array_merge($element['#parents'] , array($filter_key));
- $element[$filter_key] = filter_form($format, 1, $parents);
- }
+ return $element;
+}
+
+/**
+ * Process an individual element.
+ *
+ * Build the form element. When creating a form using FAPI #process,
+ * note that $element['#value'] is already set.
+ *
+ * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ */
+function text_textarea_with_summary_process($element, $edit, $form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $delta = $element['#delta'];
+ $filter_key = $filter_key = 'value_format';
+
+ $field_key = $element['#columns'][1];
+ $display = !empty($element['#value'][$field_key]) || !empty($instance['settings']['display_summary']);
+ $element[$field_key] = array(
+ '#title' => t('Summary'),
+ '#type' => $display ? 'textarea' : 'value',
+ '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+ '#rows' => 4,
+ '#weight' => 0,
+ // The following values were set by the field module and need
+ // to be passed down to the nested element.
+ '#title' => t('Summary'),
+ '#description' => t('Leave blank to use trimmed value of full text as the summary.'),
+ '#required' => $element['#required'],
+ '#field_name' => $element['#field_name'],
+ '#bundle' => $element['#bundle'],
+ '#delta' => $element['#delta'],
+ '#columns' => $element['#columns'],
+ '#display' => $display,
+ );
+
+ $field_key = $element['#columns'][0];
+ $element[$field_key] = array(
+ '#type' => 'textarea',
+ '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
+ '#rows' => $instance['widget']['settings']['rows'],
+ '#weight' => 1,
+ // The following values were set by the field module and need
+ // to be passed down to the nested element.
+ '#title' => t('Full text'),
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ '#field_name' => $element['#field_name'],
+ '#bundle' => $element['#bundle'],
+ '#delta' => $element['#delta'],
+ '#columns' => $element['#columns'],
+ '#text_format' => isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT,
+ '#required' => $instance['required'],
+ );
// Used so that hook_field('validate') knows where to flag an error.
$element['_error_element'] = array(
'#type' => 'value',
- '#value' => implode('][', array_merge($element['#parents'], array($field_key))),
+ '#value' => implode('][', array_merge($element['#parents'], array($element['#columns'][0]))),
);
+ $element['_error_element'][1]['#type'] = 'value';
+ $element['_error_element'][1]['#value'] = implode('][', array_merge($element['#parents'], array($element['#columns'][1])));
+
return $element;
}
@@ -399,4 +701,21 @@
function theme_text_textarea($element) {
return $element['#children'];
-}
\ No newline at end of file
+}
+
+function theme_text_textarea_with_summary($element) {
+ // If displaying both a textarea and a summary field, wrap them
+ // in a fieldset to make it clear they belong together.
+ $field_key = $element['#columns'][1];
+ if (!empty($element[$field_key]['#display'])) {
+ $fieldset = array(
+ '#title' => $element['#title'],
+ '#value' => $element['#children'],
+ '#attributes' => array('class' => 'text-textarea'),
+ );
+ return theme('fieldset', $fieldset);
+ }
+ else {
+ return $element['#children'];
+ }
+}
Index: field/theme/field.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/theme/field.css,v
retrieving revision 1.3
diff -u -r1.3 field.css
--- field/theme/field.css 10 Mar 2009 09:45:32 -0000 1.3
+++ field/theme/field.css 21 Mar 2009 19:16:30 -0000
@@ -32,7 +32,11 @@
margin: .5em 0 0;
}
-.form-item .number {
+form .form-item .text {
display: inline;
width: auto;
-}
\ No newline at end of file
+}
+form .form-item .number {
+ display: inline;
+ width: auto;
+}
Index: filter/filter.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v
retrieving revision 1.244
diff -u -r1.244 filter.module
--- filter/filter.module 15 Mar 2009 01:53:16 -0000 1.244
+++ filter/filter.module 21 Mar 2009 18:52:29 -0000
@@ -433,7 +433,7 @@
// Check for a cached version of this piece of text.
$cache_id = $format . ':' . $langcode . ':' . md5($text);
if ($cached = cache_get($cache_id, 'cache_filter')) {
- return $cached->data;
+ //return $cached->data;
}
// See if caching is allowed for this format.
@@ -458,13 +458,12 @@
// Store in cache with a minimum expiration time of 1 day.
if ($cache) {
- cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24));
+ //cache_set($cache_id, $text, 'cache_filter', REQUEST_TIME + (60 * 60 * 24));
}
}
else {
$text = t('n/a');
}
-
return $text;
}
Index: node/node.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.install,v
retrieving revision 1.14
diff -u -r1.14 node.install
--- node/node.install 17 Mar 2009 12:41:54 -0000 1.14
+++ node/node.install 21 Mar 2009 15:16:36 -0000
@@ -239,18 +239,6 @@
'not null' => TRUE,
'default' => '',
),
- 'body' => array(
- 'description' => 'The body of this version.',
- 'type' => 'text',
- 'not null' => TRUE,
- 'size' => 'big',
- ),
- 'teaser' => array(
- 'description' => 'The teaser of this version.',
- 'type' => 'text',
- 'not null' => TRUE,
- 'size' => 'big',
- ),
'log' => array(
'description' => 'The log entry explaining the changes in this version.',
'type' => 'text',
@@ -326,7 +314,7 @@
'default' => '',
),
'has_body' => array(
- 'description' => 'Boolean indicating whether this type uses the {node_revision}.body field.',
+ 'description' => 'Boolean indicating whether this type has the body field attached.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
Index: node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1032
diff -u -r1.1032 node.module
--- node/node.module 20 Mar 2009 19:18:10 -0000 1.1032
+++ node/node.module 21 Mar 2009 21:20:45 -0000
@@ -46,6 +46,11 @@
define('NODE_BUILD_PRINT', 5);
/**
+ * Name of the default body field.
+ */
+define('NODE_BODY_FIELD', 'node_body');
+
+/**
* Implementation of hook_help().
*/
function node_help($path, $arg) {
@@ -290,186 +295,6 @@
}
/**
- * See if the user used JS to submit a teaser.
- */
-function node_teaser_js(&$form, &$form_state) {
- if (isset($form_state['input']['teaser_js'])) {
- // Glue the teaser to the body.
- if (trim($form_state['values']['teaser_js'])) {
- // Space the teaser from the body
- $body = trim($form_state['values']['teaser_js']) . "\r\n\r\n" . trim($form_state['values']['body']);
- }
- else {
- // Empty teaser, no spaces.
- $body = '' . $form_state['values']['body'];
- }
- // Pass updated body value on to preview/submit form processing.
- form_set_value($form['body'], $body, $form_state);
- // Pass updated body value back onto form for those cases
- // in which the form is redisplayed.
- $form['body']['#value'] = $body;
- }
- return $form;
-}
-
-/**
- * Ensure value of "teaser_include" checkbox is consistent with other form data.
- *
- * This handles two situations in which an unchecked checkbox is rejected:
- *
- * 1. The user defines a teaser (summary) but it is empty;
- * 2. The user does not define a teaser (summary) (in this case an
- * unchecked checkbox would cause the body to be empty, or missing
- * the auto-generated teaser).
- *
- * If JavaScript is active then it is used to force the checkbox to be
- * checked when hidden, and so the second case will not arise.
- *
- * In either case a warning message is output.
- */
-function node_teaser_include_verify(&$form, &$form_state) {
- $message = '';
-
- // $form_state['input'] is set only when the form is built for preview/submit.
- if (isset($form_state['input']['body']) && isset($form_state['values']['teaser_include']) && !$form_state['values']['teaser_include']) {
- // "teaser_include" checkbox is present and unchecked.
- if (strpos($form_state['values']['body'], '') === 0) {
- // Teaser is empty string.
- $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting is ignored when the summary is empty.');
- }
- elseif (strpos($form_state['values']['body'], '') === FALSE) {
- // Teaser delimiter is not present in the body.
- $message = t('You specified that the summary should not be shown when this post is displayed in full view. This setting has been ignored since you have not defined a summary for the post. (To define a summary, insert the delimiter "<!--break-->" (without the quotes) in the Body of the post to indicate the end of the summary and the start of the main content.)');
- }
-
- if (!empty($message)) {
- drupal_set_message($message, 'warning');
- // Pass new checkbox value on to preview/submit form processing.
- form_set_value($form['teaser_include'], 1, $form_state);
- // Pass new checkbox value back onto form for those cases
- // in which form is redisplayed.
- $form['teaser_include']['#value'] = 1;
- }
- }
-
- return $form;
-}
-
-/**
- * Generate a teaser for a node body.
- *
- * If the end of the teaser is not indicated using the delimiter
- * then we generate the teaser automatically, trying to end it at a sensible
- * place such as the end of a paragraph, a line break, or the end of a
- * sentence (in that order of preference).
- *
- * @param $body
- * The content for which a teaser will be generated.
- * @param $format
- * The format of the content. If the content contains PHP code, we do not
- * split it up to prevent parse errors. If the line break filter is present
- * then we treat newlines embedded in $body as line breaks.
- * @param $size
- * The desired character length of the teaser. If omitted, the default
- * value will be used. Ignored if the special delimiter is present
- * in $body.
- * @return
- * The generated teaser.
- */
-function node_teaser($body, $format = NULL, $size = NULL) {
-
- if (!isset($size)) {
- $size = variable_get('teaser_length', 600);
- }
-
- // Find where the delimiter is in the body
- $delimiter = strpos($body, '');
-
- // If the size is zero, and there is no delimiter, the entire body is the teaser.
- if ($size == 0 && $delimiter === FALSE) {
- return $body;
- }
-
- // If a valid delimiter has been specified, use it to chop off the teaser.
- if ($delimiter !== FALSE) {
- return substr($body, 0, $delimiter);
- }
-
- // We check for the presence of the PHP evaluator filter in the current
- // format. If the body contains PHP code, we do not split it up to prevent
- // parse errors.
- if (isset($format)) {
- $filters = filter_list_format($format);
- if (isset($filters['php/0']) && strpos($body, '') !== FALSE) {
- return $body;
- }
- }
-
- // If we have a short body, the entire body is the teaser.
- if (drupal_strlen($body) <= $size) {
- return $body;
- }
-
- // If the delimiter has not been specified, try to split at paragraph or
- // sentence boundaries.
-
- // The teaser may not be longer than maximum length specified. Initial slice.
- $teaser = truncate_utf8($body, $size);
-
- // Store the actual length of the UTF8 string -- which might not be the same
- // as $size.
- $max_rpos = strlen($teaser);
-
- // How much to cut off the end of the teaser so that it doesn't end in the
- // middle of a paragraph, sentence, or word.
- // Initialize it to maximum in order to find the minimum.
- $min_rpos = $max_rpos;
-
- // Store the reverse of the teaser. We use strpos on the reversed needle and
- // haystack for speed and convenience.
- $reversed = strrev($teaser);
-
- // Build an array of arrays of break points grouped by preference.
- $break_points = array();
-
- // A paragraph near the end of sliced teaser is most preferable.
- $break_points[] = array('' => 0);
-
- // If no complete paragraph then treat line breaks as paragraphs.
- $line_breaks = array('
' => 6, '
' => 4);
- // Newline only indicates a line break if line break converter
- // filter is present.
- if (isset($filters['filter/1'])) {
- $line_breaks["\n"] = 1;
- }
- $break_points[] = $line_breaks;
-
- // If the first paragraph is too long, split at the end of a sentence.
- $break_points[] = array('. ' => 1, '! ' => 1, '? ' => 1, '。' => 0, '؟ ' => 1);
-
- // Iterate over the groups of break points until a break point is found.
- foreach ($break_points as $points) {
- // Look for each break point, starting at the end of the teaser.
- foreach ($points as $point => $offset) {
- // The teaser is already reversed, but the break point isn't.
- $rpos = strpos($reversed, strrev($point));
- if ($rpos !== FALSE) {
- $min_rpos = min($rpos + $offset, $min_rpos);
- }
- }
-
- // If a break point was found in this group, slice and return the teaser.
- if ($min_rpos !== $max_rpos) {
- // Don't slice with length 0. Length must be <0 to slice from RHS.
- return ($min_rpos === 0) ? $teaser : substr($teaser, 0, 0 - $min_rpos);
- }
- }
-
- // If a break point was not found, still return a teaser.
- return $teaser;
-}
-
-/**
* Builds a list of available node types, and returns all of part of this list
* in the specified format.
*
@@ -583,6 +408,7 @@
if (!empty($type->old_type) && $type->old_type != $type->type) {
field_attach_rename_bundle($type->old_type, $type->type);
}
+ node_configure_fields($type);
module_invoke_all('node_type', 'update', $type);
return SAVED_UPDATED;
}
@@ -591,13 +417,49 @@
db_insert('node_type')->fields($fields)->execute();
field_attach_create_bundle($type->type);
-
+ node_configure_fields($type);
module_invoke_all('node_type', 'insert', $type);
return SAVED_NEW;
}
}
/**
+ * Manage the field(s) for a node type.
+ */
+function node_configure_fields($type) {
+
+ // Add or remove the body field, as needed.
+ $field = field_info_field(NODE_BODY_FIELD);
+ $instance = field_info_instance(NODE_BODY_FIELD, $type->type);
+ if ($type->has_body) {
+ if (empty($field)) {
+ $field = array(
+ 'field_name' => NODE_BODY_FIELD,
+ 'type' => 'text_with_summary',
+ );
+ $field = field_create_field($field);
+ }
+ if (empty($instance)) {
+ $instance = array(
+ 'field_name' => NODE_BODY_FIELD,
+ 'bundle' => $type->type,
+ 'widget_type' => 'text_textarea_with_summary',
+ 'label' => $type->body_label,
+ );
+ field_create_instance($instance);
+ }
+ else {
+ $instance['label'] = $type->body_label;
+ field_update_instance($instance);
+ }
+ }
+ elseif (!empty($instance)) {
+ field_delete_instance($instance);
+ }
+
+}
+
+/**
* Deletes a node type from the database.
*
* @param $type
@@ -974,7 +836,8 @@
// Make sure the body has the minimum number of words.
// TODO : use a better word counting algorithm that will work in other languages
- if (!empty($type->min_word_count) && isset($node->body) && count(explode(' ', $node->body)) < $type->min_word_count) {
+ if (!empty($type->min_word_count) && isset($node->{NODE_BODY_FIELD}[0]['value']) && count(explode(' ', $node->{NODE_BODY_FIELD}[0]['value'])) < $type->min_word_count) {
+ // TODO: Use Field API to set this error.
form_set_error('body', t('The body of your @type is too short. You need at least %words words.', array('%words' => $type->min_word_count, '@type' => $type->name)));
}
@@ -1014,25 +877,6 @@
// Convert the node to an object, if necessary.
$node = (object)$node;
- // Generate the teaser, but only if it hasn't been set (e.g. by a
- // module-provided 'teaser' form item).
- if (!isset($node->teaser)) {
- if (isset($node->body)) {
- $node->format = (!empty($node->body_format) ? $node->body_format : FILTER_FORMAT_DEFAULT);
- $node->teaser = node_teaser($node->body, isset($node->format) ? $node->format : NULL);
- // Chop off the teaser from the body if needed. The teaser_include
- // property might not be set (eg. in Blog API postings), so only act on
- // it, if it was set with a given value.
- if (isset($node->teaser_include) && !$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) {
- $node->body = substr($node->body, strlen($node->teaser));
- }
- }
- else {
- $node->teaser = '';
- $node->format = 0;
- }
- }
-
if (user_access('administer nodes')) {
// Populate the "authored by" field.
if ($account = user_load_by_name($node->name)) {
@@ -1072,16 +916,6 @@
if (!isset($node->log)) {
$node->log = '';
}
-
- // For the same reasons, make sure we have $node->teaser and
- // $node->body. We should consider making these fields nullable
- // in a future version since node types are not required to use them.
- if (!isset($node->teaser)) {
- $node->teaser = '';
- }
- if (!isset($node->body)) {
- $node->body = '';
- }
}
elseif (!empty($node->revision)) {
$node->old_vid = $node->vid;
@@ -1216,30 +1050,6 @@
}
/**
- * Apply filters and build the node's standard elements.
- */
-function node_prepare($node, $teaser = FALSE) {
- // First we'll overwrite the existing node teaser and body with
- // the filtered copies! Then, we'll stick those into the content
- // array and set the read more flag if appropriate.
- $node->readmore = (strlen($node->teaser) < strlen($node->body));
-
- if ($teaser == FALSE) {
- $node->body = check_markup($node->body, $node->format, $node->language, FALSE);
- }
- else {
- $node->teaser = check_markup($node->teaser, $node->format, $node->language, FALSE);
- }
-
- $node->content['body'] = array(
- '#markup' => $teaser ? $node->teaser : $node->body,
- '#weight' => 0,
- );
-
- return $node;
-}
-
-/**
* Builds a structured array representing the node's content.
*
* @param $node
@@ -1249,7 +1059,7 @@
*
* @return
* An structured array containing the individual elements
- * of the node's body.
+ * of the node's content.
*/
function node_build_content($node, $teaser = FALSE) {
@@ -1258,21 +1068,33 @@
$node->build_mode = NODE_BUILD_NORMAL;
}
- // Remove the delimiter (if any) that separates the teaser from the body.
- $node->body = isset($node->body) ? str_replace('', '', $node->body) : '';
-
// The 'view' hook can be implemented to overwrite the default function
// to display nodes.
if (node_hook($node, 'view')) {
$node = node_invoke($node, 'view', $teaser);
}
- else {
- $node = node_prepare($node, $teaser);
- }
// Build fields content.
+ if (empty($node->content)) {
+ $node->content = array();
+ };
$node->content += field_attach_view('node', $node, $teaser);
+ // Always display a read more link on teasers because we have no way
+ // to know when a teaser view is different than a full view.
+ $links = array();
+ if ($teaser) {
+ $links['node_readmore'] = array(
+ 'title' => t('Read more'),
+ 'href' => 'node/' . $node->nid,
+ 'attributes' => array('rel' => 'tag', 'title' => strip_tags($node->title))
+ );
+ }
+ $node->content['links']['node'] = array(
+ '#type' => 'node_links',
+ '#value' => $links
+ );
+
// Allow modules to make their own additions to the node.
node_invoke_node($node, 'view', $teaser);
@@ -1492,16 +1314,16 @@
// Load results.
$results = array();
foreach ($find as $item) {
- // Build the node body.
+ // Render the node.
$node = node_load($item->sid);
$node->build_mode = NODE_BUILD_SEARCH_RESULT;
$node = node_build_content($node, FALSE, FALSE);
- $node->body = drupal_render($node->content);
+ $node->rendered = drupal_render($node->content);
// Fetch comments for snippet.
- $node->body .= module_invoke('comment', 'node', $node, 'update_index');
+ $node->rendered .= module_invoke('comment', 'node', $node, 'update_index');
// Fetch terms for snippet.
- $node->body .= module_invoke('taxonomy', 'node', $node, 'update_index');
+ $node->rendered .= module_invoke('taxonomy', 'node', $node, 'update_index');
$extra = node_invoke_node($node, 'search_result');
@@ -1514,7 +1336,7 @@
'node' => $node,
'extra' => $extra,
'score' => $total ? ($item->calculated_score / $total) : 0,
- 'snippet' => search_excerpt($keys, $node->body),
+ 'snippet' => search_excerpt($keys, $node->rendered),
);
}
return $results;
@@ -1623,7 +1445,7 @@
$links = array();
if ($type == 'node') {
- if ($teaser == 1 && $node->teaser && !empty($node->readmore)) {
+ if ($teaser == 1) {
$links['node_read_more'] = array(
'title' => t('Read more'),
'href' => "node/$node->nid",
@@ -1929,24 +1751,11 @@
if (node_hook($item, 'view')) {
$item = node_invoke($item, 'view', $teaser, FALSE);
}
- else {
- $item = node_prepare($item, $teaser);
- }
// Allow modules to change $node->content before the node is rendered.
node_invoke_node($item, 'view', $teaser, FALSE);
- // Set the proper node property, then unset unused $node property so that a
- // bad theme can not open a security hole.
- $content = drupal_render($item->content);
- if ($teaser) {
- $item->teaser = $content;
- unset($item->body);
- }
- else {
- $item->body = $content;
- unset($item->teaser);
- }
+ $item->rendered = drupal_render($item->content);
// Allow modules to modify the fully-built node.
node_invoke_node($item, 'alter', $teaser, FALSE);
@@ -1964,13 +1773,11 @@
// Prepare the item description
switch ($item_length) {
case 'fulltext':
- $item_text = $item->body;
+ $item_text = $item->rendered;
break;
case 'teaser':
- $item_text = $item->teaser;
- if (!empty($item->readmore)) {
- $item_text .= '' . l(t('read more'), 'node/' . $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) . '
';
- }
+ $item_text = $item->rendered;
+ $item_text .= '' . l(t('read more'), 'node/' . $item->nid, array('absolute' => TRUE, 'attributes' => array('target' => '_blank'))) . '
';
break;
case 'title':
$item_text = '';
@@ -2103,12 +1910,12 @@
// save the changed time of the most recent indexed node, for the search results half-life calculation
variable_set('node_cron_last', $node->changed);
- // Build the node body.
+ // Render the node.
$node->build_mode = NODE_BUILD_SEARCH_INDEX;
$node = node_build_content($node, FALSE, FALSE);
- $node->body = drupal_render($node->content);
+ $node->rendered = drupal_render($node->content);
- $text = '' . check_plain($node->title) . '
' . $node->body;
+ $text = '' . check_plain($node->title) . '
' . $node->rendered;
// Fetch extra data normally not visible
$extra = node_invoke_node($node, 'update_index');
@@ -2792,10 +2599,6 @@
);
}
- if ($type->has_body) {
- $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
- }
-
return $form;
}
Index: node/node.pages.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v
retrieving revision 1.57
diff -u -r1.57 node.pages.inc
--- node/node.pages.inc 14 Mar 2009 23:01:36 -0000 1.57
+++ node/node.pages.inc 21 Mar 2009 18:06:46 -0000
@@ -106,7 +106,7 @@
$form['#prefix'] = $form_state['node_preview'];
}
$node = (object)$node;
- foreach (array('body', 'title', 'format') as $key) {
+ foreach (array('title') as $key) {
if (!isset($node->$key)) {
$node->$key = NULL;
}
@@ -267,48 +267,6 @@
}
/**
- * Return a node body field, with format and teaser.
- */
-function node_body_field(&$node, $label, $word_count) {
-
- // Check if we need to restore the teaser at the beginning of the body.
- $include = !isset($node->teaser) || ($node->teaser == substr($node->body, 0, strlen($node->teaser)));
-
- $form = array(
- '#after_build' => array('node_teaser_js', 'node_teaser_include_verify'));
-
- $form['#prefix'] = '';
- $form['#suffix'] = '
';
-
- $form['teaser_js'] = array(
- '#type' => 'textarea',
- '#rows' => 10,
- '#teaser' => 'edit-body',
- '#teaser_checkbox' => 'edit-teaser-include',
- '#disabled' => TRUE,
- );
-
- $form['teaser_include'] = array(
- '#type' => 'checkbox',
- '#title' => t('Show summary in full view'),
- '#default_value' => $include,
- '#prefix' => '',
- '#suffix' => '
',
- );
-
- $form['body'] = array(
- '#type' => 'textarea',
- '#title' => check_plain($label),
- '#default_value' => $include ? $node->body : ($node->teaser . $node->body),
- '#rows' => 20,
- '#required' => ($word_count > 0),
- '#text_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT,
- );
-
- return $form;
-}
-
-/**
* Button submit function: handle the 'Delete' button on the node form.
*/
function node_form_delete_submit($form, &$form_state) {
@@ -369,16 +327,6 @@
$node->changed = REQUEST_TIME;
- // Extract a teaser, if it hasn't been set (e.g. by a module-provided
- // 'teaser' form item).
- if (!isset($node->teaser)) {
- $node->teaser = empty($node->body) ? '' : node_teaser($node->body, $node->format);
- // Chop off the teaser from the body if needed.
- if (!$node->teaser_include && $node->teaser == substr($node->body, 0, strlen($node->teaser))) {
- $node->body = substr($node->body, strlen($node->teaser));
- }
- }
-
// Display a preview of the node.
// Previewing alters $node so it needs to be cloned.
if (!form_get_errors()) {
@@ -404,28 +352,20 @@
$output = '';
$preview_trimmed_version = FALSE;
- // Do we need to preview trimmed version of post as well as full version?
- if (isset($node->teaser) && isset($node->body)) {
- $teaser = trim($node->teaser);
- $body = trim(str_replace('', '', $node->body));
-
- // Preview trimmed version if teaser and body will appear different;
- // also (edge case) if both teaser and body have been specified by the user
- // and are actually the same.
- if ($teaser != $body || ($body && strpos($node->body, '') === 0)) {
- $preview_trimmed_version = TRUE;
- }
- }
- if ($preview_trimmed_version) {
+ $trimmed = drupal_render(node_build(clone $node, TRUE));
+ $full = drupal_render(node_build($node, FALSE));
+
+ // Do we need to preview trimmed version of post as well as full version?
+ if ($trimmed != $full) {
drupal_set_message(t('The trimmed version of your post shows what your post looks like when promoted to the main page or when exported for syndication. You can insert the delimiter "<!--break-->" (without the quotes) to fine-tune where your post gets split.'));
$output .= '
' . t('Preview trimmed version') . '
';
- $output .= drupal_render(node_build(clone $node, TRUE));
+ $output .= $trimmed;
$output .= '' . t('Preview full version') . '
';
- $output .= drupal_render(node_build($node, FALSE));
+ $output .= $full;
}
else {
- $output .= drupal_render(node_build($node, FALSE));
+ $output .= $full;
}
$output .= "\n";