You are here

datereminder.module in Date Reminder 6.2

Same filename and directory in other branches
  1. 6 datereminder.module
  2. 7 datereminder.module

Support for reminders for nodes with dates.

datereminder, or date reminder. A module that lets an authorized user ask for a reminder to be set at one or more durations before a scheduled event. It can be a repeating event; reminders will be sent at the requested times before each occurance.

Significant portions of this were blatently borrowed from or at least inspired by notifications. Notifications was oh so close to what I wanted, but just not quite.

See the README file for a list of outstanding issues.

File

datereminder.module
View source
<?php

/**
 * @file
 * Support for reminders for nodes with dates.
 *
 * datereminder, or date reminder.  A module that lets an authorized user
 * ask for a reminder to be set at one or more durations before a
 * scheduled event.  It can be a repeating event; reminders will
 * be sent at the requested times before each occurance.
 *
 * Significant portions of this were blatently borrowed from or
 * at least inspired by notifications.  Notifications was oh so close
 * to what I wanted, but just not quite.
 *
 * See the README file for a list of outstanding issues.
 */

/**
 * Implements hook_menu().
 */
function datereminder_menu() {
  module_load_include('inc', 'datereminder', 'includes/admin.settings');

  // Since this isn't called often, put code off in another file.
  return _datereminder_menu();
}

/**
 * Implements hook_nodeapi().
 *
 * Note that this is essentially a D6 to D7 adapter. It implements the
 * D6 api, but calls D7 api equivalents to do the actual work.
 *
 * @param node &$node
 *   The node in question
 * @param string $op
 *   What are we doing to the node?
 * @param boolean $teaser
 *   Set if we're working with teaser.
 * @param boolean $page
 *   Set if we're working with page.
 */
function datereminder_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  module_load_include('inc', 'datereminder', 'includes/node');
  switch ($op) {
    case 'delete':

      // Clean up anything for this node, even if we don't think the node type
      // or node has any reminders.
      datereminder_node_delete($node);
      break;
    case 'load':

      // Node is being loaded.
      datereminder_node_load(array(
        $node->nid => $node,
      ), array(
        $node->type,
      ));
      return array();
    case 'view':

      // Node is about to be viewed.
      // Question - Should we show reminders in the teaser?
      $view_mode = $page ? 'full' : ($teaser ? 'teaser' : '??');
      datereminder_node_view($node, $view_mode, NULL);
      break;
    case 'insert':

      // Node was just inserted into the database.
      datereminder_node_insert($node);
      break;
    case 'update':

      // Node has been edited. Writing back now.
      datereminder_node_update($node);
      break;
  }
}

/**
 * Implements hook_node_load().
 *
 * Set datereminder_enabled in each node to indicate that reminders are
 * enabled for the node. Reminder records won't be loaded until needed later.
 *
 * @param array $nodes
 *   Array of nodes being loaded, keyed by nid.
 * @param array $types
 *   Types of nodes being loaded. (So we can check if reminders are enabled
 *   for any of these.)
 */
function datereminder_node_load($nodes, $types) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  module_load_include('inc', 'datereminder', DATEREMINDER_DB);
  $typenabled = array();
  foreach ($types as $t) {
    $typenabled[$t] = _datereminder_type_enabled($t);
  }
  foreach ($nodes as $nid => $node) {
    if ($typenabled[$node->type] >= DATEREMINDER_TYPE_ALLOWED) {
      $en = _datereminder_get_node_enabled($nid);
      if ($en == NULL) {
        $node->datereminder_enabled = DATEREMINDER_TYPE_DISABLED;
      }
    }
    else {
      $en = DATEREMINDER_TYPE_DISABLED;
    }
    $node->datereminder_enabled = $en;
  }
}

/**
 * Implements hook_node_view().
 *
 * Add reminder to node about to be viewed.
 *
 * @param node $node
 *   The node about to be viewed.
 * @param string $view_mode
 *   View mode from node_view().
 * @param string $langcode
 *   Language code (not used here).
 */
