array( 'title' => t('Create and view journal entries'), ), ); } /** * Implements hook_menu(). */ function journal_menu() { $items['admin/reports/journal'] = array( 'title' => 'Journal entries', 'description' => 'View journal entries.', 'page callback' => 'journal_view', 'access arguments' => array('access journal'), 'file' => 'journal.admin.inc', ); $items['admin/reports/journal/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/reports/journal/patches'] = array( 'title' => 'Patches', 'description' => 'View list of applied patches and hacks on this Drupal site.', 'page callback' => 'journal_patch_view', 'access arguments' => array('access journal'), 'file' => 'journal.admin.inc', 'type' => MENU_LOCAL_TASK, ); $items['admin/reports/journal/patches/edit'] = array( 'title' => 'Edit patch', 'page callback' => 'drupal_get_form', 'page arguments' => array('journal_patch_form'), 'access arguments' => array('access journal'), 'file' => 'journal.admin.inc', 'type' => MENU_CALLBACK, ); $items['admin/reports/journal/patches/delete'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('journal_patch_delete_confirm'), 'access arguments' => array('access journal'), 'file' => 'journal.admin.inc', 'type' => MENU_CALLBACK, ); 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 * (0) or force/require a journal entry for it (1). * * @see journal_form_ids_default() */ function journal_form_alter(&$form, &$form_state, $form_id) { if (!user_access('access journal')) { return; } $entry_required = FALSE; if (!isset($form['#submit'])) { $form['#submit'] = array(); } // Check whether form has to/must not be extended. $journal_ids = array_merge(journal_form_ids_default(), variable_get('journal_form_ids', array())); if (isset($journal_ids[$form_id]) || in_array('system_settings_form_submit', $form['#submit'])) { if (isset($journal_ids[$form_id]) && !$journal_ids[$form_id]) { // No journal entry for 'form_id' => 0. return; } else { // Require journal entry for 'form_id' => 1 or system settings forms. $entry_required = TRUE; } } // Prepend journal widget to form actions. if (isset($form['actions']['#weight'])) { $journal_weight = $form['actions']['#weight'] - 1; } else { // #type 'actions' uses a default #weight of 100. $journal_weight = 99; } // 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( '#weight' => $journal_weight, '#tree' => FALSE, ); // Store the path on which this form was initially displayed. // We need to store this in a hidden field, since forms with custom '#action's // (like admin/build/modules) will reset our value to $_GET['q'] otherwise. $form['journal']['journal_location'] = array( '#type' => 'hidden', '#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'), '#description' => t('If not empty, contents of this field will be logged to the system journal.'), '#required' => $entry_required, '#rows' => 2, '#wysiwyg' => FALSE, ); if ($entry_required && user_access('access devel information')) { $form['journal']['journal_entry']['#required'] = FALSE; $form['journal']['journal_omit'] = array( '#type' => 'checkbox', '#title' => t('Skip journal entry'), '#return_value' => 1, '#default_value' => 0, '#description' => t('The journal entry for this form is required. If enabled, the form can be submitted without a journal entry.'), ); $form['#validate'][] = 'journal_form_validate'; } } /** * Validate optional journal entry for privileged users. */ function journal_form_validate($form, &$form_state) { if (empty($form_state['values']['journal_omit']) && empty($form_state['values']['journal_entry'])) { form_set_error('journal', t('!name field is required.', array('!name' => t('Journal entry')))); } } /** * Save a new journal entry and clean out form values. */ 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']); } unset($form_state['values']['journal_entry'], $form_state['values']['journal_location']); } /** * Indicate if a form must not extended. * * @param string $form_id * A form_id to check against. * * @return bool * True if form should be skipped, false if form can be extended. * * @todo Introduce a new FAPI attribute #journal = TRUE to require a journal * entry if Journal module is enabled - OR - introduce a new hook_journal? */ function journal_form_ids_default() { return array( 'comment_form' => 0, 'dblog_filter_form' => 0, 'devel_admin_settings' => 0, 'devel_execute_form' => 0, 'devel_execute_block_form' => 0, 'devel_switch_user_form' => 0, 'img_assist_header_form' => 0, 'img_assist_properties_form' => 0, 'img_assist_insert_html_form' => 0, 'journal_patch_form' => 0, 'l10n_client_form' => 0, 'l10n_client_search_form' => 0, 'node_filter_form' => 0, 'page_manager_handler_summary' => 0, 'page_manager_list_pages_form' => 0, 'page_manager_page_summary' => 0, 'page_manager_save_page_form' => 0, 'poll_view_voting' => 0, 'search_block_form' => 0, 'search_box' => 0, 'search_form' => 0, 'system_modules' => 1, 'user_admin_permissions' => 1, 'user_filter_form' => 0, 'user_login_block' => 0, 'views_exposed_form' => 0, 'views_filters' => 0, 'views_ui_add_display_form' => 0, 'views_ui_add_item_form' => 0, 'views_ui_analyze_view_button' => 0, 'views_ui_config_item_form' => 0, 'views_ui_config_type_form' => 0, 'views_ui_edit_details_form' => 0, 'views_ui_edit_display_form' => 0, 'views_ui_export_page' => 0, 'views_ui_list_views_form' => 0, 'views_ui_preview_form' => 0, 'views_ui_rearrange_form' => 0, 'views_ui_remove_display_form' => 0, ); } /** * Implements hook_user_cancel(). */ function journal_user_cancel($edit, $account, $method) { switch ($method) { case 'user_cancel_reassign': db_update('journal') ->fields(array('uid' => 0)) ->condition('uid', $account->uid) ->execute(); break; } } /** * Implements hook_user_delete(). */ function journal_user_delete($account) { db_delete('journal') ->condition('uid', $account->uid) ->execute(); } /** * Implements hook_block_info(). */ function journal_block_info() { $blocks['backlog'] = array( 'info' => t('Journal entries'), 'weight' => -10, 'enabled' => 1, 'region' => 'sidebar_second', ); return $blocks; } /** * Implements hook_block_view(). */ function journal_block_view($delta = '') { $block = array(); switch ($delta) { case 'backlog': if (user_access('access journal')) { $result = db_query("SELECT j.*, u.name FROM {journal} j INNER JOIN {users} u ON j.uid = u.uid WHERE j.location = :location ORDER BY j.timestamp DESC", array( ':location' => $_GET['q'], ))->fetchAllAssoc('jid'); if ($output = journal_output($result, 'list')) { drupal_add_css(drupal_get_path('module', 'journal') . '/journal.css'); $block = array( 'subject' => t('Journal entries'), 'content' => $output, ); } } break; } return $block; } /** * Render journal entries. * * @param $journal * A list of journal entry objects to output. * @param $format * (optional) The format to output log entries in; one of 'table', 'list' or * 'text'. * @param $header * (optional) An array containing header data for $format 'table'. * * @todo Add XML output format. */ function journal_output(array $journal, $format = 'table', array $header = array()) { switch ($format) { case 'text': // Output delimiter in first line, since this may change. $output = '\t' . "\n"; foreach ($journal as $entry) { $row = array( $entry->timestamp, $entry->uid, $entry->message, $entry->location, ); $output .= implode("\t", $row) . "\n"; } break; case 'list': $output = ''; foreach ($journal as $entry) { $output .= '
  • '; $output .= '' . format_username($entry) . ' ' . format_date($entry->timestamp, 'small') . ':'; $output .= '' . filter_xss_admin($entry->message) . ''; $output .= '
  • '; } if ($output) { $output = ''; } break; case 'table': default: $rows = array(); foreach ($journal as $entry) { $rows[] = array( format_date($entry->timestamp, 'small'), format_username($entry), filter_xss_admin($entry->message), l(truncate_utf8($entry->location, 32, FALSE, TRUE), $entry->location), ); } $output['journal'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, '#empty' => t('No journal entries available.'), ); $output['pager'] = array( '#theme' => 'pager', ); break; } return $output; } /** * Store a new journal entry in the database. * * @param string $description * A journal entry text entered by an user. * @param string $location * The path on which the journal entry has been entered. * * @todo Implement proper CRUD API. */ function journal_add_entry($description, $location) { global $user; db_insert('journal') ->fields(array( 'uid' => $user->uid, 'message' => $description, 'location' => $location, 'timestamp' => REQUEST_TIME, )) ->execute(); }