<?php
// $Id$

/**
 * @file
 * Admin module used to check mails on broken links before they are sent.
 *
 * @todo multilanguage support
 * @todo simplenews D7 does not support html mails
 * @todo comment form issues in node body (links to submit form - disable comment needed)
 * @todo cleaner body creation process (separate function to build content)
 * @todo render newsletter preview EXACTLY like in newsletter
 */

/**
 * Implements hook_form_FORMID_alter().
 * 
 * FAPI form: simplenews_node_tab_send_form
 * This function alters the simplenews_node_tab_send_form form in order
 * to attach the linkchecker result table.
 */
function simplenews_linkchecker_form_simplenews_node_tab_send_form_alter(&$form, &$form_state) {
  $node = $form_state['build_info']['args'][0];
  $links = simplenews_linkchecker_check_node($node);

  if (count($links) > 0) {

    if ($node->simplenews->status >= 1) {
      unset($form['simplenews']['none']);
      $form['simplenews']['sent'] = array(
        '#markup' => t('This newsletter has already been sent.'),
        '#weight' => 0,
      );
    }
         
    $form['simplenews']['links'] = array(
      '#type' => 'fieldset',
      '#title' => t('Email links'),
      '#weight' => 2,
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );

    $form['simplenews']['links']['mail_links'] = array(
      '#markup' => theme('linkcheck_result', array('uris' => $links)),
    );

    $broke_links = simplenews_linkchecker_check_broke_links($links);

    $form['broke_links'] = array(
      '#type' => 'hidden',
      '#value' => $broke_links,
    );

    if ($broke_links && $node->simplenews->status < 1) {
      $form['simplenews']['links']['areusure'] = array(
        '#type' => 'checkbox',
        '#title' => 'Ignore broken links',
        '#description' => 'There are broken links in the mail you are about to send. Do you really want to send the mail as it is?',
        '#default_value' => 0,
        '#weight' => 2,
      );

      // add validate handler
      $form['#validate'][] = 'simplenews_linkchecker_check_send_validate';
    }

    drupal_add_css(drupal_get_path('module', 'simplenews_linkchecker') . '/linkstable.css');
  }
}

/**
 * Implements hook_theme().
 * 
 * Register the theme function that renders a table which
 * will hold the linkcheck results.
 */
function simplenews_linkchecker_theme($existing, $type, $theme, $path) {
  return array(
    'linkcheck_result' => array(
      'variables' => array('uris' => NULL),
    ),
  );
}

/**
 * Utility function used to check an array of analyzed links
 * for broken links.
 */
function simplenews_linkchecker_check_broke_links($links) {
  $broke = 0;
  foreach ($links as $link) {
    if ($link['type'] != 'absolute') {
      $broke = 1;
      break;
    }
  }
  return $broke;
}

/**
 * Validation handler for the "Ignore broken links" checkbox.
 * If there are broken links the user will have to check this
 * in order to send his mail still containing the broken links.
 */
function simplenews_linkchecker_check_send_validate(&$form, &$form_state) {
  if ($form_state['values']['simplenews']['links']['areusure'] == 0) {
    form_set_error('simplenews][links][areusure', t('You are about to send a newsletter that contains broken links. Please review your links and correct them before you send the newsletter.'));
  }
}

/**
 * Extracts links and checks them
 */
function simplenews_linkchecker_check_node($node) {

  if ($original_session = drupal_save_session()) {
    drupal_save_session(FALSE);
  }

  // Force the current user to anonymous to ensure consistent permissions.
  $original_user = $GLOBALS['user'];
  $GLOBALS['user'] = drupal_anonymous_user();

  $mailtext = node_view($node, 'email_html');
  $mailtext = drupal_render($mailtext);

  $GLOBALS['user'] = $original_user;
  if ($original_session) {
    drupal_save_session(TRUE);
  }

  $links = array();

  // parse the mail text
  $doc = new DOMDocument('1.0', 'iso-8859-1');
  // suppresses possible error messages using @ if a provided HTML
  // string isn't valid 
  @$doc->loadHTML($mailtext);
  $xpath = new DOMXPath($doc);

  // select all href attributes of a tags in the mail body
  $query = '//@href';
  $hrefs = $xpath->evaluate($query);

  foreach ($hrefs as $href) {
    $link = array(
      'element' => '&lt;' . $href->ownerElement->tagName . '&gt;',
      'attribute' => $href->name,
      'path' => $href->value,
      'type' => simplenews_linkchecker_get_type($href->value),
    );
    /*
    // status will only be determined if the checker module is installed
    if (module_exists('checker_module')) {
      $link['status'] = simplenews_linkchecker_check_link($href->value);
    }
    */
    $links[] = $link;
  }

  // select all src attributes of img tags in the mail body
  $query = '//@src';
  $srcs = $xpath->evaluate($query);

  foreach ($srcs as $src) {
    $link = array(
      'element' => '&lt;' . $src->ownerElement->tagName . '&gt;',
      'attribute' => $src->name,
      'path' => $src->value,
      'type' => simplenews_linkchecker_get_type($src->value),
    );
    /*
    // status will only be determined if the checker module is installed
    if (module_exists('checker_module')) {
      $link['status'] = simplenews_linkchecker_check_link($src->value);
    }
    */
    $links[] = $link;
  }

  asort($links);
  return $links;
}

/**
 * Function prints the table which holds the link information
 */
function theme_linkcheck_result($vars) {

  $uris = $vars['uris'];
  $headers = array('element', 'attribute', 'uri', 'type');
  /* // status will only be determined if the checker module is installed
  if (module_exists('checker_module')) {
    $headers[] = 'status';
  }
  */

  $html = '<h6><b>Maillinks</b></h6>';
  $html .= '<p>Those Links (external references) have been extracted from the resulting mail body. ';
  $html .= 'It is crucial that all links appear in absolute form, since all other references will ';
  $html .= 'be broken on receipt.</p>';

  $html .= '<table id="maillinks"><thead><tr>';

  foreach ($headers as $title) {
    $html .= "<th>$title</th>";
  }

  $html .= '</tr></thead>';
  $html .= '<tbody>';

  foreach ($uris as $element) {
    $status_class = ($element['type'] == 'absolute') ? 'alive' : 'broke' ;
    $html .= "<tr class='$status_class'>";
    foreach ($element as $key => $val) {
      $html .= "<td>$val</td>";
    }
    $html .= '</tr>';
  }
  $html .= '</tbody></table>';

  return $html;
}

/**
 * Function determines the type of a link
 *
 * As of RFC1630 checking validity of an URI is not simple.
 * We simply check most common specialcases and leave validation of other cases to the user.
 */
function simplenews_linkchecker_get_type($uri) {
  // check if it's an absolute uri
  if (substr_count($uri, '://') > 0
    || drupal_substr($uri, 0, 7) == 'mailto:') {
    return 'absolute';
  }
  else {
    return 'relative';
  }
}

/**
 * Function checks if a link is alive
 */
function simplenews_linkchecker_check_link($uri) {
  // TODO: check if a link is alive using CURL or the linkchecker module
  return 'alive';
}
