You are here

feedback.module in Feedback 5.2

Allows site visitors and users to report issues about this site.

File

feedback.module
View source
<?php

/**
 * @file
 * Allows site visitors and users to report issues about this site.
 */

/**
 * Implementation of hook_perm().
 */
function feedback_perm() {
  return array(
    'access feedback form',
    'view feedback messages',
  );
}

/**
 * Implementation of hook_menu().
 */
function feedback_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/logs/feedback',
      'title' => t('Feedback messages'),
      'description' => t('View feedback messages.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'feedback_admin_view_form',
      'access' => user_access('view feedback messages'),
    );
  }
  else {
    if (user_access('access feedback form')) {
      drupal_add_css(drupal_get_path('module', 'feedback') . '/feedback.css');
      drupal_add_js(drupal_get_path('module', 'feedback') . '/feedback.js');
    }
  }
  return $items;
}

/**
 * Implementation of hook_block().
 */
function feedback_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $blocks['form'] = array(
      'info' => t('Feedback form'),
    );
    return $blocks;
  }
  else {
    if ($op == 'view') {
      $block = array();
      switch ($delta) {
        case 'form':
          if (!user_access('access feedback form') || $_GET['q'] == 'admin/logs/feedback') {
            break;
          }
          $block['subject'] = '<span class="feedback-link">' . t('Feedback') . '</span>';
          $block['content'] = drupal_get_form('feedback_form');
          break;
      }
      return $block;
    }
  }
}

/**
 * Implementation of hook_footer().
 */
function feedback_footer($main = 0) {
  if (user_access('access feedback form') && $_GET['q'] != 'admin/logs/feedback') {
    $block = (object) module_invoke('feedback', 'block', 'view', 'form');
    $block->module = 'feedback';
    $block->delta = 'form';
    $block->region = 'footer';
    return theme('block', $block);
  }
}

/**
 * Form builder function for a user feedback form.
 */
function feedback_form() {
  $form = array();
  $form['#attributes']['class'] = 'feedback-form';

  // Store the path on which this form is displayed.
  $form['location'] = array(
    '#type' => 'value',
    '#value' => $_GET['q'],
  );

  // Allow the form to be submitted via AJAX.
  $form['ajax'] = array(
    '#type' => 'hidden',
    '#default_value' => 0,
  );
  $form['help'] = array(
    '#prefix' => '<div class="feedback-help">',
    '#value' => t('If you experience a bug or would like to see an addition on the current page, feel free to leave us a message.'),
    '#suffix' => '</div>',
  );
  if (user_access('view feedback messages')) {
    if (arg(0) != 'node') {
      $feedbacks = feedback_load(array(
        'status' => 0,
        'location_masked' => feedback_mask_path($_GET['q']),
      ));
    }
    else {
      $feedbacks = feedback_load(array(
        'status' => 0,
        'location' => $_GET['q'],
      ));
    }
    if ($feedbacks) {
      $rows = '';
      foreach ($feedbacks as $feedback) {
        $rows .= '<div class="feedback-submitted">' . theme('username', $feedback) . ' ' . format_date($feedback->timestamp, 'small') . ':</div>';
        $rows .= '<div class="feedback-body">' . feedback_format_message($feedback) . '</div>';
      }
      $form['messages'] = array(
        '#prefix' => '<div class="feedback-messages">',
        '#value' => $rows,
        '#suffix' => '</div>',
      );
    }
  }
  $form['message'] = array(
    '#type' => 'textarea',
    '#attributes' => array(
      'class' => 'feedback-message',
    ),
    '#cols' => 20,
    '#title' => t('Message'),
    '#required' => TRUE,
    '#wysiwyg' => FALSE,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Send'),
    '#id' => 'feedback-submit',
    '#prefix' => '<div id="feedback-throbber">',
    '#suffix' => '</div>',
  );
  return $form;
}
function feedback_form_submit($form_id, $form_values) {
  feedback_add_entry($form_values['message'], $form_values['location']);
  $message = t('Thanks for your feedback!');
  if ($form_values['ajax']) {
    echo drupal_to_js(array(
      'message' => $message,
    ));
    exit;
  }
  else {
    drupal_set_message($message);
  }
}