function datereminder_node_view($node, $view_mode, $langcode) {

  // Don't bother with teasers or other, or if reminders are
  // displayed in a tab.
  $enabled = $node->datereminder_enabled;
  if ($enabled > 0) {

    // This node has reminders to display. But only display if this is
    // full (not teaser) view, and if reminders aren't under a separate tab.
    if ($view_mode == 'full' && !variable_get('datereminder_as_tab', FALSE)) {
      module_load_include('inc', 'datereminder', 'includes/defines');

      // Do we actually want reminders for this node?
      if ($enabled >= DATEREMINDER_TYPE_ALLOWED) {
        $content = _datereminder_node_output($node, 'node');
        $node->content['reminder'] = array(
          '#value' => $content,
          '#weight' => 50,
        );
      }
    }
  }
}

/**
 * Add option to node allowing user to request a reminder.
 */
function _datereminder_node_output(&$node, $type = 'node') {
  module_load_include('inc', 'datereminder', 'includes/defines');
  global $user;
  $output = '';
  if (empty($node->datereminder_enabled) || $node->datereminder_enabled < DATEREMINDER_TYPE_ALLOWED) {
    return $output;
  }
  if (datereminder_allowed_access_node($node, 'own', 'inline')) {
    $output = '<a name="reminder"></a>';
    $output .= _datereminder_current_user_reminder($node, $type);
  }
  return $output;
}

/**
 * Get table of the given node's reminders.
 */
function _datereminder_current_user_reminder($node, $type = 'node') {
  global $user;
  $output = '';
  $fieldset = $type == 'node' ? 'f' : 'm';
  $output .= drupal_get_form('datereminder_form', $node, $fieldset);
  return $output;
}

/**
 * Implements hook_user().
 *
 * Remove any reminders for given users(s)
 *
 * Note: This is the D6 api. Ideally, we'd turn this into calls to the
 * D7 functions, but the interfaces and implementations are different
 * enough (and simple enough) that we'll just keep both.
 */
function datereminder_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'delete':
      module_load_include('inc', 'datereminder', 'includes/date');
      module_load_include('inc', 'datereminder', 'includes/defines');
      module_load_include('inc', 'datereminder', DATEREMINDER_DB);
      if (is_array($edit['accounts'])) {

        // A multi-user delete from Admin > User management > Users.
        $uids = $edit['accounts'];
      }
      else {

        // A single-user delete from the edit tab on the user's profile.
        $uids = array(
          $edit['_account']->uid,
        );
      }
      _datereminder_delete_uids($uids);
      break;
  }
}

/**
 * Implements hook_node_type().
 *
 * Need to delete anything associated with any nodes of this type.
 *
 * Note that this is a D6->D7 adapter. It supports the D6 api, but
 * redirects to the D7 versions.
 */
function datereminder_node_type($op, $info) {
  module_load_include('inc', 'datereminder', 'includes/node_type');
  switch ($op) {
    case 'delete':
      datereminder_node_type_delete($info);
      break;
    case 'update':
      datereminder_node_type_update($info);
      break;
  }
}

/**
 * Implements hook_form_alter().
 *
 * Add section to node type edit form to enable reminders.
 */
function datereminder_form_alter(&$form, &$form_state, $form_id) {
  switch ($form_id) {
    case 'node_type_form':
      module_load_include('inc', 'datereminder', 'includes/node_form');
      datereminder_alter_node_type_form($form, $form_state, $form['#node_type']->type);
      break;
    default:
      if (!empty($form['type']['#value'])) {
        if ($form_id == $form['type']['#value'] . '_node_form') {
          module_load_include('inc', 'datereminder', 'includes/node_form');
          datereminder_alter_node_form($form, $form_state, $form_id);
        }
      }
      break;
  }
}

