You are here

discussthis.module in Discuss This! 6

Same filename and directory in other branches
  1. 5 discussthis.module
  2. 7.2 discussthis.module
  3. 7 discussthis.module

Associations discussions in forums with specific nodes

File

discussthis.module
View source
<?php

/**
 * @file
 *
 * Associations discussions in forums with specific nodes
 */
define('DISCUSSTHIS_DEFAULT_NODE_MESSAGE', 'Following is a discussion on the [node-type-name] item titled: [node-link].<br/>' . 'Below is the discussion so far. Feel free to add your own comments!<br/>');

// Hook Implementations

/**
 * Display help and module information
 * @param section which section of the site we're displaying help for
 * @return help text for section
 */
function discussthis_help($section) {
  switch ($section) {
    case 'admin/help#discussthis':
      $output = '';
      $output = '<p>' . t('Displays links to discussion forums for a given node. Administrators can select the types of nodes for which this is enabled, and for each of these, which forum new topics should be created in.') . '</p>';
      $output .= '<ul><li><a href="admin/settings/discussthis">Discuss This configuration settings</a></li>';
      $output .= '<li><a href="admin/user/access#module-discussthis">Discuss This permissions configuration</a></li></ul>';

      //$output .= filter_filter('process', 2, null, file_get_contents(dirname(__FILE__) .'/README.txt') );
      return $output;
  }
}

/**
 * \brief Implementation of hook_init().
 * Add the CSS.
 */
function discussthis_init() {
  $path = drupal_get_path('module', 'discussthis');
  drupal_add_css($path . '/discussthis.css');
}

/**
 * \brief Implementation of hook_perm().
 * Permissions for this module
 * @return array An array of valid permissions for the discussthis module
 */
function discussthis_perm() {
  return array(
    'administer discuss this',
    'override discuss this forums',
    'access discuss this links',
    'initiate discuss this topics',
  );
}

/**
 * \brief Implementation of hook_menu().
 *
 * Generate an array of the Discuss This! menus.
 *
 * \return array of menu items
 */
function discussthis_menu() {
  module_load_include('admin.inc', 'discussthis');
  return _discussthis_menu();
}

/**
 * \brief Implementation of hook_theme().
 *
 * This enables us to tweak the form so it looks as expected.
 *
 * \return An array of theme supported by this module.
 */
function discussthis_theme() {
  return array(
    'discussthis_admin_settings_forums' => array(
      'arguments' => array(
        'form',
      ),
      'file' => 'discussthis.admin.inc',
    ),
  );
}

/**
 * \brief hook_nodeapi implementation
 *
 * This function ensures that deleted nodes and topics unlink nodes
 * and topics that are linked via the Discuss This! module.
 *
 * In View mode, this function generates comments at the bottom
 * of the page being displayed. The comments are taken from the
 * topic.
 *
 * \param[in,out] $node the node being acted on
 * \param[in] $op the operation being done to the node
 * \param[in] $teaser whether this is a teaser view
 * \param[in] $page whether this is a full page view
 */
function discussthis_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'validate':
      if ($node && user_access('override discuss this forums') && isset($node->discussthis)) {
        if (!empty($node->discussthis['discussthis_topic'])) {
          $topic_nid = $node->discussthis['discussthis_topic'];

          // valid integer?
          // and if valid, is that this very node? if so, that's not good either
          if ($topic_nid == (int) $topic_nid && !is_null($node->nid) && $topic_nid != $node->nid && is_numeric($topic_nid)) {

            // make sure we can actually load that node and that's a forum's node
            $topic = node_load(array(
              'nid' => (int) $topic_nid,
            ));
            if (!$topic || $topic->type != 'forum') {
              $topic_nid = FALSE;
            }
          }
          else {
            $topic_nid = FALSE;
          }
          if (!$topic_nid) {

            // ugly error setting which works
            form_set_error('discussthis][discussthis_topic', t('The Discuss This! forum topic #@nid was not found (or you chose this very node.) Please, try again. Use the auto-complete feature or enter the exact node identifier. If you did not change the topic identifier, it could have been deleted while you were editing this node. Simply clear the topic entry in that case.', array(
              '@nid' => $node->discussthis['discussthis_topic'],
            )));
            unset($node->discussthis['discussthis_topic']);
          }
        }

        // the forum should never be wrong with a select unless we're working with a hacker
        if (!empty($node->discussthis['discussthis_forum'])) {

          // valid integer? representing a valid forum?
          $forum_tid = $node->discussthis['discussthis_forum'];
          if ((int) $forum_tid != -1) {
            if ($forum_tid == (int) $node->discussthis['discussthis_forum']) {
              $vid = variable_get('forum_nav_vocabulary', '');
              $term = taxonomy_get_term($forum_tid);
              if (!$term || $term->vid != $vid) {
                form_set_error('discussthis][discussthis_forum', t('The Discuss This! forum #@tid was not found.', array(
                  '@tid' => $forum_tid,
                )));
                unset($node->discussthis['discussthis_forum']);
              }
            }
            else {
              unset($node->discussthis['discussthis_forum']);
            }
          }
        }
      }
      break;
    case 'insert':
    case 'update':
      if ($node) {
        if ($node->type == 'forum') {
        }
        else {
          if (user_access('override discuss this forums')) {

            // the forum field is only available for nodes that are
            // not yet attached to a topic, so it may not be defined
            if (isset($node->discussthis['discussthis_forum'])) {
              $discussthis_forum['nid'] = $node->nid;
              $discussthis_forum['forum_tid'] = $node->discussthis['discussthis_forum'];
              _discussthis_set_forum($discussthis_forum);
            }

            // topics are defined by nid
            db_lock_table('discussthis');
            $sql = "DELETE FROM {discussthis} WHERE nid = %d";
            db_query($sql, $node->nid);
            if (!empty($node->discussthis['discussthis_topic'])) {
              $sql = "INSERT INTO {discussthis} (nid, topic_nid) VALUES (%d, %d)";
              db_query($sql, $node->nid, $node->discussthis['discussthis_topic']);
            }
            db_unlock_tables();
          }
        }
      }
      break;
    case 'delete':

      // drop any reference to that node
      //
      // PROBLEM: Note that if someone just initiated a "Discuss this"
      // form, then they will have a surprise when saving since the
      // page will be gone. I don't see any way around that though.
      //
      $sql = 'DELETE FROM {discussthis} WHERE nid = %d OR topic_nid = %d';
      db_query($sql, $node->nid, $node->nid);
      $sql = 'DELETE FROM {discussthis_forums} WHERE nid = %d';
      db_query($sql, $node->nid);
      break;
    case 'view':

      // only pages being displayed by themselves have comments shown
      if (!$teaser && $page) {
        $node->content['body']['#value'] .= discussthis_comment_render($node);
      }
      break;
  }
}

