summaryrefslogtreecommitdiff
path: root/modules/field
diff options
context:
space:
mode:
Diffstat (limited to 'modules/field')
-rw-r--r--modules/field/field.crud.inc4
-rw-r--r--modules/field/field.test7
-rw-r--r--modules/field/modules/list/list.info1
-rw-r--r--modules/field/modules/number/number.info1
-rw-r--r--modules/field/modules/options/options.info1
-rw-r--r--modules/field/modules/text/text.info1
-rw-r--r--modules/field/modules/text/text.module444
-rw-r--r--modules/field/modules/text/text.test153
-rw-r--r--modules/field/theme/field.css8
9 files changed, 550 insertions, 70 deletions
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index 97b3d8a70..956122ecf 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -425,7 +425,9 @@ function field_create_instance($instance) {
// Set the field id.
$instance['field_id'] = $field['id'];
- // TODO: Check that the specifed bundle exists.
+ // Note that we do *not* prevent creating a field on non-existing bundles,
+ // because that would break the 'Body as field' upgrade for contrib
+ // node types.
// TODO: Check that the widget type is known and can handle the field type ?
// TODO: Check that the formatters are known and can handle the field type ?
diff --git a/modules/field/field.test b/modules/field/field.test
index c4aee219b..07d047f06 100644
--- a/modules/field/field.test
+++ b/modules/field/field.test
@@ -930,10 +930,9 @@ class FieldInfoTestCase extends DrupalWebTestCase {
$this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
}
- // Verify that no fields or instances exist
- $fields = field_info_fields();
+ // Verify that no unexpected instances exist.
+ $core_fields = field_info_fields();
$instances = field_info_instances(FIELD_TEST_BUNDLE);
- $this->assertTrue(empty($fields), t('With no fields, info fields is empty.'));
$this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
// Create a field, verify it shows up.
@@ -943,7 +942,7 @@ class FieldInfoTestCase extends DrupalWebTestCase {
);
field_create_field($field);
$fields = field_info_fields();
- $this->assertEqual(count($fields), 1, t('One field exists'));
+ $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists'));
$this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
$this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
$this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info
index 1e201a386..96a98d500 100644
--- a/modules/field/modules/list/list.info
+++ b/modules/field/modules/list/list.info
@@ -5,3 +5,4 @@ package = Core - fields
version = VERSION
core = 7.x
files[]=list.module
+required = TRUE
diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info
index e77fa7959..314506807 100644
--- a/modules/field/modules/number/number.info
+++ b/modules/field/modules/number/number.info
@@ -5,3 +5,4 @@ package = Core - fields
version = VERSION
core = 7.x
files[]=number.module
+required = TRUE
diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info
index d61d108b8..04d53c422 100644
--- a/modules/field/modules/options/options.info
+++ b/modules/field/modules/options/options.info
@@ -5,3 +5,4 @@ package = Core - fields
version = VERSION
core = 7.x
files[]=options.module
+required = TRUE
diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info
index 17a676025..6e8bb24ee 100644
--- a/modules/field/modules/text/text.info
+++ b/modules/field/modules/text/text.info
@@ -6,3 +6,4 @@ version = VERSION
core = 7.x
files[] = text.module
files[] = text.test
+required = TRUE \ No newline at end of file
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index 7c31d54cb..00ba52017 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -14,6 +14,9 @@ function text_theme() {
'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,22 @@ function text_theme() {
'field_formatter_text_trimmed' => array(
'arguments' => array('element' => NULL),
),
+ 'field_formatter_text_summary_or_trimmed' => array(
+ 'arguments' => array('element' => NULL),
+ ),
);
}
/**
* Implement hook_field_info().
+ *
+ * Field settings:
+ * - max_length: the maximum length for a varchar field.
+ * Instance settings:
+ * - text_processing: whether text input filters should be used.
+ * - 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(
@@ -39,18 +53,25 @@ function text_field_info() {
'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),
'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.'),
+ 'settings' => array('max_length' => ''),
'instance_settings' => array('text_processing' => 0),
- 'widget_settings' => array('rows' => 5),
'default_widget' => 'text_textarea',
'default_formatter' => 'text_default',
),
+ 'text_with_summary' => array(
+ 'label' => t('Long text and summary'),
+ 'description' => t('This field stores long text in the database along with optional summary text.'),
+ 'settings' => array('max_length' => ''),
+ 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0),
+ 'default_widget' => 'text_textarea_with_summary',
+ 'default_formatter' => 'text_summary_or_trimmed',
+ ),
);
}
@@ -58,23 +79,39 @@ function text_field_info() {
* Implement hook_field_schema().
*/
function text_field_schema($field) {
- if ($field['type'] == 'text_long') {
- $columns = array(
- 'value' => array(
- 'type' => 'text',
- 'size' => 'big',
- 'not null' => FALSE,
- ),
- );
- }
- else {
- $columns = array(
- 'value' => array(
- 'type' => 'varchar',
- 'length' => $field['settings']['max_length'],
- 'not null' => FALSE,
- ),
- );
+ switch ($field['type']) {
+ case 'text':
+ $columns = array(
+ 'value' => array(
+ 'type' => 'varchar',
+ 'length' => $field['settings']['max_length'],
+ 'not null' => FALSE,
+ ),
+ );
+ break;
+ case 'text_long':
+ $columns = array(
+ 'value' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ );
+ break;
+ case 'text_with_summary':
+ $columns = array(
+ 'value' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ 'summary' => array(
+ 'type' => 'text',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ),
+ );
+ break;
}
$columns += array(
'format' => array(
@@ -95,16 +132,27 @@ function text_field_schema($field) {
* Implement hook_field_validate().
*
* Possible error codes:
- * - 'text_max_length': The value exceeds the maximum length.
+ * - 'text_value_max_length': The value exceeds the maximum length.
+ * - 'text_summary_max_length': The summary exceeds the maximum length.
*/
function text_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
foreach ($items as $delta => $item) {
- if (!empty($item['value'])) {
- if (!empty($field['settings']['max_length']) && drupal_strlen($item['value']) > $field['settings']['max_length']) {
- $errors[$field['field_name']][$delta][] = array(
- 'error' => 'text_max_length',
- 'message' => t('%name: the value may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length'])),
- );
+ foreach (array('value' => t('full text'), 'summary' => t('summary')) as $column => $desc) {
+ if (!empty($item[$column])) {
+ if (!empty($field['settings']['max_length']) && drupal_strlen($item[$column]) > $field['settings']['max_length']) {
+ switch ($column) {
+ case 'value':
+ $message = t('%name: the text may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']));
+ break;
+ case 'summary':
+ $message = t('%name: the summary may not be longer than %max characters.', array('%name' => $instance['label'], '%max' => $field['settings']['max_length']));
+ break;
+ }
+ $errors[$field['field_name']][$delta][] = array(
+ 'error' => "text_{$column}_length",
+ 'message' => $message,
+ );
+ }
}
}
}
@@ -126,17 +174,22 @@ function text_field_load($obj_type, $objects, $field, $instances, &$items) {
if (!empty($instances[$id]['settings']['text_processing'])) {
// Only process items with a cacheable format, the rest will be
// handled by text_field_sanitize().
- if (filter_format_allowcache($item['format'])) {
+ $format = $item['format'];
+ if (filter_format_allowcache($format)) {
// TODO D7 : this code is really node-related.
$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->language, $check, FALSE) : '';
+ $lang = isset($object->language) ? $object->language : $language->language;
+ $items[$id][$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check, FALSE) : '';
+ if ($field['type'] == 'text_with_summary') {
+ $items[$id][$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check, FALSE) : '';
+ }
}
}
else {
- $text = check_plain($item['value']);
- }
- if (isset($text)) {
- $items[$id][$delta]['safe'] = $text;
+ $items[$id][$delta]['safe'] = check_plain($item['value']);
+ if ($field['type'] == 'text_with_summary') {
+ $items[$id][$delta]['safe_summary'] = check_plain($item['summary']);
+ }
}
}
}
@@ -155,14 +208,21 @@ function text_field_sanitize($obj_type, $object, $field, $instance, &$items) {
// from a form preview.
if (!isset($items[$delta]['safe'])) {
if (!empty($instance['settings']['text_processing'])) {
+ $format = $item['format'];
// TODO D7 : this code is really node-related.
$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->language, $check) : '';
+ $lang = isset($object->language) ? $object->language : $language->language;
+ $items[$delta]['safe'] = isset($item['value']) ? check_markup($item['value'], $format, $lang, $check) : '';
+ if ($field['type'] == 'text_with_summary') {
+ $items[$delta]['safe_summary'] = isset($item['summary']) ? check_markup($item['summary'], $format, $lang, $check) : '';
+ }
}
else {
- $text = check_plain($item['value']);
+ $item[$delta]['safe'] = check_plain($item['value']);
+ if ($field['type'] == 'text_with_summary') {
+ $item[$delta]['safe_summary'] = check_plain($item['summary']);
+ }
}
- $items[$delta]['safe'] = $text;
}
}
}
@@ -184,21 +244,39 @@ function text_field_formatter_info() {
return array(
'text_default' => array(
'label' => t('Default'),
- 'field types' => array('text', 'text_long'),
+ 'field types' => array('text', 'text_long', 'text_with_summary'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
),
'text_plain' => array(
'label' => t('Plain text'),
- 'field types' => array('text', 'text_long'),
+ 'field types' => array('text', 'text_long', 'text_with_summary'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
),
+
+ // The text_trimmed formatter displays the trimmed version of the
+ // full element of the field. It is intended to be used with text
+ // and text_long fields. It also works with text_with_summary
+ // fields though the text_summary_or_trimmed formatter makes more
+ // sense for that field type.
'text_trimmed' => array(
'label' => t('Trimmed'),
- 'field types' => array('text', 'text_long'),
+ 'field types' => array('text', 'text_long', 'text_with_summary'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
+ ),
+ ),
+
+ // The 'summary or trimmed' field formatter for text_with_summary
+ // fields displays returns the summary element of the field or, if
+ // the summary is empty, the trimmed version of the full element
+ // of the field.
+ 'text_summary_or_trimmed' => array(
+ 'label' => t('Summary or trimmed'),
+ 'field types' => array('text_with_summary'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
),
@@ -226,7 +304,150 @@ function theme_field_formatter_text_plain($element) {
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_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL);
+}
+
+/**
+ * Theme function for 'summary or trimmed' field formatter for
+ * text_with_summary fields. This formatter returns the summary
+ * element of the field or, if the summary is empty, the trimmed
+ * version of the full element of the field.
+ */
+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_summary($element['#item']['safe'], $instance['settings']['text_processing'] ? $element['#item']['format'] : NULL);
+ }
+}
+
+/**
+ * Generate a trimmed, formatted version of a text field value.
+ *
+ * If the end of the summary is not indicated using the <!--break--> delimiter
+ * then we generate the summary 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 $text
+ * The content for which a summary will be generated.
+ * @param $format
+ * The format of the content.
+ * If the PHP filter is present and $text 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
+ * $text as line breaks.
+ * If the htmlcorrector filter is present, it will be run on the generated
+ * summary (if different from the incoming $text).
+ * @param $size
+ * The desired character length of the summary. If omitted, the default
+ * value will be used. Ignored if the special delimiter is present
+ * in $text.
+ * @return
+ * The generated summary.
+ */
+function text_summary($text, $format = NULL, $size = NULL) {
+
+ if (!isset($size)) {
+ // What used to be called 'teaser' is now called 'summary', but
+ // the variable 'teaser_length' is preserved for backwards compatibility.
+ $size = variable_get('teaser_length', 600);
+ }
+
+ // Find where the delimiter is in the body
+ $delimiter = strpos($text, '<!--break-->');
+
+ // If the size is zero, and there is no delimiter, the entire body is the summary.
+ if ($size == 0 && $delimiter === FALSE) {
+ return $text;
+ }
+
+ // If a valid delimiter has been specified, use it to chop off the summary.
+ if ($delimiter !== FALSE) {
+ return substr($text, 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($text, '<?') !== FALSE) {
+ return $text;
+ }
+ }
+
+ // If we have a short body, the entire body is the summary.
+ if (drupal_strlen($text) <= $size) {
+ return $text;
+ }
+
+ // If the delimiter has not been specified, try to split at paragraph or
+ // sentence boundaries.
+
+ // The summary may not be longer than maximum length specified. Initial slice.
+ $summary = truncate_utf8($text, $size);
+
+ // Store the actual length of the UTF8 string -- which might not be the same
+ // as $size.
+ $max_rpos = strlen($summary);
+
+ // How much to cut off the end of the summary 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 summary. We use strpos on the reversed needle and
+ // haystack for speed and convenience.
+ $reversed = strrev($summary);
+
+ // Build an array of arrays of break points grouped by preference.
+ $break_points = array();
+
+ // A paragraph near the end of sliced summary is most preferable.
+ $break_points[] = array('</p>' => 0);
+
+ // If no complete paragraph then treat line breaks as paragraphs.
+ $line_breaks = array('<br />' => 6, '<br>' => 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 summary.
+ foreach ($points as $point => $offset) {
+ // The summary 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 stop searching.
+ if ($min_rpos !== $max_rpos) {
+ // Don't slice with length 0. Length must be <0 to slice from RHS.
+ $summary = ($min_rpos === 0) ? $summary : substr($summary, 0, 0 - $min_rpos);
+ break;
+ }
+ }
+
+ // If the htmlcorrector filter is present, apply it to the generated summary.
+ if (isset($filters['filter/3'])) {
+ $summary = _filter_htmlcorrector($summary);
+ }
+
+ return $summary;
}
/**
@@ -260,6 +481,15 @@ function text_field_widget_info() {
'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,
+ ),
+ ),
);
}
@@ -281,14 +511,23 @@ function text_elements() {
'#input' => TRUE,
'#columns' => array('value'), '#delta' => 0,
'#process' => array('text_textfield_elements_process'),
+ '#theme_wrapper' => 'text_textfield',
'#autocomplete_path' => FALSE,
- ),
+ ),
'text_textarea' => array(
'#input' => TRUE,
'#columns' => array('value', 'format'), '#delta' => 0,
'#process' => array('text_textarea_elements_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,
+ ),
);
}
@@ -329,6 +568,10 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del
'#type' => $instance['widget']['type'],
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
);
+ if (!empty($instance['settings']['text_processing'])) {
+ $element['#value_callback'] = 'text_field_widget_formatted_text_value';
+ }
+
return $element;
}
@@ -336,7 +579,17 @@ function text_field_widget(&$form, &$form_state, $field, $instance, $items, $del
* Implement hook_field_widget_error().
*/
function text_field_widget_error($element, $error) {
- form_error($element['value'], $error['message']);
+ switch ($error['error']) {
+ case 'text_summary_max_length':
+ $error_element = $element[$element['#columns'][1]];
+ break;
+
+ default:
+ $error_element = $element[$element['#columns'][0]];
+ break;
+ }
+
+ form_error($error_element, $error['message']);
}
/**
@@ -364,24 +617,17 @@ function text_textfield_elements_process($element, $form_state, $form) {
'#autocomplete_path' => $element['#autocomplete_path'],
'#size' => $instance['widget']['settings']['size'],
'#attributes' => array('class' => 'text'),
- // The following values were set by the field module and need
- // to be passed down to the nested element.
'#title' => $element['#title'],
'#description' => $element['#description'],
'#required' => $element['#required'],
- '#field_name' => $element['#field_name'],
- '#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];
+ $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);
+ $element[$field_key]['#text_format'] = $format;
}
return $element;
@@ -400,33 +646,95 @@ function text_textarea_elements_process($element, $form_state, $form) {
$instance = $form['#fields'][$element['#field_name']]['instance'];
$field_key = $element['#columns'][0];
$delta = $element['#delta'];
+
$element[$field_key] = array(
'#type' => 'textarea',
'#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
'#rows' => $instance['widget']['settings']['rows'],
'#weight' => 0,
- // The following values were set by the field module and need
- // to be passed down to the nested element.
'#title' => $element['#title'],
'#description' => $element['#description'],
'#required' => $element['#required'],
- '#field_name' => $element['#field_name'],
- '#bundle' => $element['#bundle'],
- '#delta' => $element['#delta'],
- '#columns' => $element['#columns'],
);
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);
+ $element[$field_key]['#text_format'] = $format;
+ }
+
+ 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, $form_state, $form) {
+ $field = $form['#fields'][$element['#field_name']]['field'];
+ $instance = $form['#fields'][$element['#field_name']]['instance'];
+ $delta = $element['#delta'];
+
+ $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' => $instance['widget']['settings']['summary_rows'],
+ '#weight' => 0,
+ '#title' => t('Summary'),
+ '#description' => t('Leave blank to use trimmed value of full text as the summary.'),
+ '#required' => $element['#required'],
+ '#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,
+ '#title' => $display ? t('Full text') : $element['#title'],
+ '#description' => $element['#description'],
+ '#required' => $element['#required'],
+ '#required' => $instance['required'],
+ );
+
+ 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;
+ $element[$field_key]['#text_format'] = $format;
}
return $element;
}
/**
+ * Helper function to determine the value for a formatted text widget.
+ *
+ * '#text_format' puts the format in '[column 0]_format' in incoming values,
+ * while we need it in '[column 1]'.
+ */
+function text_field_widget_formatted_text_value($form, $edit = FALSE) {
+ if ($edit !== FALSE) {
+ $field_key = $form['#columns'][0];
+ $filter_key = (count($form['#columns']) == 2) ? $form['#columns'][1] : 'format';
+ $default_key = $field_key . '_format';
+ // The format selector uses #access = FALSE if only one format is
+ // available. In this case, we don't receive its value, and need to
+ // manually set it.
+ $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_resolve_format(FILTER_FORMAT_DEFAULT);
+ unset($edit[$default_key]);
+ return $edit;
+ }
+}
+
+/**
* FAPI theme for an individual text elements.
*
* The textfield or textarea is already rendered by the
@@ -444,3 +752,21 @@ function theme_text_textfield($element) {
function theme_text_textarea($element) {
return $element['#children'];
}
+
+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'),
+ '#id' => str_replace('_', '-', $element['#field_name']) . '-summary-wrapper',
+ );
+ return theme('fieldset', $fieldset);
+ }
+ else {
+ return $element['#children'];
+ }
+}
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index cb240b6c9..3c2d706b9 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -13,7 +13,7 @@ class TextFieldTestCase extends DrupalWebTestCase {
}
function setUp() {
- parent::setUp('field', 'text', 'field_test');
+ parent::setUp('field_test');
$web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
$this->drupalLogin($web_user);
@@ -149,7 +149,7 @@ class TextFieldTestCase extends DrupalWebTestCase {
// selector is displayed
$this->drupalGet('test-entity/add/test-bundle');
$this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed'));
- $this->assertNoFieldByName($this->field_name . '[0][format]', '1', t('Format selector is not displayed'));
+ $this->assertNoFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is not displayed'));
// Submit with data that should be filtered.
$value = $this->randomName() . '<br />' . $this->randomName();
@@ -175,11 +175,11 @@ class TextFieldTestCase extends DrupalWebTestCase {
// We should now have a 'text format' selector.
$this->drupalGet('test-entity/' . $id . '/edit');
$this->assertFieldByName($this->field_name . '[0][value]', '', t('Widget is displayed'));
- $this->assertFieldByName($this->field_name . '[0][format]', '1', t('Format selector is displayed'));
+ $this->assertFieldByName($this->field_name . '[0][value_format]', '1', t('Format selector is displayed'));
// Edit and change the format to 'Full HTML'.
$edit = array(
- $this->field_name . '[0][format]' => 2,
+ $this->field_name . '[0][value_format]' => 2,
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated'));
@@ -196,3 +196,148 @@ class TextFieldTestCase extends DrupalWebTestCase {
*
*/
}
+
+class TextSummaryTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => t('Text summary'),
+ 'description' => t('Test text_summary() with different strings and lengths.'),
+ 'group' => t('Field'),
+ );
+ }
+
+ /**
+ * Tests an edge case where the first sentence is a question and
+ * subsequent sentences are not. This edge case is documented at
+ * http://drupal.org/node/180425.
+ */
+ function testFirstSentenceQuestion() {
+ $text = 'A question? A sentence. Another sentence.';
+ $expected = 'A question? A sentence.';
+ $this->callTextSummary($text, $expected, NULL, 30);
+ }
+
+ /**
+ * Test summary with long example.
+ */
+ function testLongSentence() {
+ $text = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' . // 125
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' . // 108
+ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ' . // 103
+ 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; // 110
+ $expected = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' .
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ' .
+ 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.';
+ // First three sentences add up to: 336, so add one for space and then 3 to get half-way into next word.
+ $this->callTextSummary($text, $expected, NULL, 340);
+ }
+
+ /**
+ * Test various summary length edge cases.
+ */
+ function testLength() {
+ // This string tests a number of edge cases.
+ $text = "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>";
+
+ // The summaries we expect text_summary() to return when $size is the index
+ // of each array item.
+ // Using no text format:
+ $expected = array(
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<",
+ "<p",
+ "<p>",
+ "<p>\n",
+ "<p>\nH",
+ "<p>\nHi",
+ "<p>\nHi\n",
+ "<p>\nHi\n<",
+ "<p>\nHi\n</",
+ "<p>\nHi\n</p",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ );
+
+ // And using a text format WITH the line-break and htmlcorrector filters.
+ $expected_lb = array(
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<",
+ "<p",
+ "<p></p>",
+ "<p></p>",
+ "<p></p>",
+ "<p></p>",
+ "<p>\nHi</p>",
+ "<p>\nHi</p>",
+ "<p>\nHi</p>",
+ "<p>\nHi</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ "<p>\nHi\n</p>\n<p>\nfolks\n<br />\n!\n</p>",
+ );
+
+ // Test text_summary() for different sizes.
+ for ($i = 0; $i <= 37; $i++) {
+ $this->callTextSummary($text, $expected[$i], NULL, $i);
+ $this->callTextSummary($text, $expected_lb[$i], 1, $i);
+ $this->callTextSummary($text, $expected_lb[$i], 2, $i);
+ }
+ }
+
+ /**
+ * Calls text_summary() and asserts that the expected teaser is returned.
+ */
+ function callTextSummary($text, $expected, $format = NULL, $size = NULL) {
+ $summary = text_summary($text, $format, $size);
+ $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected)));
+ }
+}
diff --git a/modules/field/theme/field.css b/modules/field/theme/field.css
index 17bf18915..93dcbcb08 100644
--- a/modules/field/theme/field.css
+++ b/modules/field/theme/field.css
@@ -32,7 +32,11 @@ form .field-add-more-submit {
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;
+}