/**
 * Make sure that the user doesn't try to set node enabled to silly value.
 *
 * Note: This is only invoked if reminders are allowed for this node type.
 *
 * @param array $form
 *   The raw form.
 * @param array $form_state
 *   State info about form, including submitted values.
 */
function _datereminder_form_validate_node(&$form, &$form_state) {
  module_load_include('inc', 'datereminder', 'includes/defines');

  // What is user setting it to?
  $en = $form_state['values']['datereminder_enabled'];
  if ($en != DATEREMINDER_TYPE_DISABLED && $en != DATEREMINDER_TYPE_ON && $en != DATEREMINDER_TYPE_RETAIN) {
    form_set_error('datereminder_enabled', t("That's not a legal reminder setting"), 'error');
  }
}

/**
 * Handle form submit when admin enables or disables reminder for a node.
 *
 * Note: We don't need to check user access here. We'll only come here
 * if datereminder_alter_node_form() said we should. And it will only do
 * that if it's OK for the user to enable or disable reminders on this node.
 * Note that if reminders are enabled for this node type, anyone who can
 * modify a node of that type can enable or disable reminders.
 *
 * @param array &$form
 *   The submitted form
 * @param array &$form_state
 *   State of submitted form.
 *
 * @todo
 *   Move this into include/node.inc. Requires updating node_form.inc
 *   to direct the form update there.
 */
function _datereminder_form_submit_node(&$form, &$form_state) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  module_load_include('inc', 'datereminder', DATEREMINDER_DB);
  _datereminder_set_node_enabled($form['#node'], $form_state['values']['datereminder_enabled']);
}

/**
 * Get enablement for a content type.
 *
 * Functionally, it would make sense to put this in node_type.inc, but
 * it's used in so many places...
 *
 * @param string $type
 *   The content type
 *
 * @return int
 *   Enum telling default value for reminders for this type.
 */
function _datereminder_type_enabled($type) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  return variable_get("datereminder_enabled_{$type}", DATEREMINDER_TYPE_DISABLED);
}

/**
 * Get existing reminder info for a node and current user.
 *
 * @todo Pull in collateral info at the same time, like node title
 * and user name.
 */
function _datereminder_get_node_user_reminders(&$node) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  global $user;
  if (!user_access(DATEREMINDER_REQUEST_REMINDER)) {
    return NONE;
  }
  if (isset($node->reminders)) {
    $rem = $node->reminders;
  }
  else {
    module_load_include('inc', 'datereminder', DATEREMINDER_DB);
    $rem = _datereminder_load_reminders(array(
      'uid' => $user->uid,
      'nid' => $node->nid,
    ), $node, 'leadtime');
    $node->reminders = $rem;
  }
  return $rem;
}

/**
 * Callback on validate from datereminder_form.
 *
 * Did the user put reasonable values in the form?
 */
function _datereminder_form_validate_user($form, &$form_state) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  global $user;
  $ok = TRUE;
  $v = $form_state['values'];
  $nid = $v['nid'];
  $node = node_load($nid);
  if (empty($node) || !user_access(DATEREMINDER_REQUEST_REMINDER)) {
    form_set_error(NULL, t('Permission error'), 'error');
    return FALSE;
  }

  // Any reason not to do this?
  $form_state['datereminder_node'] = $node;

  // Get existing reminders.
  $r = _datereminder_get_node_user_reminders($node);
  $other_email = user_access(DATEREMINDER_OTHER_EMAIL);
  $rows = $v['rids'];
  $added = 0;
  if (!isset($rows) or $rows == '') {
    form_set_error(NULL, t('Corrupted form'));
    return FALSE;
  }
  $rows = explode(',', $rows);
  if (count($rows) > variable_get('datereminder_max_reminders', DATEREMINDER_MAX_REMINDERS)) {
    form_set_error(NULL, t('Permission error'), 'error');
    return;
  }
  foreach ($rows as $rid) {
    if (substr($rid, 0, 1) != 'n') {
      if (!isset($r[$rid])) {
        form_set_error(NULL, t('Permission error'), 'error');
        return;
      }
    }
    if ($other_email) {
      $e = $v["datereminder_email_{$rid}"];
      if ($e) {
        foreach (explode(',', $e) as $email) {
          if ($email && !valid_email_address($email)) {
            form_set_error("datereminder_email_{$rid}", t('This email address appears to be invalid'), 'error');
            $ok = FALSE;
          }
        }
      }
    }
  }
  return $ok;
}