/**
 * Implementation of hook_rules_event_info().
 * @ingroup rules
 */
function discussthis_rules_event_info() {
  return array(
    'discussthis_discussion_new' => array(
      'label' => t('A new discussion is started'),
      'module' => 'discussthis',
      'arguments' => array(
        'related_node' => array(
          'type' => 'node',
          'label' => t('Related node.'),
        ),
        'comment' => array(
          'type' => 'comment',
          'label' => t('Comment'),
        ),
      ),
    ),
    'discussthis_discussion_update' => array(
      'label' => t('A discussion is updated'),
      'module' => 'discussthis',
      'arguments' => array(
        'related_node' => array(
          'type' => 'node',
          'label' => t('Related node.'),
        ),
        'comment' => array(
          'type' => 'comment',
          'label' => t('Comment'),
        ),
      ),
    ),
  );
}

/**
 * Implementation of hook_comment().
 */
function discussthis_comment(&$comment, $op) {
  if ($op == 'publish' && module_exists('rules')) {
    $nid = db_result(db_query("SELECT nid FROM {discussthis} WHERE topic_nid = %d", $comment['nid']));
    if ($nid) {
      $node = node_load($nid);
      if (arg(0) == 'discussthis') {
        rules_invoke_event('discussthis_discussion_new', $node, $comment);
      }
      else {
        rules_invoke_event('discussthis_discussion_update', $node, $comment);
      }
    }
  }
}

/**
 * \brief Render comments.
 *
 * This function renders comments as the Comment module would do.
 * (I started with a copy of comment_render())
 *
 * There are mainly two things that we don't do in this version:
 *
 * 1) we do not want to have the pager
 *
 * 2) we want to use the Discuss This! counter
 *
 * \note
 * This function uses the default themes as defined in the comment
 * module.
 *
 * \todo
 * The mode and sort are currently taken from the Topic linked to
 * the node passed as a parameter. It may be preferable to have our
 * own Discuss This! settings on a per node type.
 *
 * \param[in] $node The node that will receive the comments
 */
function discussthis_comment_render($node) {

  // make sure the user wants to see something
  $comments_per_page = variable_get('discussthis_comments', 0);
  if ($comments_per_page <= 0) {
    return '';
  }

  // user can access comments?
  if (!user_access('access comments')) {
    return '';
  }

  // node has a topic attached?
  // (we are actually interested by the topic comments!)
  $nid = _discussthis_get_topic($node->nid);
  if (!$nid) {
    return '';
  }
  $topic = node_load(array(
    'nid' => $nid,
  ));

  // use the topic settings (or should we use the node?!)
  // TODO: maybe we want to add an entry in the node type
  //       so Discuss This! can have its own settings?
  $mode = _comment_get_display_setting('mode', $topic);
  $order = _comment_get_display_setting('sort', $topic);

  // using c.cid AS cid because of the SQL rewrite below
  $query = 'SELECT c.cid AS cid, c.pid, c.nid, c.subject, c.comment, c.format, c.timestamp,' . ' c.name, c.mail, c.homepage, u.uid, u.name AS registered_name, u.signature,' . ' u.signature_format, u.picture, u.data, c.thread, c.status' . ' FROM {comments} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.nid = %d';
  $query_args = array(
    $nid,
  );
  if (!user_access('administer comments')) {
    $query .= ' AND c.status = %d';
    $query_args[] = COMMENT_PUBLISHED;
  }
  if ($order == COMMENT_ORDER_NEWEST_FIRST) {
    if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
      $query .= ' ORDER BY c.cid DESC';
    }
    else {
      $query .= ' ORDER BY c.thread DESC';
    }
  }
  elseif ($order == COMMENT_ORDER_OLDEST_FIRST) {
    if ($mode == COMMENT_MODE_FLAT_COLLAPSED || $mode == COMMENT_MODE_FLAT_EXPANDED) {
      $query .= ' ORDER BY c.cid';
    }
    else {

      // See Comment module for more info.
      $query .= ' ORDER BY SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))';
    }
  }
  $query = db_rewrite_sql($query, 'c', 'cid');

  // Start a form, for use with comment control.
  $result = db_query_range($query, $query_args, 0, $comments_per_page);
  $divs = 0;
  $comments = '';
  drupal_add_css(drupal_get_path('module', 'comment') . '/comment.css');
  while ($comment = db_fetch_object($result)) {
    $comment = drupal_unpack($comment);
    $comment->depth = count(explode('.', $comment->thread)) - 1;
    if ($comment->uid) {
      $comment->name = $comment->registered_name;
    }
    if ($mode == COMMENT_MODE_THREADED_COLLAPSED || $mode == COMMENT_MODE_THREADED_EXPANDED) {
      if ($comment->depth > $divs) {
        $divs++;
        $comments .= '<div class="indented">';
      }
      else {
        while ($comment->depth < $divs) {
          $divs--;
          $comments .= '</div>';
        }
      }
    }
    if ($mode == COMMENT_MODE_FLAT_COLLAPSED) {
      $comments .= theme('comment_flat_collapsed', $comment, $topic);
    }
    elseif ($mode == COMMENT_MODE_FLAT_EXPANDED) {
      $comments .= theme('comment_flat_expanded', $comment, $topic);
    }
    elseif ($mode == COMMENT_MODE_THREADED_COLLAPSED) {
      $comments .= theme('comment_thread_collapsed', $comment, $topic);
    }
    elseif ($mode == COMMENT_MODE_THREADED_EXPANDED) {
      $comments .= theme('comment_thread_expanded', $comment, $topic);
    }
  }
  for (; $divs > 0; --$divs) {
    $comments .= '</div>';
  }
  if ($comments) {
    return '<div class="discussthis-comments">' . theme('comment_wrapper', $comments, $topic) . '</div>';
  }
  return '';
}

