<?php
// $Id$

/**
 * @file
 * Provides common BOINC module functionality.
 *
 * In general, any custom feature or function required independently by
 * multiple BOINC modules should be in this module.
 */


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Includes that provide supporting functions
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

require_once('includes/boinctranslate.forms.inc');
require_once('includes/boinctranslate.helpers.inc');
require_once('boinctranslate.admin.inc');


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Hooks into core modules
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Implementation of hook_menu(); determine the actions that correspond
 * with defined URL paths
 */
function boinctranslate_menu() {
  $items['admin/boinc/translation'] = array(
    'title' => 'Environment: Translation',
    'description' => 'Configure URLs for translation files (i.e. integration
      with BOINC Translation Services).',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boinctranslate_admin_settings'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'boinctranslate.admin.inc'
  );
  $items['admin/boinc/translation/export'] = array(
    'title' => 'Export translations',
    'page callback' => 'boinctranslate_export_translations',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/boinc/translation/import'] = array(
    'title' => 'Import translations',
    'page callback' => 'boinctranslate_refresh_translations',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/boinc/translation/initialize-languages'] = array(
    'title' => 'Install official BOINC languages',
    'page callback' => 'boinctranslate_initialize_languages',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/boinc/translation/update-official-boinc'] = array(
    'title' => 'Update official BOINC translations',
    'page callback' => 'boinctranslate_update_official_boinc_translations',
    'access arguments' => array('update official BOINC translations'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/boinc/translation/download-pot'] = array(
    'title' => 'Download translation template',
    'page callback' => 'boinctranslate_download_pot',
    'access arguments' => array('administer site configuration'),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_nodeapi(); add custom actions to node operations
 * Obsolete in Drupal 7...
 */
function boinctranslate_nodeapi(&$node, $op, $a3 = null, $a4 = null) {
  // In Drupal 7, these operation cases will all exist as their own hooks,
  // so let's approximate that here so that this function can simply be removed
  // upon migration to 7
  switch($op) {
  case 'update':
    boinctranslate_node_update($node);
    break;
  case 'view':
    global $language;
    boinctranslate_node_view($node, 'full', $language->language);
    break;
  default:
  }
}

/**
 * Implementation of hook_node_update(); add custom actions when a node
 * is updated (forward compatible to Drupal 7)
 */
function boinctranslate_node_update($node) {
  // Only process nodes of certain types as defined by the individual
  // node's setting for field_boinctranslate_transifex.
  if ( ($node->field_boinctranslate_transifex) and ($node->field_boinctranslate_transifex[0]['value']=='1') ) {
    // Add page content to translation table.
    $textgroup = 'project';
    $location = "node:{$node->nid}:body";

    // Query database for lid and location (hash) of this node.
    $sql1 = "SELECT lid,location FROM locales_source
             WHERE location REGEXP '%s' AND textgroup = '%s' ORDER BY lid ASC";

    $myregexp = "node:{$node->nid}:body";
    $dbresult = db_query($sql1, $myregexp, $textgroup);
    // Table of lid and hashes
    $nodehashtable = array();
    while ($row = db_fetch_array($dbresult)) {
      $locsplit = explode(":", $row['location']);
      // Look for hash in the fourth position.
      if (isset($locsplit[3])) {
        $nodehashtable[$row['lid']] = $locsplit[3];
      }
    }// while

    // Break node body into chunks.
    $chunks = boinctranslate_splitfortranslation($node->body);
    foreach ($chunks as $idx => $chunk) {

      // Only process this chunk if there is a hash associated with it.
      if ($chunk['hash']) {
        $chunkloc = $location . ":{$chunk['hash']}";

        // If the hash for this chunk is in the hash table, logically
        // the chunk is already in the database locales_source table. As
        // a result remove the row from the nodehashtable array.
        // Otherwise, add the chunk to the database.
        $lidkey = array_search($chunk['hash'], $nodehashtable);
        if ($lidkey) {
          unset($nodehashtable[$lidkey]);
        }
        else {

          $result = db_query("
            INSERT INTO {locales_source}
            SET location = '%s', textgroup = '%s', source = '%s'",
            $chunkloc, $textgroup, $chunk['content']
          );
          if ($result) {
            watchdog(
              'boinctranslate',
              'Added translation source strings for node @nid chunk @chunk.',
              array('@nid' => $node->nid, '@chunk' => $idx)
           );
          }
          else {
            drupal_set_message(
              t('ERROR: Unable to add translation source strings.'), 'error'
            );
            watchdog(
              'boinctranslate',
              'Unable to add translation source strings for node @nid chunk @chunk.',
              array('@nid' => $node->nid, '@chunk' => $idx),
              WATCHDOG_ERROR
            );
          }// if $result
        }// if lidkey

      }// if chunk['hash']
    }// foreach chunks

    // Any remaining hashes in the node hash table are deleted.
    foreach ($nodehashtable as $lid => $hash) {
      db_query('DELETE FROM {locales_source} WHERE lid = %d', $lid);
      db_query('DELETE FROM {locales_target} WHERE lid = %d', $lid);
      cache_clear_all('locale:', 'cache', TRUE);
    }
  }// if type
}

/**
 * Implementation of hook_node_view(); add custom actions when a node
 * is viewed (forward compatible to Drupal 7)
 */
function boinctranslate_node_view($node, $view_mode, $langcode) {
  switch($node->type) {
  case 'page':
    break;
  default:
  }
}


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Implementation of hook_filter() and associated functions
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * boinctranslate_filter_tips
 */
function boinctranslate_filter_tips($delta, $format, $long = FALSE) {
  if ($long) {
    return _boinctranslate_filter_tips_long();
  } else {
    $output = t('You may use !token (opens in new window) as a delimiter to split the translation into different chunks. See the link for a more detailed guide.',
      array(
        '!token' =>  l('#SPLIT_TOKEN#', "filter/tips/$format",
        array(
          'fragment' => 'filter-boinctranslate-' . $delta,
          'attributes' =>
            array(
              'target'=>'_blank',
              'rel'=>'noopener noreferrer',
            ),
        ))
      ));
    //$output = 'Help with BOINC Translate input filter.';
    return $output;
  }
}

/**
 * boinctranslate_filter
 */
function boinctranslate_filter($op, $delta = 0, $format = -1, $text = '', $cache_id = 0) {
  switch ($op) {
  case 'list':
    return array(0 => bts('BOINC translation filter to help split content into chunks for translations.', array(), NULL, 'boinc:adminpage-boinctranslate-inputfilter'));

  case 'description':
    return bts('Splits node content into chunks for translation.', array(), NULL, 'boinc:input-filter-help');

  case 'process':
    /* Only process nodes of certain types. On an individual
     * node-by-node level, content editors may choose to select
     * "Export for translation", which will allow this specific node
     * to be translated. By default this is enabled.
     */
    if ($node = menu_get_object()) {
      $mynid = $node->nid;
    } else {
      return $text;
    }

    if ( ($node->field_boinctranslate_transifex) and ($node->field_boinctranslate_transifex[0]['value']=='1') ) {
      if (variable_get("boinctranslate_filter_debug_$format", 0)) {
        $timing_start = explode(' ', microtime());
        $ret = _boinctranslate_filter_process($text, $format, $mynid);
        $timing_stop = explode(' ', microtime());
        $elapsed = $timing_stop[1] - $timing_start[1];
        $elapsed += $timing_stop[0] - $timing_start[0];
        $mess = 'DEBUG ' . l('BOINC translate', "filter/tips/$format") .' parsed on '.date('r').'<br />Execution time: '.$elapsed.' seconds.';
        drupal_set_message($mess, 'warning');
        return $ret;
      }
      else {
        return _boinctranslate_filter_process($text, $format, $mynid);
      }// if debug
    }
    else {
      return $text;
    }// if type
    break;

  case 'settings':
    return boinctranslate_filter_settings_form($format);

  default:
    return $text;
  }
}

/**
 * Implementation of hook_panels_pane_content_alter()
 * This workaround makes panel page titles and pane titles translatable
 */
function boinctranslate_panels_pane_content_alter($content, $pane, $args, $context) {
  if ($content->title) {
    $content->title = t($content->title);
  }
  return $content;
}

/**
 * Implementation of hook_perm()
 */
function boinctranslate_perm() {
  return array('update official BOINC translations');
}

/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Helper functions
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Splits a string, and then hashes the resulting chunks. Returns an
 * associative array of the chunks and hashes.
 *
 * The individual chunks are stripped of <p> tags and then cleaned. The
 * resulting text is hashed. The 'content' that is saved for translation
 * is the original text with the tags.
 */

/**
 * Splits an input string using the boinc translate defined token.
 *
 * @param string $instring
 *   The input string to find.
 */
function boinctranslate_splitfortranslation($instring) {
  $parts = preg_split('/(#SPLIT_TOKEN#)|<p>(#SPLIT_TOKEN#)<\/p>/', $instring);
  $chunks = array();
  foreach ($parts as $key => $value) {
    if (preg_match('/(#SPLIT_TOKEN#)/', $value)) {
      $chunks[] = array ('delimiter' => $value,);
    }
    else {
      $cleaned = preg_replace('/\s|\n/', '', $value);
      if (!empty($cleaned)) {
        $chunks[] = array(
          'content' => $value,
          'hash' => hash('sha1', $cleaned),
        );
      }// !empty cleaned
    }// preg_match
  }// foreach part
  return $chunks;
}


/**
 * Helper function to actually process the input text. Splits the text
 * into chunks. Each chunk has a hash. Use bts() to "lookup" that hash
 * in the database to obtain the translation.
 *
 * @param string $text
 *   The input text to filter.
 * @param int $format
 *   A format type, currently not used.
 * @param int $nid
 *   The node id, from the node the text is from. Used in
 *   location/context string.
 */
function _boinctranslate_filter_process(&$text, $format = -1, $nid) {
  $newbody = '';
  // Break node body into chunks.
  $chunks = boinctranslate_splitfortranslation($text);
  foreach ($chunks as $chunk) {
    $location = "project:node:${nid}:body";
    // Only add this chunk to the content if it has a hash, i.e., it
    // is not a delimiter.
    if ($chunk['hash']) {
      $location .= ":{$chunk['hash']}";
      $newbody .= bts($chunk['content'], array(), $langcode, $location, FALSE);
    }
  }
  return $newbody;
}


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Implementation of hooks for l10n_update
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */
function boinctranslate_l10n_update_projects_alter(&$projects) {
  foreach ($projects as &$proj) {
    $proj['info']['l10n path'] = 'https://ftp-origin.drupal.org/files/translations/%core/%project/%project-%release.%language.po';
  }
}