/**
 * Callback on submit from datereminder_form.
 *
 * @todo The "send-now" operation sends email using the last reminder
 * found in the list, which may not correspond to anything useful. For
 * normal users, this isn't an issue. If the user can send emails, though,
 * the reminder used determines the target email address.  Probably should
 * let priviledged user select which email to send to.
 */
function _datereminder_form_submit_user($form, &$form_state) {
  global $user;
  $v = $form_state['values'];
  $nid = $v['nid'];
  $node = $form_state['datereminder_node'];
  $rems = _datereminder_get_node_user_reminders($node);
  $rows = explode(',', $v['rids']);
  module_load_include('inc', 'datereminder', 'includes/date');
  $df = _datereminder_get_node_datefield($node);
  $other_email = user_access(DATEREMINDER_OTHER_EMAIL);
  $changedrems = array();
  foreach ($rows as $rtag) {
    $changed = FALSE;
    $newtime = FALSE;
    if (substr($rtag, 0, 1) == 'n') {

      // This is a new reminder.
      unset($r);
      $r->leadtime = $v["datereminder_lead_{$rtag}"];
      if ($r->leadtime > 0) {
        $rid = -substr($rtag, 1);
        $r->nid = $node->nid;
        $r->node = $node;
        $r->uid = $user->uid;
        $r->user = $user;
        $r->next = 0;
        $r->rid = $rid;
        if ($other_email) {
          $r->email = $v["datereminder_email_{$rtag}"];
        }
        else {
          $r->email = '';
        }
        $newtime = TRUE;
      }
    }
    else {

      // This is an existing reminder.
      $rid = $rtag;
      $r = $rems[$rid];
      $ltn = $v["datereminder_lead_{$rtag}"];
      if ($ltn != $r->leadtime) {
        $r->leadtime = $ltn;
        $newtime = TRUE;
      }
      if ($other_email) {
        $em = $v["datereminder_email_{$rtag}"];
        if ($em != $r->email) {
          $r->email = $em;
          $changed = TRUE;
        }
      }
    }
    if ($newtime) {
      _datereminder_get_next_reminder($r, $df);
      if (!isset($r->next)) {
        drupal_set_message(t("Sorry, but it's too late to send that reminder"), 'error');
        return;
      }
      $changed = TRUE;
    }
    if ($changed) {
      $changedrems[$rid] = $r;
    }
  }
  if (count($changedrems) > 0) {
    _datereminder_set_reminders($changedrems);
    unset($node->reminders);
  }
  if ($v['send-now']) {
    module_load_include('inc', 'datereminder', 'includes/messaging');
    $r = NULL;
    $r->uid = $user->uid;
    $r->nid = $node->nid;
    $r->node = $node;
    $r->user = $user;
    $r->leadtime = 0;
    $r->next = NULL;
    if (_datereminder_send_reminder($r)) {
      drupal_set_message(t('An email reminder has been sent'));
    }
    else {
      drupal_set_message(t('Sorry, there was a problem sending mail'), 'error');
    }
  }
}

/**
 * Callback to delete a group of reminders from admin menu form.
 */
function _datereminder_admin_delete_set($form, &$form_state) {
  $v = $form_state['values'];
  $reminders = $v['reminders'];
  $dset = array();
  foreach ($reminders as $rem) {
    if ($rem > 0) {
      $dset[] = $rem;
    }
  }
  if (count($dset) > 0) {
    module_load_include('inc', 'datereminder', 'includes/defines');
    module_load_include('inc', 'datereminder', DATEREMINDER_DB);
    _datereminder_delete_rids($dset);
  }
}