/**
 * \brief Implementation of hook_link().
 *
 * This adds the custom link to the node view
 *
 * \param[in] $type An identifier declaring what kind of link is being requested.
 * \param[in] $node A node object being passed in the case of node links.
 * \param[in] $teaser A boolean flag depending on whether the node is displayed as a teaser or full form.
 *
 * \return An array of the requested links
 */
function discussthis_link($type, $node = NULL, $teaser = FALSE) {
  if ($type == 'node' && $node) {
    $links = array();

    // verify that the type is selected
    $node_types = node_get_types('names');
    $discussthis_nodetypes = variable_get('discussthis_nodetypes', $node_types);
    if (empty($discussthis_nodetypes[$node->type])) {
      return $links;
    }

    // verify that the type has a valid discussion forum selected
    $discussthis_forum = _discussthis_get_forum($node->nid, $node->type);
    if ($discussthis_forum['forum_tid'] <= 0) {
      return $links;
    }

    // lookup for topic nid, if it exists (otherwise we get 0)
    $topic_nid = _discussthis_get_topic($node->nid);
    $discussthis_link = variable_get('discussthis_link', '');
    if (!$discussthis_link) {
      $discussthis_link = t('Discuss This!');
    }
    $participate_link = variable_get('discussthis_participate', '');
    if (!$participate_link) {
      $participate_link = t('Participate in this discussion');
    }

    // if the topic exists, and the user has access to link to it
    if ($topic_nid && user_access('access discuss this links')) {
      $all = comment_num_all($topic_nid);
      if (variable_get('discussthis_showcounts', 1)) {
        $new = comment_num_new($topic_nid);
        $counts = t(' (@new new/@all total)', array(
          '@new' => $new,
          '@all' => $all,
        ));
      }
      $links['discussthis'] = array(
        // use "Discuss This!" when no discussion started yet
        'title' => $all == 0 ? $discussthis_link : $participate_link . $counts,
        'href' => 'node/' . $topic_nid,
        'attributes' => array(
          'class' => 'discussthis-link',
          'title' => t('Participate to the discussion about this page'),
        ),
      );
    }
    elseif ($topic_nid == 0) {

      // no topic exists
      // check whether the user has permission to initiate a new topics
      if (user_access('initiate discuss this topics')) {
        $links['discussthis'] = array(
          'title' => $discussthis_link,
          'href' => 'discussthis/new/' . $node->nid,
          'attributes' => array(
            'class' => 'discussthis-link',
            'title' => t('Start a discussion about this page'),
            'rel' => 'nofollow',
          ),
        );
      }
    }

    // still no link?
    if (count($links) == 0) {

      // if the user is not logged in, offer him to do so if he otherwise
      // would have access to the link
      global $user;
      if ($user->uid == 0 && variable_get('discussthis_login', 1) && user_access('access discuss this links')) {
        if ($topic_nid) {
          $all = comment_num_all($topic_nid);
          if (variable_get('discussthis_showcounts', 1)) {
            $new = comment_num_new($topic_nid);
            $counts = t(' (@new new/@all total)', array(
              '@new' => $new,
              '@all' => $all,
            ));
          }
          $destination = 'destination=node/' . $topic_nid;
          $appeal = $all == 0 ? $discussthis_link : $participate_link . $counts;
        }
        else {

          // The topic does not exist--note: if someone else creates it
          // before this user comes back, the proper post will be shown
          // automatically (if not, the bug is not here.)
          $destination = 'destination=discussthis/new/' . $node->nid;
          $appeal = $discussthis_link;
        }
        $attributes = array(
          'class' => 'discussthis-login',
          'title' => t('Log in or register and start a discussion about this page'),
          'rel' => 'nofollow',
        );
        if (variable_get('user_register', 1)) {
          if (variable_get('user_email_verification', 1)) {

            // Users can register themselves but have to go through e-mail verification process
            $new_user = variable_get('discussthis_new_user', '');
            if (!$new_user) {
              $new_user = t('(new users have to return to the discussion after validating their new account by e-mail)');
            }
            $appeal .= ' ' . $new_user;
            $href = t('!login or !register to @appeal', array(
              '!login' => l(t('Log in'), 'user/login', array(
                'query' => $destination,
                'attributes' => $attributes,
              )),
              '!register' => l(t('Register'), 'user/register'),
              '@appeal' => $appeal,
            ));
          }
          else {

            // Users can register themselves without e-mail verification
            $href = t('!login or !register to @appeal', array(
              '!login' => l(t('Log in'), 'user/login', array(
                'query' => $destination,
              )),
              '!register' => l(t('Register'), 'user/register', array(
                'query' => $destination,
                'attributes' => $attributes,
              )),
              '@appeal' => $appeal,
            ));
          }
        }
        else {

          // Only admins can add new users, no public registration.
          $href = t('!login to @appeal', array(
            '!login' => l('Log in', 'user/login', array(
              'query' => $destination,
              'attributes' => $attributes,
            )),
            '@appeal' => $appeal,
          ));
        }
        $links['discussthis'] = array(
          'title' => $href,
          'html' => TRUE,
        );
      }
    }
    return $links;
  }
}

