<?php
/**
 * @file
 * Enables comments to be nodes if needed.
 */

require_once(dirname(__FILE__) . '/includes/nodecomment.common.inc');
require_once(dirname(__FILE__) . '/includes/nodecomment.forms.inc');
require_once(dirname(__FILE__) . '/includes/nodecomment.theme.inc');

/**
 * Implementation of hook_nodeapi().
 */
function nodecomment_nodeapi(&$node, $op, $arg = 0, $page = 0) {
  switch ($op) {
    case 'load':
      return _nodecomment_nodeapi_load($node, $op, $arg, $page);

    case 'presave':
      // Restore comment setting so that we don't disable commenting
      // accidentally.
      if (isset($node->node_comment)) {
        // Allow changing of comment setting during node form submission.
        if (!isset($node->nodecomment_real_node_form)) {
          $node->comment = $node->node_comment;
        }
      }
      break;

    case 'insert':
    case 'update':
      // If this is a comment, save comment data.
      if (isset($node->comment_target_nid)) {
        nodecomment_save($node);
      }
      break;

    case 'delete':
      // If this node has own comments, delete them.
      // For increased durability, don't add any checks here: it should help to
      // deal with the orphan problem.
      _nodecomment_delete_comments($node->nid);

      // If this is a comment, delete it and it's children comments from the thread.
      if (isset($node->comment_target_nid)) {
        _nodecomment_thread_delete_children($node->nid, $node->comment_target_nid);

        // For increased durability, delete node_comments entries one by one,
        // together with their nodes, even when mass deleting.
        db_query('DELETE FROM {node_comments} WHERE cid = %d', $node->nid);

        _nodecomment_update_node_statistics($node->comment_target_nid);
      }
      break;

    case 'view':
      // If this is a comment.
      if ($page && isset($node->comment_target_nid)) {
        // Redirect to target node, if needed.
        // We could do it inside hook_init() but then we would add 1 query for
        // every node view, which is a tax it's better not to pay.
        if (variable_get('node_comment_node_redirect', TRUE)) {
          if (!nodecomment_is_content($node->type)) {
            // This is a pure comment. Pure comments don't have own pages.
            // Redirect this to the node page the comment is displayed on.
            _nodecomment_target_node_redirect($node);
          }
        }
        // Adjust the breadcrumb trail to include target node.
        $target_node = node_load($node->comment_target_nid);
        nodecomment_set_breadcrumb($target_node);
      }
      break;
  }
}

function _nodecomment_nodeapi_load($node, $op, $arg, $page) {
  // We want to process 3 cases:
  // - node which is a node comment
  // - node which has node comments
  // - both
  $comment_types = nodecomment_get_comment_types();
  $node->comment_type = nodecomment_get_comment_type($node->type);
  $comment_data = array();

  // Is this a comment type ?
  if (in_array($node->type, $comment_types)) {
    $query = "SELECT nc.nid AS comment_target_nid, nc.pid AS comment_target_cid,
              nc.hostname, nc.thread, nc.name, nc.uid, nc.mail, nc.homepage,
              u.signature, u.signature_format
              FROM {node_comments} nc
              INNER JOIN {users} u ON nc.uid = u.uid
              WHERE nc.cid = %d";
    $comment_data = db_fetch_array(db_query($query, $node->nid));
    if ($comment_data) {
      // It's a node comment! Populate commenty stuff.
      // Don't let the "name" field in the comment overwrite a username.
      if (!empty($node->uid)) {
        unset($comment_data['name']);
      }
      // Add the user signature like comment.module does
      if (variable_get('user_signatures', 0) && !empty($comment_data['signature'])) {
        $comment_data['signature'] = check_markup($comment_data['signature'], $comment_data['signature_format'], FALSE);
      }
      else {
        $comment_data['signature'] = '';
      }
    }
    else {
      // Node type is a comment, but node_comments table entry is missing.
      watchdog(
        'nodecomment',
        'Node @nid is configured as comment, but the comment data is missing.',
        array('@nid' => $node->nid),
        WATCHDOG_ERROR
      );
    }
  }

  // Does this node have node comments ?
  if ($node->comment_type) {
    // Move $node->comment to $node->node_comment and set $node->comment
    // to disabled to prevent core comment module messing with the node.
    // In presave nodeapi operation restore this setting.
    // In 3.x branch this is the only hack we do with core comment module.
    $node->node_comment = $node->comment;
    $node->comment = COMMENT_NODE_DISABLED;
  }

  return $comment_data;
}

/**
 * Implementation of hook_user().
 */
function nodecomment_user($type, $edit, &$user, $category = NULL) {
  if ($type == 'delete') {
    // 'node_comment_statistics' table is updated by comment module.
    db_query('UPDATE {node_comments} SET uid = 0 WHERE uid = %d', $user->uid);
  }
}

/**
 * Implementation of hook_link().
 */
