Index: modules/node/node.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.admin.inc,v retrieving revision 1.74 diff -u -r1.74 node.admin.inc --- modules/node/node.admin.inc 27 Oct 2009 04:06:44 -0000 1.74 +++ modules/node/node.admin.inc 2 Nov 2009 14:39:42 -0000 @@ -70,8 +70,22 @@ */ function node_filters() { // Regular filters + $filters['titlesearch'] = array( + 'title' => t('title'), + 'type' => 'textfield', + ); + + // If the search module is enabled, use it to search the contents of nodes. + if (module_exists('search')) { + $filters['textsearch'] = array( + 'title' => t('data'), + 'type' => 'textfield', + ); + } + $filters['status'] = array( 'title' => t('status'), + 'type' => 'select', 'options' => array( 'status-1' => t('published'), 'status-0' => t('not published'), @@ -89,16 +103,16 @@ ); } - $filters['type'] = array('title' => t('type'), 'options' => node_type_get_names()); + $filters['type'] = array('title' => t('type'), 'type' => 'select', 'options' => node_type_get_names()); // The taxonomy filter if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) { - $filters['term'] = array('title' => t('term'), 'options' => $taxonomy); + $filters['term'] = array('title' => t('term'), 'type' => 'select', 'options' => $taxonomy); } // Language filter if there is a list of languages if ($languages = module_invoke('locale', 'language_list')) { $languages = array('' => t('Language neutral')) + $languages; - $filters['language'] = array('title' => t('language'), 'options' => $languages); + $filters['language'] = array('title' => t('language'), 'type' => 'select', 'options' => $languages); } return $filters; } @@ -116,6 +130,33 @@ foreach ($filter_data as $index => $filter) { list($key, $value) = $filter; switch ($key) { + case 'titlesearch': + $query->condition('n.title', '%' . $value .'%', 'LIKE'); + break; + case 'textsearch': + // Avoid errors when there is session data but the module is disabled. + if (!module_exists('search')) { + break; + } + + // First get a list of nids that match the description. + $result = node_search_execute($value); + $nids = array(); + if (is_array($result)) { + foreach ($result as $node_result) { + $nids[] = $node_result['node']->nid; + } + } + + if (!empty($nids)) { + // Now restrict to just that list of nids. + $query->condition('nid', $nids); + } + else { + // No nodes match, force a return of nothing without an error. + $query->condition('1', '0'); + } + break; case 'term': $index = 'tn' . $counter++; $query->join('taxonomy_term_node', $index, "n.nid = $index.nid"); @@ -155,7 +196,7 @@ elseif ($type == 'language') { $value = empty($value) ? t('Language neutral') : module_invoke('locale', 'language_name', $value); } - else { + elseif ($filters[$type]['type'] == 'select') { $value = $filters[$type]['options'][$value]; } if ($i++) { @@ -164,18 +205,21 @@ else { $form['filters']['current'][] = array('#markup' => t('%type is %value', array('%type' => $filters[$type]['title'], '%value' => $value))); } - if (in_array($type, array('type', 'language'))) { - // Remove the option if it is already being filtered on. + if (in_array($type, array('type', 'language')) || $filters[$type]['type'] == 'textfield') { + // Remove the option if it is already being filtered on and can't have multiple options. unset($filters[$type]); } } foreach ($filters as $key => $filter) { $names[$key] = $filter['title']; - $form['filters']['status'][$key] = array('#type' => 'select', '#options' => $filter['options']); + $form['filters']['status'][$key] = array('#type' => $filter['type']); + if (isset($filter['options'])) { + $form['filters']['status'][$key]['#options'] = $filter['options']; + } } - $form['filters']['filter'] = array('#type' => 'radios', '#options' => $names, '#default_value' => 'status'); + $form['filters']['filter'] = array('#type' => 'checkboxes', '#options' => $names); $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => (count($session) ? t('Refine') : t('Filter'))); if (count($session)) { $form['filters']['buttons']['undo'] = array('#type' => 'submit', '#value' => t('Undo')); @@ -247,14 +291,26 @@ switch ($form_state['values']['op']) { case t('Filter'): case t('Refine'): - if (isset($form_state['values']['filter'])) { - $filter = $form_state['values']['filter']; - - // Flatten the options array to accommodate hierarchical/nested options. - $flat_options = form_options_flatten($filters[$filter]['options']); + foreach ($form_state['values']['filter'] as $filter) { + // Filter may not have been checked, but will still be in the array. + if (empty($filter)) { + continue; + } - if (isset($flat_options[$form_state['values'][$filter]])) { - $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]); + if ($filters[$filter]['type'] == 'select' && is_array($filters[$filter]['options'])) { + // Flatten the options array to accommodate hierarchical/nested options. + $flat_options = form_options_flatten($filters[$filter]['options']); + + if (isset($flat_options[$form_state['values'][$filter]])) { + $_SESSION['node_overview_filter'][] = array($filter, $form_state['values'][$filter]); + } + } + else { + // Text fields. + $value = $form_state['values'][$filter]; + if (!empty($value)) { + $_SESSION['node_overview_filter'][] = array($filter, $value); + } } } break; Index: modules/node/node.test =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.test,v retrieving revision 1.51 diff -u -r1.51 node.test --- modules/node/node.test 30 Oct 2009 22:33:35 -0000 1.51 +++ modules/node/node.test 2 Nov 2009 14:39:42 -0000 @@ -704,7 +704,8 @@ // Enable dummy module that implements hook_node_grants(), // hook_node_access_records(), hook_node_grants_alter() and // hook_node_access_records_alter(). - parent::setUp('node_test'); + // Search module is an optional dependency, used in the node search form. + parent::setUp('node_test', 'search'); } /** @@ -973,6 +974,8 @@ $node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => 1)); $node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => 0)); $node3 = $this->drupalCreateNode(array('type' => 'page')); + // Additional node for title search tests. + $node4 = $this->drupalCreateNode(array('type' => 'article', 'status' => 1)); $this->drupalGet('admin/content'); $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Node appears on the node administration listing.')); @@ -982,24 +985,78 @@ // Filter the node listing by status. $edit = array( - 'filter' => 'status', + 'filter[status]' => 'status', 'status' => 'status-1', ); $this->drupalPost('admin/content', $edit, t('Filter')); $this->assertRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.')); - $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Published node appears on the node administration listing.')); + // For result assertRaw, must appear in the node list as a link rather than in the filter. + $this->assertRaw($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Published node appears on the node administration listing.')); + // NoText = shouldn't appear at all. $this->assertNoText($node2->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished node does not appear on the node administration listing.')); + $this->verbose($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . ''); + // Filter the node listing by content type. $edit = array( - 'filter' => 'type', + 'filter[type]' => 'type', 'type' => 'article', ); $this->drupalPost('admin/content', $edit, t('Refine')); $this->assertRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.')); $this->assertRaw(t('%type is %value', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is filtered by content type.')); - $this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Article node appears on the node administration listing.')); + $this->assertRaw($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Article node appears on the node administration listing.')); + $this->assertNoText($node3->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page node does not appear on the node administration listing.')); + + // Filter the node listing by title. + $edit = array( + 'filter[titlesearch]' => 'titlesearch', + 'titlesearch' => $node1->title[FIELD_LANGUAGE_NONE][0]['value'], + ); + $this->drupalPost('admin/content', $edit, t('Refine')); + $this->assertRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.')); + $this->assertRaw(t('%type is %value', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is filtered by content type.')); + $this->assertRaw(t('%type is %value', array('%type' => t('title'), '%value' => $node1->title[FIELD_LANGUAGE_NONE][0]['value'])), t('The node administration listing is filtered by node title.')); + $this->assertRaw($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Article node appears on the node administration listing.')); + $this->assertNoText($node3->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page node does not appear on the node administration listing.')); + $this->assertNoText($node4->title[FIELD_LANGUAGE_NONE][0]['value'], t('Non-matching article title does not appear on the node administration listing.')); + + // Test the Undo button, which removes the most recently added filter. + $edit = array(); + $this->drupalPost('admin/content', $edit, t('Undo')); + $this->assertRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.')); + $this->assertRaw(t('%type is %value', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is filtered by content type.')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('title'), '%value' => $node1->title[FIELD_LANGUAGE_NONE][0]['value'])), t('The node administration listing is no longer filtered by title.')); + $this->assertRaw($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Article node appears on the node administration listing.')); $this->assertNoText($node3->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page node does not appear on the node administration listing.')); + $this->assertRaw($node4->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Non-matching article title now appears again on the node administration listing.')); + + // Test the Reset button, which removes all remaining filters. + $edit = array(); + $this->drupalPost('admin/content', $edit, t('Reset')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is no longer filtered by status.')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is no longer filtered by content type.')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('title'), '%value' => $node1->title[FIELD_LANGUAGE_NONE][0]['value'])), t('The node administration listing is no longer filtered by title.')); + $this->assertRaw($node1->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Article node appears on the node administration listing.')); + $this->assertRaw($node3->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Page node appears on the node administration listing.')); + $this->assertRaw($node4->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Non-matching article title now appears again on the node administration listing.')); + + // Filter the node listing by search data. Must trigger search indexing first. + /* TODO: The textsearch field needs search index data in place for it to work. + $edit = array( + 'filter[textsearch]' => 'textsearch', + 'textsearch' => $node2->title[FIELD_LANGUAGE_NONE][0]['value'], + ); + $this->drupalPost('admin/content', $edit, t('Filter')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is no longer filtered by status.')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is no longer filtered by content type.')); + $this->assertNoRaw(t('%type is %value', array('%type' => t('title'), '%value' => $node1->title[FIELD_LANGUAGE_NONE][0]['value'])), t('The node administration listing is no longer filtered by title.')); + $this->assertRaw(t('%type is %value', array('%type' => t('data'), '%value' => $node2->title[FIELD_LANGUAGE_NONE][0]['value'])), t('The node administration listing filtered by search data.')); + $this->assertRaw($node2->title[FIELD_LANGUAGE_NONE][0]['value'] . '', t('Node matching search appears on the node administration listing.')); + $this->assertNoText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Non-matching article title does not appear on the node administration listing.')); + */ + + // TODO: Also test language and taxonomy filters. } } Index: modules/system/system.css =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.css,v retrieving revision 1.63 diff -u -r1.63 system.css --- modules/system/system.css 21 Sep 2009 08:52:41 -0000 1.63 +++ modules/system/system.css 2 Nov 2009 14:39:42 -0000 @@ -193,7 +193,7 @@ padding-bottom: 0; font-size: 0.9em; } -dl.multiselect dd.b, dl.multiselect dd.b .form-item, dl.multiselect dd.b select { +dl.multiselect dd.b, dl.multiselect dd.b .form-item, dl.multiselect dd.b select, dl.multiselect dd.b input.form-text { font-family: inherit; font-size: inherit; width: 14em; Index: misc/form.js =================================================================== RCS file: /cvs/drupal/drupal/misc/form.js,v retrieving revision 1.12 diff -u -r1.12 form.js --- misc/form.js 16 Oct 2009 16:37:00 -0000 1.12 +++ misc/form.js 2 Nov 2009 14:39:41 -0000 @@ -61,9 +61,9 @@ Drupal.behaviors.multiselectSelector = { attach: function (context, settings) { // Automatically selects the right radio button in a multiselect control. - $('.multiselect select', context).once('multiselect').change(function () { - $('.multiselect input:radio[value="' + this.id.substr(5) + '"]') - .attr('checked', true); + $('.multiselect select, .multiselect input:text', context).once('multiselect').change(function () { + $('.multiselect input:radio[value="' + this.name + '"], .multiselect input:checkbox[value="' + this.name + '"]') + .attr('checked', true); }); } };