/**
 * \brief hook_form_alter implementation
 *
 * Adds per-node override forum dropdown and topic autocomplete
 * form for node edit forms
 *
 * \param[in] $form the form itself
 * \param[in,out] $form_state the current state of the form
 * \param[in] $form_id the id of the form to be altered
 */
function discussthis_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'forum_node_form' && !empty($form_state['discussthis']['post'])) {

    // we need to tweak forum forms to make sure that captcha do not
    // both us when we're auto-posting a new entry in the forum
    // if the user has a captcha on the forum, we need to add a dummy entry
    if (module_exists('captcha') && (arg(0) != 'admin' || variable_get('captcha_allow_on_admin_pages', FALSE)) && !user_access('skip CAPTCHA')) {
      module_load_include('inc', 'captcha');
      $captcha_point = captcha_get_form_id_setting('forum_node_form');
      if ($captcha_point && $captcha_point->type) {

        // the captcha type is set to none so it will be ignored
        $form['buttons']['captcha']['#captcha_type'] = 'none';
      }
    }
  }

  // can the user override the Discuss This! selection?
  if (!user_access('override discuss this forums')) {
    return;
  }

  // we're only modifying node forms, if the type field isn't set we don't need
  // to bother; otherwise, store it for later retrieval.
  if (isset($form['type']['#value'])) {
    $type = $form['type']['#value'];
  }
  elseif (isset($form['orig_type']['#value'])) {
    $type = $form['orig_type']['#value'];
  }
  else {
    return;
  }

  // we only support node forms
  if ($form_id != $type . '_node_form') {
    return;
  }

  // we know it is a node, attempt loading info about it
  // WARNING: $nid can be NULL or 0 when creating a new node
  $nid = $form['nid']['#value'];

  // only modify the form if this node type is discussthis-enabled
  // note that a type may be turned off, but if the node already
  // had a discussthis post attached to it, then it is still editable
  $discussthis_forum = _discussthis_get_forum($nid, $type);
  $forum_tid = $discussthis_forum['forum_tid'];
  if (!$forum_tid) {
    return;
  }
  $form['discussthis'] = array(
    '#type' => 'fieldset',
    '#title' => t('Discuss This'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#tree' => TRUE,
  );
  $name = '';
  $topic_nid = _discussthis_get_topic($nid);
  if ($topic_nid) {

    // there is a topic linked, is the node still available?
    $topic = node_load(array(
      'nid' => $topic_nid,
    ));
    if ($topic) {

      // TODO: also get the name of the forum to display to the user
      $name = '<strong>' . t('Current topic #@nid: @title', array(
        '@title' => $topic->title,
        '@nid' => $topic_nid,
      )) . '</strong><br /><br />';
    }
    else {
      $sql = "DELETE FROM {discussthis} WHERE nid = %d";
      db_query($sql, $nid);
      unset($topic_nid);
    }
  }
  if (!$topic_nid) {

    // let users override the default module of new discussion
    // (this is not shown unless there is a discussion)
    // list of forums, or "No Discuss This!"
    $forums = forum_get_forums();
    $discussthis_forums[-1] = 'No Discuss This!';
    foreach ($forums as $tid => $forum) {
      if (empty($forum->container)) {
        $discussthis_forums[$tid] = $forum->name;
      }
    }
    $form['discussthis']['discussthis_forum'] = array(
      '#type' => 'select',
      '#title' => t('Forum for new discussion'),
      '#required' => TRUE,
      '#description' => t('Select the forum where the first discussion about this node will be created. Or select "No Discuss This!" to prevent discussions on this node. Note: if a topic is attached to this node, then this parameter will have no effect.'),
      '#options' => $discussthis_forums,
      '#default_value' => $forum_tid,
    );
  }

  // let the user assign a forum topic to this node
  // this can be done at the time a node is being created
  // any forum is acceptable (although we could have flags to know whether that's the case...)
  $form['discussthis']['discussthis_topic'] = array(
    '#type' => 'textfield',
    '#title' => t('Identifier of the forum topic to attach to this node'),
    '#description' => $name . t('To use the auto-complete feature, enter the title of a topic to attach to this node. Otherwise, simply enter the topic identifier directly. Clear the identifier or use 0 to detach this node from the current topic it is attached to. New node can immediately be attached to a discussion topic. WARNING: this feature does not prevent you from selecting a topic from a forum that is not otherwise explicitly connected with the Discuss This! module.'),
    '#default_value' => $topic_nid ? $topic_nid : '',
    '#autocomplete_path' => 'discussthis/autocomplete',
    '#maxlength' => 255,
  );
}