function nodecomment_link($type, $node = NULL, $teaser = FALSE) {
  global $user;
  $links = array();

  if ($type != 'node') {
    return;
  }

  if (isset($node->comment_target_nid)) {
    // This node is a comment to a parent node.
    _nodecomment_comment_links($links, $node, $teaser);
  }

  if (!empty($node->comment_type)) {
    // This node can have node comments, read only or writable.
    _nodecomment_node_links($links, $node, $teaser);
  }

  return $links;
}

function _nodecomment_comment_links(&$links, &$node, $teaser) {
  // This really looks like a "security by obscurity" anti-pattern: user with
  // edit permission can still edit comment even while the link is hidden.
  // But the core comment does the same.
  // Fixing this properly will require an advanced node access module.
  $target_node = node_load($node->comment_target_nid);
  if ($target_node && $target_node->status == 1 && nodecomment_is_readwrite($target_node)) {
    if (node_access('update', $node)) {
      $links['comment_edit'] = array(
        'title' => t('edit'),
        'href' => 'node/'. $node->nid .'/edit',
        'query' => drupal_get_destination(),
      );
    }
    if (node_access('delete', $node)) {
      $links['comment_delete'] = array(
        'title' => t('delete'),
        'href' => 'node/'. $node->nid .'/delete',
        'query' => drupal_get_destination(),
      );
    }
    // Show comment reply links in threaded mode. In flat mode we only
    // hide the link: separate comment reply pages are always accessible.
    $mode = _comment_get_display_setting('mode', $node);
    $flat = in_array($mode, array(COMMENT_MODE_FLAT_COLLAPSED, COMMENT_MODE_FLAT_EXPANDED));
    if (!$flat && node_access('create', $node)) {
      $links['comment_reply'] = array(
        'title' => t('reply'),
        'href' => 'node/add/'. str_replace('_', '-', $node->type) .'/'. $node->comment_target_nid .'/'. $node->nid,
      );
    }
  }

  return $links;
}

function _nodecomment_node_links(&$links, &$node, $teaser) {
  global $user;

  $comment_read = nodecomment_get_commentable($node);
  if ($comment_read && $teaser && user_access('access comments')) {
    // Main page: display the number of comments that have been posted.
    $all = comment_num_all($node->nid);
    if ($all) {
      $links['comment_comments'] = array(
        'title' => theme('nodecomment_comment_count', $all, $node->comment_type),
        'href' => "node/$node->nid",
        'attributes' => array('title' => t('Jump to the first comment of this posting.')),
        'fragment' => 'comments'
      );

      $new = nodecomment_num_new($node->nid);
      if ($new) {
        $links['comment_new_comments'] = array(
          'title' => theme('nodecomment_new_comment_count', $new, $node->comment_type),
          'href' => "node/$node->nid",
          'query' => nodecomment_new_page_count($all, $new, $node),
          'attributes' => array('title' => t('Jump to the first new comment of this posting.')),
          'fragment' => 'new'
        );
      }
    }
  }

  // This node needs an Add new comment link.
  // Note that the add comment link is shown on full node view no matter
  // where the comment form is located, which differs from core's
  // comment.module. See http://drupal.org/node/480282.
  if (nodecomment_is_readwrite($node)) {
    $comment_type = nodecomment_get_comment_type($node->type);
    // Can the current user create the node comment ?
    if (user_access("create $comment_type content")) {
      // Is num of comments link present ?
      if (!isset($links['comment_comments'])) {
        $links['comment_add'] = array(
          'title' => t('Add new @comment_type', array('@comment_type' => node_get_types('name', $comment_type))),
          'attributes' => array('title' => t('Add a new comment to this page.')),
        );
        if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == 1) {
          $links['comment_add']['href'] = "node/$node->nid";
          $links['comment_add']['fragment'] = 'node-form';
        }
        else {
          $links['comment_add']['href'] = "node/add/". str_replace('_', '-', $comment_type) ."/". $node->nid;
        }
      }
    }
    // The user can't create the comment nodetype.
    elseif ($user->uid == 0) {
      // Show anonymous users the chance to login or register
      // We cannot use drupal_get_destination() because these links sometimes
      // appear on /node and taxonomy listing pages.
      if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
        $destination = 'destination='. drupal_urlencode('node/add/'. str_replace('_', '-', $comment_type) .'/'. $node->nid);
      }
      else {
        $destination = 'destination='. drupal_urlencode('node/'. $node->nid .'#nodecomment_form');
      }

      $links['login_register']['html'] = TRUE;
      if (variable_get('user_register', 1)) {
        $links['login_register']['title'] = t(
          '<a href="@login">Login</a> or <a href="@register">register</a> to post @comments',
          array(
            '@login' => url('user/login', array('query' => $destination)),
            '@register' => url('user/register', array('query' => $destination)),
            '@comments' => variable_get('node_comment_plural_'. $node->type, 'comments')
          )
        );
      }
      else {
        $links['login_register']['title'] = t(
          '<a href="@login">Login</a> to post @comments',
          array(
            '@login' => url('user/login', array('query' => $destination)),
            '@comments' => variable_get('node_comment_plural_'. $node->type, 'comments')
          )
        );
      }
    }
  }
}