/**
 * Implements hook_perm().
 */
function datereminder_perm() {
  module_load_include('inc', 'datereminder', 'includes/defines');
  return array(
    DATEREMINDER_ADMINISTER_REMINDERS,
    DATEREMINDER_VIEW_OTHER_USER_REMINDERS,
    DATEREMINDER_OTHER_EMAIL,
    DATEREMINDER_REQUEST_REMINDER,
  );
}

/**
 * Implements hook_messaging().
 */
function datereminder_messaging($op, $type = NULL, $arg2 = NULL) {
  module_load_include('inc', 'datereminder', 'includes/messaging');
  return _datereminder_messaging($op, $type, $arg2);
}

/**
 * Utility function to load node and user objects for a reminder when needed.
 *
 * @param reminder  $r
 *   Reminder to be completed
 */
function _datereminder_complete_reminder(&$r) {
  if (!isset($r->node)) {
    $r->node = node_load($r->nid);
  }
  if (!isset($r->user)) {
    $r->user = user_load(array(
      'uid' => $r->uid,
    ));
  }
}

/**
 * Implements hook_token().
 *
 * @param string $type
 *   Type of tokens being requested
 * @param string $object
 *   Associated object
 * @param array $options
 *   Not used here
 */
function datereminder_token_values($type, $object = NULL, $options = array()) {
  $values = array();
  if ($type == 'datereminder') {
    $r = $object;
    if ($r) {
      module_load_include('inc', 'datereminder', 'includes/date');
      $datefield = _datereminder_get_datefield($r);
      $tz = $r->user->timezone_name;
      if (!isset($tz)) {
        $tz = date_default_timezone_name(FALSE);
      }
      $next_due = $r->next_due;

      // We want then next-date tokens to show next occurrance after
      // the time this reminder will be sent. But if the reminder isn't
      // currently scheduled, use 'now'.
      if (isset($next_due)) {
        $next_due = _datereminder_internal_date_to_datetime($next_due);
      }
      else {
        $next_due = _datereminder_now();
      }
      $dobj = _datereminder_get_occurance_after_date($datefield, $next_due);
      if (isset($dobj)) {
        date_timezone_set($dobj, timezone_open($tz));
        $values['next-date-short'] = date_format_date($dobj, 'short');
        $values['next-date-medium'] = date_format_date($dobj, 'medium');
        $values['next-date-long'] = date_format_date($dobj, 'long');
      }
      else {
        $values['next-date-short'] = t('Past');
        $values['next-date-medium'] = t('Past');
        $values['next-date-long'] = t('Past');
      }
    }
  }
  return $values;
}

/**
 * Implements hook_token_list().
 */
function datereminder_token_list($type = 'all') {
  $tokens = array();
  if ($type == 'datereminder' || $type == 'all') {
    $tokens['datereminder']['next-date-short'] = t('The date of the next occurrance of this event, short form');
    $tokens['datereminder']['next-date-medium'] = t('The date and time of the next occurance. medium form');
    $tokens['datereminder']['next-date-long'] = t('The date and time of the next occurance. long form');
  }
  return $tokens;
}

/**
 * Implements hook_cron().
 *
 * Check to see if we need to send any reminders.
 */
function datereminder_cron() {
  module_load_include('inc', 'datereminder', 'includes/cron');
  _datereminder_cron();
}

/**
 * Check if this user can access reminders at all.
 *
 * @global type $user
 *
 * @param user $acct
 *   A user struct, maybe not the current one
 *
 * @return boolean
 *   TRUE if requested access is allowed
 */
function datereminder_access_user($acct) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  global $user;
  if ($acct && $acct->uid && $user->uid == $acct->uid) {
    return user_access(DATEREMINDER_REQUEST_REMINDER);
  }
  return FALSE;
}