/**
 * \brief Implementation of hook_token_list()
 *
 * This function provides a list of tokens offered by this module.
 *
 * \param[in] $type the context of the tokens being requested
 *
 * \return array of tokens grouped by context
 */
function discussthis_token_list($type = 'all') {
  if ($type == 'discussthis' || $type == 'all') {
    $tokens['discussthis']['node-link'] = t('A link to the original node under discussion.');
    $tokens['discussthis']['node-title'] = t('The title of the original node.');
    $tokens['discussthis']['node-type-name'] = t('The name of the original node\'s type.');
    $tokens['discussthis']['node-type'] = t('The original node\'s type.');
    $tokens['discussthis']['node-teaser'] = t('Node teaser');
    $tokens['discussthis']['node-body'] = t('Node body');
    $tokens['discussthis']['node-body-50'] = t('First 50 characters of a body');
    $tokens['discussthis']['node-body-100'] = t('First 100 characters of a body');
    $tokens['discussthis']['node-body-200'] = t('First 200 characters of a body');
    $tokens['discussthis']['node-body-400'] = t('First 400 characters of a body');
    return $tokens;
  }
}

/**
 * \brief Implementation of hook_token_values()
 *
 * This function provides the values for discussthis tokens.
 *
 * \note
 * We expect the forum to have a filter validating the node contents that
 * we provide in the tokens. Because of this, we do not tweak the data at
 * all (except for the <!--break--> which gets removed from the body.)
 *
 * \param[in] $type the context/type of object
 * \param[in] $object the object itself
 * \param[in] $options the options, context and object-sensitive
 *
 * \return array of token values keyed by their name
 */
function discussthis_token_values($type, $object = NULL, $options = array()) {
  if ($type == 'discussthis' && $object) {
    $object->body = str_replace('<!--break-->', '', $object->body);
    $tokens['node-link'] = l($object->title, 'node/' . $object->nid);
    $tokens['node-title'] = $object->title;
    $tokens['node-type-name'] = node_get_types('name', $object);
    $tokens['node-type'] = $object->type;
    $tokens['node-teaser'] = $object->teaser;
    $tokens['node-body'] = $object->body;
    $tokens['node-body-50'] = substr($object->body, 0, 50);
    $tokens['node-body-100'] = substr($object->body, 0, 100);
    $tokens['node-body-200'] = substr($object->body, 0, 200);
    $tokens['node-body-400'] = substr($object->body, 0, 400);
    return $tokens;
  }
}

/**
 * \brief User is creating a new comment.
 *
 * This function redirects the user so he/she can post a comment
 * on the concerned node.
 *
 * The forum topic will be created later once the comment is actually
 * posted to us.
 *
 * \param[in] $nid The node identifier
 */
function discussthis_new($nid) {

  //$topic_nid = _discussthis_new_topic($nid);

  //if ($topic_nid) {

  //  drupal_goto('comment/reply/'. $topic_nid, NULL, 'comment-form');

  //}

  // current user has the right to do that?!
  if (!user_access('initiate discuss this topics')) {
    drupal_access_denied();
    return;
  }

  // topic already exists?
  $topic_nid = _discussthis_get_topic($nid);
  if ($topic_nid) {

    // just open a comment window
    drupal_goto('comment/reply/' . $topic_nid, NULL, 'comment-form');
  }

  // we goto because of the possibility of a preview
  // but we still have a problem with duplicates
  // (i.e. two people could access the form at the same time
  // and then both create a new forum topic, argh!)
  drupal_goto('discussthis/create/' . $nid);

  //return drupal_get_form('discussthis_create_form', $nid);
}

/**
 * \brief Avoid having the form cached.
 *
 * Since it can generate problems to have a cached form
 * (i.e. especially a form with a CAPTCHA) we tell boost
 * to ignore our create form.
 *
 * \param[in] $path The path to the form being checked by CAPTCHA
 *
 * \return FALSE if the path starts with discussthis/create, NULL otherwise
 */
function discussthis_boost_is_cacheable($path) {
  if (strncmp($path, 'discussthis/create', 18) == 0) {
    return FALSE;
  }
}

/**
 * \brief Create a comment form.
 *
 * This function is more or less a copy of the Core comment form.
 * It creates a form where the user is expected to enter his/her
 * comment and click Preview/Submit.
 *
 * \param[in] $form_state The current state of the form
 * \param[in] $nid The node that is being discussed
 *
 * \return The form in HTML
 */