/**
 * Implementation of hook_link_alter().
 */
function nodecomment_link_alter(&$links, $node) {
  // Ensure comment links go before "read more" one. Other modules can still
  // add links and break the order, but its the best we can do.
  if (isset($links['node_read_more'])) {
    $readmore = $links['node_read_more'];
    unset($links['node_read_more']);
    $links['node_read_more'] = $readmore;
  }
}

/**
 * Implementation of hook_menu().
 */
function nodecomment_menu() {
  $items = array();
  $items['admin/settings/nodecomment'] = array(
    'title' => 'Node Comments',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('nodecomment_admin_settings_form'),
    'access arguments' => array('administer content types'),
    'file' => 'includes/nodecomment.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_menu_alter()
 *
 * Alter the node view page to come to us instead. Don't do this if
 * another module has already done so, or if delegator module is enabled.
 */
function nodecomment_menu_alter(&$items) {
  // Override the node view handler for our purpose.
  if ((!module_exists('page_manager') ||
      variable_get('page_manager_node_view_disabled', TRUE)) &&
      $items['node/%node']['page callback'] == 'node_page_view') {

    $items['node/%node']['page callback'] = 'nodecomment_node_view';
  }

  $comment_types = nodecomment_get_comment_types();
  foreach ($comment_types as $type) {
    $path = "node/add/". str_replace('_', '-', $type);
    if (isset($items[$path])) {
      // Attach custom access callback that ensures proper access denied page
      // when needed.
      $items[$path]['access callback'] = '_nodecomment_node_add_access';
      // Make all our comment types not visible to node/add.
      $items[$path]['type'] = MENU_CALLBACK;
    }
  }
}

/**
 * Menu access callback for node/add pages of node comment types.
 */
function _nodecomment_node_add_access($op, $type) {
  // Note: menu callbacks receive proper type with underscores instead of
  // hyphens because Node module creates menu items with predefined
  // callback arguments rather than lets callback fetch argument from the url.

  // Don't allow to add nodecomments without comment context.
  //
  // TODO: we may want to allow this later, if we want to add comments
  // that are "content" by our terms without comment context.
  // But before doing so, we need to be sure that our nodeapi & nodecomment
  // logics can handle that.
  if (!is_numeric(arg(3))) {
    return FALSE;
  }

  // Check basic create permission before performing more heavy checks.
  if (!node_access('create', $type)) {
    return FALSE;
  }

  $target_node = node_load(arg(3));

  // Verify that target node being commented on exists and can be commented.
  if (!$target_node || !nodecomment_is_readwrite($target_node)) {
    return FALSE;
  }

  // Check that we are trying to add a proper node comment type.
  $comment_type = nodecomment_get_comment_type($target_node->type);
  if (!$comment_type || $type != $comment_type) {
    return FALSE;
  }

  // If we are replying to a nodecomment, check it's validity.
  if (is_numeric(arg(4))) {
    $target_comment = node_load(arg(4));
    // Target node should be a comment and should belong to the same thread.
    if (!$target_comment ||
        !isset($target_comment->comment_target_nid) ||
        $target_comment->comment_target_nid != $target_node->nid) {

      return FALSE;
    }
  }

  return TRUE;
}

/**
 * Ask Page Manager to use our version of the node page view instead of
 * Drupal's when falling back.
 */
function nodecomment_page_manager_override($type) {
  if ($type == 'node_view') {
    return 'nodecomment_node_view';
  }
}

/**
 * Implementation of hook_views_pre_build()
 */
function nodecomment_views_pre_build(&$view) {
  if ($view->display[$view->current_display]->display_plugin == 'nodecomment_comments') {
    $view->display_handler->pre_build();
  }
}

/**
 * Implementation of hook_node_type().
 *
 * Update nodecomment variables when node type information changes.
 */
function nodecomment_node_type($op, $info) {
  if ($op == 'delete') {
    $vars = _nodecomment_type_vars($info->type);
    foreach ($vars as $var) {
      variable_del($var);
    }
  }
  else if ($op == 'update' && !empty($info->old_type) && $info->type != $info->old_type) {
    $vars = _nodecomment_type_vars($info->old_type);
    foreach ($vars as $var) {
      variable_del($var);
    }
  }
}

/**
 * Implementation of hook_features_pipe_COMPONENT_alter().
 */
function nodecomment_features_pipe_node_alter(&$pipe, $data, $export) {
  if (!empty($data)) {
    foreach ($data as $node_type) {
      $variables = _nodecomment_type_vars($node_type);
      foreach ($variables as $variable_name) {
        $pipe['variable'][] = $variable_name;
      }
    }
  }
}

/**
* Implementation of hook_ctools_plugin_directory().
*/
function nodecomment_ctools_plugin_directory($module, $plugin) {
  if ($module == 'ctools') {
    return 'plugins/' . $plugin;
  }
}

/**
 * Implementation of hook_views_api().
 */
function nodecomment_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'nodecomment') .'/views',
  );
}
