Index: journal.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/journal/journal.install,v retrieving revision 1.8 diff -u -p -r1.8 journal.install --- journal.install 21 Jun 2009 00:02:13 -0000 1.8 +++ journal.install 23 Jun 2009 05:17:38 -0000 @@ -28,6 +28,9 @@ function journal_schema() { 'timestamp' => array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => t('The UNIX timestamp when the journal entry was created.'), ), + 'tag' => array('type' => 'varchar', 'length' => 255, 'default' => '', + 'description' => t('A tag used to group/sort journal entries.'), + ), ), 'primary key' => array('jid'), 'indexes' => array( @@ -135,3 +138,14 @@ function journal_update_6103() { return $ret; } +/** + * Add a tag column to the journal table. + */ +function journal_update_6104() { + $ret = array(); + db_add_field($ret, 'journal', 'tag', array( + 'type' => 'varchar', 'length' => 255, 'default' => '', + 'description' => t('A tag used to group/sort journal entries.'))); + return $ret; +} + Index: journal.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/journal/journal.module,v retrieving revision 1.20 diff -u -p -r1.20 journal.module --- journal.module 21 Jun 2009 00:02:13 -0000 1.20 +++ journal.module 23 Jun 2009 05:17:38 -0000 @@ -4,14 +4,14 @@ /** * @file * Allows to maintain a custom log of performed setup and configuration actions. - * + * * Journal module adds additional fields to all forms in a Drupal site. * Only users granted the 'access journal' permission are able to add entries * to the journal. * * Journal form fields may be disabled for certain forms, for example forms that * are displayed in blocks. - * + * * @todo Describe Demo module implementation. * @todo Are we able to track enabling/disabling of modules automatically? * @todo Allow to include all form field values in a journal entry. @@ -51,9 +51,9 @@ function journal_menu() { 'weight' => -10, ); $items['admin/reports/journal/patches'] = array( + 'page callback' => 'journal_patch_view', 'title' => 'Patches', 'description' => 'View list of applied patches and hacks on this Drupal site.', - 'page callback' => 'journal_patch_view', 'access arguments' => array('access journal'), 'type' => MENU_LOCAL_TASK, ); @@ -70,12 +70,17 @@ function journal_menu() { 'access arguments' => array('access journal'), 'type' => MENU_CALLBACK, ); + $items['admin/journal/ajax/autocomplete/tag'] = array( + 'access arguments' => array('access journal'), + 'type' => MENU_CALLBACK, + 'page callback' => 'journal_autocomplete_tag', + ); return $items; } /** * Add Journal fields to all forms. - * + * * Any form, except user-defined form_ids, will be extended by a fieldset * to enter a journal entry. All journal form ids are stored in one variable * array; having form_ids as keys and a boolean value whether to skip a form id @@ -114,13 +119,14 @@ function journal_form_alter(&$form, &$fo else { $journal_weight = 100; } - + // Prepend our journal submit handler, so we can eliminate the form value of // journal_entry, which would be saved as a variable in system_settings_form() // otherwise. array_unshift($form['#submit'], 'journal_form_submit'); $form['journal'] = array( + '#title' => t('Journal'), '#weight' => $journal_weight, '#tree' => FALSE, ); @@ -133,7 +139,6 @@ function journal_form_alter(&$form, &$fo '#value' => (!empty($_REQUEST['journal_location']) ? $_REQUEST['journal_location'] : $_GET['q']), ); - // Add journal entry field. $form['journal']['journal_entry'] = array( '#type' => 'textarea', '#title' => t('Journal entry'), @@ -141,6 +146,14 @@ function journal_form_alter(&$form, &$fo '#required' => $entry_required, '#wysiwyg' => FALSE, ); + // Add journal entry field. + $form['journal']['journal_tag'] = array( + '#type' => 'textfield', + '#title' => t('Journal tag'), + '#description' => t('Optionally tag this journal entry; only used to filter entries in the journal.', array('@journal-url' => url('admin/reports/journal'))), + '#autocomplete_path' => 'admin/journal/ajax/autocomplete/tag', + ); + if ($entry_required && user_access('access devel information')) { $form['journal']['journal_entry']['#required'] = FALSE; $form['journal']['journal_omit'] = array( @@ -168,14 +181,14 @@ function journal_form_validate($form, &$ */ function journal_form_submit($form, &$form_state) { if (!empty($form_state['values']['journal_entry'])) { - journal_add_entry($form_state['values']['journal_entry'], $form_state['values']['journal_location']); + journal_add_entry($form_state['values']['journal_entry'], $form_state['values']['journal_location'], $form_state['values']['journal_tag']); } - unset($form_state['values']['journal_entry'], $form_state['values']['journal_location']); + unset($form_state['values']['journal_entry'], $form_state['values']['journal_location'], $form_state['values']['journal_tag']); } /** * Indicate if a form must not extended. - * + * * @param string $form_id * A form_id to check against. * @@ -263,31 +276,38 @@ function journal_block($op = 'list', $de /** * Output a sortable table containing all journal entries. */ -function journal_view() { +function journal_view($tag = '') { $sql = "SELECT j.*, u.name FROM {journal} j INNER JOIN {users} u ON j.uid = u.uid"; - + $header = array( array('data' => t('Date'), 'field' => 'j.timestamp', 'sort' => 'desc'), array('data' => t('User'), 'field' => 'u.name'), t('Message'), t('Location'), + t('Tag'), ); $tablesort = tablesort_sql($header); - $result = pager_query($sql . $tablesort, 50); + + if (!empty($tag)) { + $sql .= " WHERE tag LIKE '%s'"; + $result = pager_query($sql . $tablesort, 50, 0, NULL, $tag); + } else { + $result = pager_query($sql . $tablesort, 50); + } return journal_output($result, 'table', $header); } /** * Render journal entries. - * + * * Use this function to render and return * - a journal provided as a database query result resource or * - a custom journal provided as an array containing journal entry objects. * * This function may look insane to some, but it ensures that implementation of * journal module into other modules is as easy as possible. - * + * * @param array $journal * A database query result resource or an array containing journal entry * objects to output. @@ -310,11 +330,12 @@ function journal_output($journal, $forma $entry->uid, $entry->message, $entry->location, + $entry->tag, ); $output .= implode("\t", $row) ."\n"; } break; - + case 'list': $output = ''; while ($entry = (is_array($journal) ? array_shift($journal) : db_fetch_object($journal))) { @@ -337,6 +358,7 @@ function journal_output($journal, $forma theme('username', $entry), filter_xss_admin($entry->message), l(truncate_utf8($entry->location, 32, FALSE, TRUE), $entry->location), + l($entry->tag, 'admin/reports/journal/' . $entry->tag), ); } @@ -354,7 +376,7 @@ function journal_output($journal, $forma /** * Convert a journal into an array. - * + * * @param mixed $data * Journal data. * @param string $type @@ -365,22 +387,22 @@ function journal_output($journal, $forma */ function journal_convert($data, $type = 'text') { $journal = array(); - + switch ($type) { case 'text': default: $data = explode('\n', $data); - + // Determine delimiter string. $delimiter = array_shift($data); - + while ($row = array_shift($data)) { $row = explode($delimiter, $row); $journal[] = (object)$row; } break; } - + return $journal; } @@ -392,10 +414,10 @@ function journal_convert($data, $type = * @param string $location * The path on which the journal entry has been entered. */ -function journal_add_entry($description, $location) { +function journal_add_entry($description, $location, $tag = '') { global $user; - - db_query("INSERT INTO {journal} (uid, message, location, timestamp) VALUES (%d, '%s', '%s', %d)", $user->uid, $description, $location, time()); + + db_query("INSERT INTO {journal} (uid, message, location, tag, timestamp) VALUES (%d, '%s', '%s', '%s', %d)", $user->uid, $description, $location, $tag, time()); } /** @@ -583,3 +605,18 @@ function journal_patch_delete_confirm_su $form_state['redirect'] = 'admin/reports/journal/patches'; } +/** + * Page callback for journal tag autocomplete. + */ +function journal_autocomplete_tag($string = '') { + $matches = array(); + if ($string) { + $result = db_query_range("SELECT DISTINCT tag FROM {journal} WHERE LOWER(tag) LIKE LOWER('%%%s%%')", array($string), 0, 10); + while ($journal = db_fetch_object($result)) { + $matches[$journal->tag] = check_plain($journal->tag); + } + } + + drupal_json($matches); +} + Index: tests/journal.test =================================================================== RCS file: tests/journal.test diff -N tests/journal.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/journal.test 23 Jun 2009 05:17:38 -0000 @@ -0,0 +1,105 @@ + t('Journal module functionality'), + 'description' => t('Test the Journal module functionality.'), + 'group' => t('Journal'), + ); + } + + function setUp() { + parent::setUp('journal'); + + $user = $this->drupalCreateUser(array('administer blocks', 'access journal')); + $this->drupalLogin($user); + } + + /** + * Test a basic journal entry. + */ + function testJournalAddEntry() { + // Add a new block. + $title = $this->randomName(8); + $description = $this->randomName(8); + $journal_entry = $this->randomName(8); + $edit = array( + 'info' => $description, + 'title' => $title, + 'body' => $this->randomName(8), + 'pages' => '', + 'journal_entry' => $journal_entry, + ); + $this->drupalPost('admin/build/block/add', $edit, 'Save block'); + + // Make sure the journal entry we added above is in the journal report. + $this->drupalGet('admin/reports/journal'); + $this->assertText($journal_entry, t('Verify that the journal entry is listed.')); + $this->assertText('admin/build/block/add', t('Verify that the journal entry location is listed.')); + } + + /** + * Test a journal entry where #tree = TRUE. + * + * The admin/build/block page is used to test this case because the + * form there is nested. We do not need to position a block to make a + * a journal entry. + */ + function testJournalAddEntryWhereTreeTrue() { + $journal_entry = $this->randomName(8); + $edit = array( + 'journal_entry' => $journal_entry, + ); + $this->drupalPost('admin/build/block', $edit, 'Save blocks'); + + // Verify the entry is in the journal report. + $this->drupalGet('admin/reports/journal'); + $this->assertText($journal_entry, t('Verify that the journal entry is listed.')); + $this->assertText('admin/build/block', t('Verify that the journal entry location is listed.')); + } + + /** + * Test adding and filtering by tags. + * + * Testing adding tags and filtering by tags is included in the same unit + * test so that the added tags can be later used as filters. + */ + function testJournalTag() { + $journals = array(); + + // Add a few journal entries. + for ($i = 0; $i < 10; $i++) { + $journal_entry = $this->randomName(8); + $journal_tag = $this->randomName(8); + $edit = array ( + 'journal_entry' => $journal_entry, + 'journal_tag' => $journal_tag, + ); + $this->drupalPost('admin/build/block', $edit, 'Save blocks'); + $journals[$journal_entry] = $journal_tag; + } + + // Verify entries and the tags exist in the view. + foreach ($journals as $entry => $tag) { + $this->drupalGet('admin/reports/journal'); + $this->assertText($entry, t('Verify that the journal entry is listed.')); + + // Verify that the location is listed. + $this->assertText('admin/build/block', t('Verify that the location is listed.')); + + // Verify that we can filter by tag. + $this->drupalGet('admin/reports/journal/'. $tag); + $this->assertText($tag, t('Verify that the journal entry is listed when filtered by tag.')); + + $this->drupalGet('admin/reports/journal/list/'. $tag); + $this->assertText($tag, t('Verify that the journal entry is listed when filtered by tag.')); + } + } +} +