function discussthis_create_form($form_state, $nid) {

  // the user usually enters this function because the topic does not
  // exist yet, this form asks for the comment with our own discussthis
  // form (i.e. avoid creating a new forum node until the person posts
  // his/her comment out)
  //
  // IMPORTANT: we do NOT check whether the topic already exists because
  // it might when the person clicks on Preview. Please, see the submit
  // function for more information in that regard. Just don't add a test
  // for the topic ID in this function...
  // current user has the right to do that?!
  if (!user_access('initiate discuss this topics')) {
    drupal_access_denied();
    return;
  }
  $title = variable_get('discussthis_new_post_title', '');
  if ($title) {
    drupal_set_title($title);
  }
  global $user;
  $op = empty($form_state['post']['op']) ? '' : $form_state['post']['op'];
  $is_ready = $op == t('Preview') || $op == t('Save');
  if ($is_ready) {
    $comment = $user;
    $comment->comment = check_markup($form_state['post']['comment'], $form_state['post']['format'], FALSE);
    $comment->timestamp = time();
    $comment->new = FALSE;

    // this is not displayed in a very good way, in general
    $comment->preview = TRUE;
    $comment->subject = $form_state['post']['subject'];
    $node = array(
      'type' => 'forum',
    );
    $node = (object) $node;
    $form['preview_comment'] = array(
      '#title' => t('Preview'),
      '#value' => theme('comment', $comment, $node),
    );
  }
  $comment_anonymous_forum = variable_get('comment_anonymous_forum', COMMENT_ANONYMOUS_MAYNOT_CONTACT);
  if (!$user->uid && $comment_anonymous_forum != COMMENT_ANONYMOUS_MAYNOT_CONTACT) {
    drupal_add_js(drupal_get_path('module', 'comment') . '/comment.js');
  }
  if ($user->uid) {
    $form['_author'] = array(
      '#type' => 'item',
      '#title' => t('Your name'),
      '#value' => theme('username', $user),
    );
    $form['author'] = array(
      '#type' => 'value',
      '#value' => $user->name,
    );
  }
  elseif ($comment_anonymous_forum == COMMENT_ANONYMOUS_MAY_CONTACT) {
    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => t('Your name'),
      '#maxlength' => 60,
      '#size' => 30,
      '#default_value' => variable_get('anonymous', t('Anonymous')),
    );
    $form['mail'] = array(
      '#type' => 'textfield',
      '#title' => t('E-mail'),
      '#maxlength' => 64,
      '#size' => 30,
      '#description' => t('The content of this field is kept private and will not be shown publicly.'),
    );
    $form['homepage'] = array(
      '#type' => 'textfield',
      '#title' => t('Homepage'),
      '#maxlength' => 255,
      '#size' => 30,
    );
  }
  elseif ($comment_anonymous_forum == COMMENT_ANONYMOUS_MUST_CONTACT) {
    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => t('Your name'),
      '#maxlength' => 60,
      '#size' => 30,
      '#default_value' => variable_get('anonymous', t('Anonymous')),
      '#required' => TRUE,
    );
    $form['mail'] = array(
      '#type' => 'textfield',
      '#title' => t('E-mail'),
      '#maxlength' => 64,
      '#size' => 30,
      '#description' => t('The content of this field is kept private and will not be shown publicly.'),
      '#required' => TRUE,
    );
    $form['homepage'] = array(
      '#type' => 'textfield',
      '#title' => t('Homepage'),
      '#maxlength' => 255,
      '#size' => 30,
    );
  }
  if (variable_get('comment_subject_field_forum', 1) == 1) {
    $form['subject'] = array(
      '#type' => 'textfield',
      '#title' => t('Subject'),
      '#maxlength' => 64,
    );
  }
  $form['comment_filter']['comment'] = array(
    '#type' => 'textarea',
    '#title' => t('Comment'),
    '#rows' => 15,
    '#required' => TRUE,
  );
  $form['comment_filter']['format'] = filter_form(FILTER_FORMAT_DEFAULT);
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $user->uid,
  );
  $form['discussthis_nid'] = array(
    '#type' => 'value',
    '#value' => $nid,
  );

  // Only show save button if preview is optional or if we are in preview mode.
  // We show the save button in preview mode even if there are form errors so that
  // optional form elements (e.g., captcha) can be updated in preview mode.
  if ($is_ready || variable_get('comment_preview_forum', COMMENT_PREVIEW_REQUIRED) == COMMENT_PREVIEW_OPTIONAL) {
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save'),
      '#weight' => 19,
    );
    $form['#submit'][] = 'discussthis_create_form_submit';
  }
  $form['preview'] = array(
    '#type' => 'button',
    '#value' => t('Preview'),
    '#weight' => 20,
  );
  return $form;
}

/**
 * \brief Validate the comment the user just posted.
 *
 * This function validates the entered data. The comment module
 * expects a valid name, email address and URL.
 *
 * \param[in] $form The form
 * \param[in,out] $form_state The current state of the form
 */
function discussthis_create_form_validate($form, &$form_state) {
  comment_form_validate($form, $form_state);
}

/**
 * \brief Save the new comment.
 *
 * This function creates a new forum topic if this is the first
 * comment on this node. Otherwise, it just adds the comment to
 * the existing forum.
 *
 * \todo
 * We've got a problem here, if the administrator wants to moderate
 * these comments, then we should not create the forum node. This
 * is a similar problem to the one we see in creating the forum
 * node before the comment is even posted to us.
 *
 * I guess that the biggest problem is that a comment, to exist,
 * needs to be attached to a node.
 *
 * I see two potential solutions:
 *
 * 1) we handle our own comments
 *
 * 2) we create the comment on the node being discussed and move
 *    it later to the forum
 *
 * \param[in] $form The form info
 * \param[in] $form_state The current state of the form, including the values
 */