/**
 * Format a feedback entry.
 *
 * @param $entry
 *   A feedback object.
 */
function feedback_format_message($entry) {
  $message = check_plain($entry->message);
  if (!empty($entry->useragent)) {
    if (module_exists('browscap')) {
      $browserinfo = browscap_get_browser($entry->useragent);
      $browser = $browserinfo['parent'] ? $browserinfo['parent'] . ' / ' . $browserinfo['platform'] : $browserinfo['useragent'];
      $message .= '<div class="browserinfo">(' . check_plain($browser) . ')</div>';
    }
    else {
      $message .= '<div class="browserinfo">(' . check_plain($entry->useragent) . ')</div>';
    }
  }
  return $message;
}

/**
 * Load feedback entries from the database.
 *
 * @param $array
 *   A keyed array of optional where clause conditions.
 */
function feedback_load($array) {
  $where = $args = array();
  if (!empty($array)) {
    foreach ($array as $column => $value) {
      $where[] = 'f.' . $column . ' = ' . (is_numeric($value) ? '%d' : "'%s'");
      $args[] = $value;
    }
  }
  $sql = "SELECT f.*, u.name FROM {feedback} f INNER JOIN {users} u ON f.uid = u.uid";
  if (!empty($where)) {
    $sql .= ' WHERE ' . implode(' AND ', $where);
  }
  $result = db_query($sql, $args);
  $entries = array();
  while ($entry = db_fetch_object($result)) {
    $entries[$entry->fid] = $entry;
  }
  return $entries;
}

/**
 * 'Mask' a path, i.e. replace all numeric arguments in a path with '%' placeholders.
 *
 * Please note that only numeric arguments with a preceding slash will be
 * replaced.
 *
 * @param $path
 *   An internal Drupal path, f.e. 'user/123/edit'.
 * @return
 *   A 'masked' path, for above example 'user/%/edit'.
 */
function feedback_mask_path($path) {
  return preg_replace('@/\\d+@', '/%', $path);
}

/**
 * Store a new feedback entry in the database.
 *
 * @param string $message
 *   A feedback message text entered by an user.
 * @param string $location
 *   The path on which the feedback message was entered.
 */
function feedback_add_entry($message, $location) {
  global $user;
  $fid = db_next_id("{feedback}_fid");
  db_query("INSERT INTO {feedback} (fid, uid, message, location, location_masked, url, timestamp, useragent) VALUES (%d, %d, '%s', '%s', '%s', '%s', %d, '%s')", $fid, $user->uid, trim($message), $location, feedback_mask_path($location), url($location, NULL, NULL, TRUE), time(), $_SERVER['HTTP_USER_AGENT']);
}

/**
 * Implementation of hook_user().
 */
function feedback_user($op, &$edit, &$account) {
  if ($op == 'delete') {
    db_query('UPDATE {feedback} SET uid = 0 WHERE uid = %d', $account->uid);
  }
}

/**
 * Build a (sortable) form containing a checkbox for each feedback entry.
 */