/**
 * Check if this user can access reminders for this node.
 *
 * @param node $node
 *   The node
 * @param string $acc
 *   What kind of access is requested?
 *   'own' means set/view own reminder
 *   'all' means view others' reminders
 */
function datereminder_allowed_access_node($node = NULL, $acc = 'own', $loc = 'inline') {
  module_load_include('inc', 'datereminder', 'includes/defines');

  // First, be sure reminders are on for this.
  if (!isset($node) || $node->datereminder_enabled != DATEREMINDER_TYPE_ON) {
    return FALSE;
  }
  $astab = variable_get('datereminder_as_tab', FALSE);
  if ($loc == 'inline') {
    if ($astab) {
      return FALSE;
    }
  }
  else {
    if (!$astab) {
      return FALSE;
    }
  }
  if (user_access(DATEREMINDER_VIEW_OTHER_USER_REMINDERS) || user_access(DATEREMINDER_ADMINISTER_REMINDERS)) {
    return TRUE;
  }
  return $acc == 'own' && user_access(DATEREMINDER_REQUEST_REMINDER);
}

/**
 * Check if this user see another specific user's reminders.
 *
 * @param user $u
 *   The user struct. NULL means all users.
 */
function datereminder_allowed_access_user($u = NULL) {
  module_load_include('inc', 'datereminder', 'includes/defines');
  global $user;
  if (user_access(DATEREMINDER_ADMINISTER_REMINDERS) || user_access(DATEREMINDER_VIEW_OTHER_USER_REMINDERS)) {
    return TRUE;
  }
  if (!user_access(DATEREMINDER_REQUEST_REMINDER)) {
    return FALSE;
  }
  return isset($u) && $u->uid == $user->uid;
}

/**
 * Implements hook_theme().
 */
function datereminder_theme() {
  return array(
    'datereminder_manage_reminders' => array(
      'arguments' => array(
        'form' => NULL,
      ),
      'file' => 'datereminder.form.inc',
      'file path' => drupal_get_path('module', 'datereminder') . '/includes',
    ),
    'datereminder_table' => array(
      'arguments' => array(
        'form' => NULL,
      ),
      'file' => 'datereminder.form.inc',
      'file path' => drupal_get_path('module', 'datereminder') . '/includes',
    ),
  );
}

Functions

Namesort descending Description
datereminder_access_user Check if this user can access reminders at all.
datereminder_allowed_access_node Check if this user can access reminders for this node.
datereminder_allowed_access_user Check if this user see another specific user's reminders.
datereminder_cron Implements hook_cron().
datereminder_form_alter Implements hook_form_alter().
datereminder_menu Implements hook_menu().
datereminder_messaging Implements hook_messaging().
datereminder_nodeapi Implements hook_nodeapi().
datereminder_node_load Implements hook_node_load().
datereminder_node_type Implements hook_node_type().
datereminder_node_view Implements hook_node_view().
datereminder_perm Implements hook_perm().
datereminder_theme Implements hook_theme().
datereminder_token_list Implements hook_token_list().
datereminder_token_values Implements hook_token().
datereminder_user Implements hook_user().
_datereminder_admin_delete_set Callback to delete a group of reminders from admin menu form.
_datereminder_complete_reminder Utility function to load node and user objects for a reminder when needed.
_datereminder_current_user_reminder Get table of the given node's reminders.
_datereminder_form_submit_node Handle form submit when admin enables or disables reminder for a node.
_datereminder_form_submit_user Callback on submit from datereminder_form.
_datereminder_form_validate_node Make sure that the user doesn't try to set node enabled to silly value.
_datereminder_form_validate_user Callback on validate from datereminder_form.
_datereminder_get_node_user_reminders Get existing reminder info for a node and current user.
_datereminder_node_output Add option to node allowing user to request a reminder.
_datereminder_type_enabled Get enablement for a content type.