function discussthis_create_form_submit($form, &$form_state) {
  global $user;

  // we got a post, so we want to create the forum node
  // load the node being discussed
  $node = node_load($form_state['values']['discussthis_nid']);
  if (!$node) {

    // what shall we do here? the admin could delete the node while
    // a user is trying to discuss it...
    drupal_set_message(t('The node being discussed is not available anymore. (nid: @nid)', array(
      '@nid' => $form_state['values']['discussthis_nid'],
    )), 'error');
    drupal_goto('');
  }

  // TODO:

  //if (!user_access('post comments without approval')) {

  //  // The comment needs to be approved, save it in our discussthis_comment table
  //  // (this is annoying, but otherwise we get a post in the way?! maybe we could
  //  // have a flag in the settings of discussthis so users can choose how to handle
  //  // this case?)
  //  $sql = "INSERT INTO {discussthis_comment} (...) VALUES (...)";
  //  db_query($sql, ...);

  //}

  // some of the functions used to create a node require node pages include
  module_load_include('inc', 'node', 'node.pages');

  // The core defines a way to create a node which is to fill a form
  // and then call drupal_execute() from includes/form.inc.
  $sub_form_state = array();

  // get the author of the node and pass that to the token_replace() call
  $author = user_load(array(
    'uid' => $node->uid,
  ));

  // TITLE
  $sub_form_state['values']['title'] = token_replace(variable_get('discussthis_newsubject', '[node-title]'), 'discussthis', $node);
  if ($author) {
    $sub_form_state['values']['title'] = token_replace($sub_form_state['values']['title'], 'user', $author);
  }

  // BODY
  $default_body = DISCUSSTHIS_DEFAULT_NODE_MESSAGE;
  $sub_form_state['values']['body'] = token_replace(variable_get('discussthis_newtemplate', $default_body), 'discussthis', $node);
  if ($author) {
    $sub_form_state['values']['body'] = token_replace($sub_form_state['values']['body'], 'user', $author);
  }

  // FORMAT
  $sub_form_state['values']['format'] = NULL;

  // FORUM TERM
  $forum_vid = variable_get('forum_nav_vocabulary', 1);
  $vocabulary = taxonomy_vocabulary_load($forum_vid);
  $discussthis_forum = _discussthis_get_forum($node->nid, $node->type);
  $tid = $discussthis_forum['forum_tid'];
  if ($vocabulary->tags) {

    // tags are a special case, also you cannot choose tags for a forum...
    $sub_form_state['values']['taxonomy']['tags'][$vid] = taxonomy_implode_tags($tid, $forum_vid);
  }
  elseif ($vocabulary->multiple) {

    // forums should never use multiple either
    $sub_form_state['values']['taxonomy'][$forum_vid][$tid] = $tid;
  }
  else {
    $sub_form_state['values']['taxonomy'][$forum_vid] = $tid;
  }

  // LOG
  $sub_form_state['values']['log'] = t('Node automatically created by discussthis for comment @subject', array(
    '@subject' => $form_state['values']['subject'],
  ));

  // OPERATION
  $sub_form_state['values']['op'] = t('Save');

  // STATUS
  // Although it would be better to keep the status to the default of the forum
  // 1) the post here will include data that is under control (i.e. data from
  //    another node that was already agreed upon)
  // 2) the post needs to be published for a comment to be appended
  $sub_form_state['values']['status'] = 1;

  // Values that will get proper defaults automatically

  //$sub_form_state['values']['date'] = date('Y-m-d H:i:s O', time());

  // A special value to recognize this form in the discussthis_form_alter() function
  $sub_form_state['discussthis']['post'] = 1;

  // FORUM POST
  $topic = (object) array(
    'type' => 'forum',
  );

  // IMPORTANT NOTE:
  // This call generates a form for the current user, not the future user of
  // the new topic. This is an important distinction since some parameters
  // just cannot be passed to this function if we want it to work. For instance,
  // the format is set to default so it works. Any other format could fail and
  // the creation of the topic would fail. ("default" is available to all users!)
  drupal_execute('forum_node_form', $sub_form_state, $topic);
  $topic->nid = $sub_form_state['nid'];
  if (!$topic->nid) {

    // it did not work... hmmm...
    drupal_set_message('Forum post could not be created for your comment to be published.', 'error');
    drupal_goto();
  }

  // Now we can save the author in the new post as expected by the
  // administrator; when we try to set the UID in the node info
  // before calling drupal_execute() it is reset for security reason
  // and the result is that the UID is set to 0 (anonymous)
  //
  // load the user that was setup by the admin for this post,
  // if it fails, use the anonymous user
  $format = NULL;
  $author_name = variable_get('discussthis_author', $user->name);
  if (!$author_name || $user->name == $author_name) {
    $author = $user;
  }
  elseif ($author_name == '*') {
    $author = user_load(array(
      'uid' => $node->uid,
    ));

    // if we keep the same author, we can keep the same format
    // (although that author may not be authorized to use that
    // format anymore, we consider this node as safe at this
    // point...)
    $format = $node->format;
  }
  else {
    $author = user_load(array(
      'name' => $author_name,
    ));
  }
  if (!$author) {
    $author = array(
      'uid' => 0,
    );
  }
  $sql = "UPDATE {node} SET uid = %d WHERE nid = %d";
  db_query($sql, $author->uid, $topic->nid);

  // now that we have the right author, compute the right format
  // note that we leave the format alone if the offered format
  // is not otherwise allowed
  if (is_null($format)) {

    // get the format selected by the admin and verify its validity
    $format = (int) variable_get('discussthis_format_' . $node->type, 0);
    if ($format > 0) {
      $allowed = user_access('administer filters', $author);
      if (!$allowed) {

        // make sure the new author role has permission for this format
        $query = 'SELECT roles FROM {filter_formats} WHERE format = %d';
        $result = db_query($query, $format);
        $format_roles = db_fetch_array($result);
        $allowed = count(array_intersect($author->roles, explode(',', $format_roles))) > 0;
      }
      if ($allowed) {

        // allowed, make it the format for this node
        $sql = "UPDATE {node_revisions} SET format = %d" . " WHERE nid = %d" . " AND vid = (SELECT vid FROM {node} WHERE nid = %d)";
        db_query($sql, $format, $topic->nid, $topic->nid);
      }
    }
  }

  // now that we created the topic node, we want to link it to the
  // node being discussed ($topic and $node)
  //
  // here we use a LOCK to make sure we can create the new row all
  // alone, if the row actually exists, we actually had another user
  // create a post ahead of us! so we're going to add the comment
  // to that other node and we can delete the node we just created.
  // make sure we INSERT alone!
  db_lock_table('discussthis');
  $sql = "SELECT topic_nid FROM {discussthis} WHERE nid = %d";
  $other_topic_nid = db_result(db_query($sql, $node->nid));
  if (!$other_topic_nid) {

    // this is a new topic, we're the first to post!
    // so attach the new forum post to the node being discussed
    $sql = "INSERT INTO {discussthis} (nid, topic_nid) VALUES (%d, %d)";
    db_query($sql, $node->nid, $topic->nid);
  }

  // COMMIT the result, no insert if a topic existed anyway...
  db_unlock_tables();
  if ($other_topic_nid) {

    // What a waste! but I don't see any way around it.
    // Plus if you have signals on node creation, you'll get an email
    // and this deletes right after... so the email will point to
    // a non-existant node... Argh!
    node_delete($topic->nid);

    // hmmm... should we add an alias from $topic->nid to $topic_nid?!
    // we'll assume that this will be particularly rare!
    // So, the new topic nid is really the other topic nid...
    $topic->nid = $other_topic_nid;
  }

  // now we want to save the comment
  $form_state['values']['nid'] = $topic->nid;

  // before calling comment_save() they call this one:
  _comment_form_submit($form_state['values']);
  comment_form_submit($form, $form_state);
  $form_state['redirect'] = 'node/' . $topic->nid;

  // if you have boost, we want to reset the discussed node since
  // now it will look "different" (the link and eventually the new
  // comment)
  if (function_exists('boost_expire_node')) {
    boost_expire_node($node);
  }
}