function feedback_admin_view_form() {
  $form = array();
  $status_headings = array(
    0 => t('Open feedback messages'),
    1 => t('Processed feedback messages'),
  );
  $form['#feedback_header'] = array(
    array(),
    array(
      'data' => t('Location'),
      'field' => 'f.location_masked',
      'sort' => 'asc',
    ),
    array(
      'data' => t('Date'),
      'field' => 'f.timestamp',
    ),
    array(
      'data' => t('User'),
      'field' => 'u.name',
    ),
    t('Message'),
  );

  // Hack to prevent pager_query() from issuing PHP notices.
  if (!isset($_GET['page'])) {
    $_GET['page'] = '';
  }
  if (count(explode(',', $_GET['page'])) < 2) {
    $_GET['page'] .= ',0';
  }
  $form['feedback-messages'] = array(
    '#tree' => TRUE,
  );
  foreach (array(
    0,
    1,
  ) as $status) {
    $sql = "SELECT f.*, u.name FROM {feedback} f INNER JOIN {users} u ON f.uid = u.uid WHERE f.status = %d";
    $count_query = "SELECT COUNT(fid) FROM {feedback} WHERE status = %d";
    $tablesort = tablesort_sql($form['#feedback_header']);
    $result = pager_query($sql . $tablesort, 50, $status, $count_query, $status);
    $form['feedback-messages'][$status] = array(
      '#type' => 'fieldset',
      '#title' => $status_headings[$status],
      '#collapsible' => TRUE,
      '#collapsed' => $status,
      '#attributes' => array(
        'class' => 'feedback-messages',
      ),
    );
    while ($entry = db_fetch_object($result)) {
      $form['feedback-messages'][$status][$entry->fid] = array(
        '#type' => 'checkbox',
        '#return_value' => 1,
        '#default_value' => FALSE,
      );
      $form['feedback-messages'][$status][$entry->fid]['location'] = array(
        '#value' => l(truncate_utf8($entry->location, 32, FALSE, TRUE), $entry->url),
      );
      $form['feedback-messages'][$status][$entry->fid]['date'] = array(
        '#value' => format_date($entry->timestamp, 'small'),
      );
      $form['feedback-messages'][$status][$entry->fid]['user'] = array(
        '#value' => theme('username', $entry),
      );
      $form['feedback-messages'][$status][$entry->fid]['message'] = array(
        '#value' => feedback_format_message($entry),
      );
    }
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Output a sortable table containing all feedback entries.
 */
function theme_feedback_admin_view_form($form) {
  $output = '';
  foreach (element_children($form['feedback-messages']) as $status) {
    $item =& $form['feedback-messages'][$status];
    if (!isset($item['#type']) || $item['#type'] != 'fieldset') {
      continue;
    }

    // Build the table.
    $rows = array();
    foreach (element_children($item) as $element_entry) {
      $entry =& $item[$element_entry];

      // Render the data first.
      $rows[] = array(
        0,
        drupal_render($entry['location']),
        drupal_render($entry['date']),
        drupal_render($entry['user']),
        drupal_render($entry['message']),
      );

      // Render the checkbox.
      $rows[count($rows) - 1][0] = drupal_render($entry);
    }
    if (empty($rows)) {
      $rows[] = array(
        array(
          'data' => t('No feedback entries available.'),
          'colspan' => 5,
        ),
      );
    }

    // Inject the table.
    $item['messages'] = array(
      '#value' => theme('table', $form['#feedback_header'], $rows) . theme('pager', array(), 50, $status),
      '#weight' => -1,
    );

    // Render the fieldset.
    $output .= drupal_render($item);
  }

  // Render internal FAPI and potential extra form elements.
  $output .= drupal_render($form);
  return $output;
}

/**
 * Form submit callback for admin view form.
 */
function feedback_admin_view_form_submit($form_id, $form_values) {
  $update = array();

  // Determine feedback entries to update.
  foreach ($form_values['feedback-messages'] as $status => $entries) {
    $entries = array_filter($entries);
    foreach ($entries as $fid => $value) {

      // Lame for now. :(
      $update[$fid] = $status == 0 ? 1 : 0;
    }
  }

  // Update status of entries in database.
  foreach ($update as $fid => $value) {
    db_query("UPDATE {feedback} SET status = %d WHERE fid = %d", $value, $fid);
  }
}

Functions

Namesort descending Description
feedback_add_entry Store a new feedback entry in the database.
feedback_admin_view_form Build a (sortable) form containing a checkbox for each feedback entry.
feedback_admin_view_form_submit Form submit callback for admin view form.
feedback_block Implementation of hook_block().
feedback_footer Implementation of hook_footer().
feedback_form Form builder function for a user feedback form.
feedback_format_message Format a feedback entry.
feedback_form_submit
feedback_load Load feedback entries from the database.
feedback_mask_path 'Mask' a path, i.e. replace all numeric arguments in a path with '%' placeholders.
feedback_menu Implementation of hook_menu().
feedback_perm Implementation of hook_perm().
feedback_user Implementation of hook_user().
theme_feedback_admin_view_form Output a sortable table containing all feedback entries.