/**
 * \brief Find forum topic attached to node.
 *
 * Lookup the given nid in the discussthis db table, and return the
 * corresponding forum topic nid, otherwise return the default for
 * this node type
 *
 * \param[in] $nid The node to be checked for a topic identifier.
 *
 * \return The topic nid or 0 if no topic is found
 */
function _discussthis_get_topic($nid) {
  $sql = 'SELECT topic_nid FROM {discussthis} WHERE nid = %d';
  $topic_nid = db_result(db_query($sql, $nid));
  return $topic_nid ? $topic_nid : 0;
}

/**
 * \brief Read the node specific forum settings.
 *
 * Load the Discuss This! data attached to the specified node.
 *
 * For new nodes, load the defaults as defined in the global
 * settings.
 *
 * \param[in] $nid The node identifier, or 0
 * \param[in] $type The type of node
 *
 * \return Array representing a discussthis_forums row
 */
function _discussthis_get_forum($nid, $type) {

  // try loading the data from the discussthis_forums table
  $sql = 'SELECT * FROM {discussthis_forums} WHERE nid = %d';
  $result = db_query($sql, $nid);
  $discussthis_forum = db_fetch_array($result);

  // create defaults if we cannot find data in the table
  if (!$discussthis_forum) {
    $discussthis_forum = array(
      'nid' => $nid,
      'forum_tid' => variable_get('discussthis_node_' . $type, 0),
    );
  }
  return $discussthis_forum;
}

/**
 * \brief Save a node to forum mapping.
 *
 * This function stores a mapping between the given node (nid)
 * and a forum (tid).
 *
 * \param[in] $discussthis_forum The array to save in the discussthis_forums table
 */
function _discussthis_set_forum($discussthis_forum) {
  db_lock_table('discussthis_forums');
  $sql = "DELETE FROM {discussthis_forums} WHERE nid = %d";
  db_query($sql, $discussthis_forum['nid']);
  $sql = "INSERT INTO {discussthis_forums} (nid, forum_tid)" . " VALUES (%d, %d)";
  db_query($sql, $discussthis_forum['nid'], $discussthis_forum['forum_tid']);
  db_unlock_tables();
}

// vim: ts=2 sw=2 et syntax=php

Functions

Namesort descending Description
discussthis_boost_is_cacheable \brief Avoid having the form cached.
discussthis_comment Implementation of hook_comment().
discussthis_comment_render \brief Render comments.
discussthis_create_form \brief Create a comment form.
discussthis_create_form_submit \brief Save the new comment.
discussthis_create_form_validate \brief Validate the comment the user just posted.
discussthis_form_alter \brief hook_form_alter implementation
discussthis_help Display help and module information
discussthis_init \brief Implementation of hook_init(). Add the CSS.
discussthis_link \brief Implementation of hook_link().
discussthis_menu \brief Implementation of hook_menu().
discussthis_new \brief User is creating a new comment.
discussthis_nodeapi \brief hook_nodeapi implementation
discussthis_perm \brief Implementation of hook_perm(). Permissions for this module
discussthis_rules_event_info Implementation of hook_rules_event_info().
discussthis_theme \brief Implementation of hook_theme().
discussthis_token_list \brief Implementation of hook_token_list()
discussthis_token_values \brief Implementation of hook_token_values()
_discussthis_get_forum \brief Read the node specific forum settings.
_discussthis_get_topic \brief Find forum topic attached to node.
_discussthis_set_forum \brief Save a node to forum mapping.

Constants