You are here

persiantools.module in PersianTools 7

Same filename and directory in other branches
  1. 8 persiantools.module

Adds common features and fixes for persian pages.

File

persiantools.module
View source
<?php

/**
 * @file
 * Adds common features and fixes for persian pages.
 */

/**
 * Canstants Definition
 */
class PERSIANTOOLS_CONST {

  // Statment direction
  const RTL = 0;
  const LTR = 1;

  // Persian digits
  public static $FA_DIGITS = array(
    '۰',
    '۱',
    '۲',
    '۳',
    '۴',
    '۵',
    '۶',
    '۷',
    '۸',
    '۹',
  );

  // Characters type
  const UN = 0;

  // UNknow
  const FA = 1;
  const EN = 2;
  const OPENING = 3;
  const CLOSING = 4;
  const DIGIT = 5;
  const EOS = 6;

  // End Of Statment
  const SLASH = 7;
  const WS = 8;

  // White Space
  public static $OPENING_SYMS = array(
    '(',
    '{',
    '[',
    '"',
    '\'',
  );
  public static $STATMENT_END = array(
    '.',
    '!',
    ';',
    '?',
  );

}

/**
 * Implements hook_menu().
 */
function persiantools_menu() {
  $items['admin/config/persiantools'] = array(
    'title' => 'Persian Tools',
    'description' => 'Implements common features and missing fixes for the Perisan language.',
    'position' => 'right',
    'weight' => -5,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/config/persiantools/settings'] = array(
    'title' => 'Persian Tools Settings',
    'description' => 'PersianTools features can be managed here!',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'persiantools_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'persiantools.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_preprocess_html().
 */
function persiantools_preprocess_html(&$variables) {
  persiantools_convert_data($variables['page']['#children']);
}
function persiantools_convert_data(&$data) {
  global $language;
  $lang = $language->language;
  if ($lang != 'fa' && $lang != 'ar') {
    return;
  }
  $skip_tags = '(<(textarea|script|style)( [^>]*)?>(.*?)<\\/\\3>\\s*)';
  $normal_tags = '(<\\/?[^>]+>\\s*)?([^<]*)(?=<)';
  $chained_tags = '/(?s)(' . $skip_tags . '|' . $normal_tags . ')([^<]*)(?=<)/';
  preg_match_all($chained_tags, $data, $matches);
  $new_data = '';
  for ($i = 0; $i < count($matches[0]); $i++) {
    if (strlen($matches[7][$i]) + strlen($matches[8][$i]) > 0) {
      $matches[7][$i] = persiantools_convert_sm($matches[7][$i] . $matches[8][$i]);
    }
    $new_data .= $matches[2][$i] . $matches[6][$i] . $matches[7][$i];
  }
  $data = $new_data;
}

/**
 * Implements hook_ajax_render_alter().
 */
function persiantools_ajax_render_alter(&$commands) {
  foreach ($commands as &$item) {
    if (array_key_exists('command', $item)) {
      if ($item['command'] == "insert") {
        if (array_key_exists('data', $item)) {
          persiantools_convert_data($item['data']);
        }
      }
    }
  }
}

/**
 * Main function for multiple features and fixes of persiantools module.
 */
function persiantools_convert_sm($str) {
  $is_all_en = TRUE;
  $any_en = FALSE;
  $digit_method = variable_get('persiantools_digit_method');
  $rtl_ltr_fix = variable_get('persiantools_rtl_ltr_fix');
  $closing_ch = '\\0';
  $paren_state = 0;
  $dir = PERSIANTOOLS_CONST::RTL;
  $len = drupal_strlen($str);
  for ($i = 0; $i < $len; $i = $i + 1) {
    $ch = drupal_substr($str, $i, 1);

    // Skip unicode characters, which might be mistaken for english characters
    // (e.g. &nbsp;).
    if ($ch == '&') {
      for ($j = $i; $j < $len; $j = $j + 1) {
        if (drupal_substr($str, $j, 1) == ';') {
          $i = $j;
          continue 2;
        }
        elseif ($j > $i + 9) {
          break;
        }
      }
    }
    $type = persiantools_get_char_type($ch);
    if ($type == PERSIANTOOLS_CONST::FA) {
      $is_all_en = FALSE;
    }
    elseif ($ch == $closing_ch) {
      $type = PERSIANTOOLS_CONST::CLOSING;
    }
    elseif ($type == PERSIANTOOLS_CONST::UN) {

      // Last char should go through anyway, to wrap things up.
      if ($i != $len - 1) {
        continue;
      }
    }
    if ($rtl_ltr_fix) {
      list($str, $changed) = persiantools_fix_mixed_path($str, $ch, $type, $i, $len);
      if ($changed) {
        $i += 5;
        $len += 5;
      }
    }
    switch ($type) {
      case PERSIANTOOLS_CONST::DIGIT:
        if ($digit_method == 'full' || $digit_method == 'smart' && $dir == PERSIANTOOLS_CONST::RTL && !($is_all_en && $any_en)) {
          $new_digit = PERSIANTOOLS_CONST::$FA_DIGITS[$ch - '0'];
          $str = drupal_substr($str, 0, $i) . $new_digit . drupal_substr($str, $i + 1);
          $len += drupal_strlen($new_digit) - 1;
          $i += drupal_strlen($new_digit) - 1;
        }
        break;
      case PERSIANTOOLS_CONST::EN:
        $dir = PERSIANTOOLS_CONST::LTR;
        $any_en = TRUE;
        break;
      case PERSIANTOOLS_CONST::FA:
        $dir = PERSIANTOOLS_CONST::RTL;
        break;
      case PERSIANTOOLS_CONST::OPENING:
        $opening_pos = $i;
        $paren_state = 1;
        break;
    }

    // Fix misplaced enclosing chars, like paranthesis, bracket, quotation, ...
    if ($rtl_ltr_fix) {
      switch ($paren_state) {
        case 1:
          $pre_open = $dir;
          $closing_ch = persiantools_get_closing_char($ch);
          $paren_state = 2;
          break;
        case 2:
          if ($type == PERSIANTOOLS_CONST::CLOSING) {

            // Fix misplaced empty enclosing chars, like function calls, array
            // access, ...
            if ($dir == PERSIANTOOLS_CONST::LTR) {
              $str = persiantools_insert_str($str, '&lrm;', $i + 1);
              $len += 5;
              $i += 5;
            }
            $paren_state = 0;
          }
          elseif ($type == PERSIANTOOLS_CONST::EN || $type == PERSIANTOOLS_CONST::FA) {
            $post_open = $dir;
            $paren_state = 3;
          }
          break;
        case 3:
          if ($type == PERSIANTOOLS_CONST::CLOSING) {
            $pre_close = $dir;
            $paren_state = 4;
            $closed_pos = $i;
          }
          break;
        case 4:
          if ($type == PERSIANTOOLS_CONST::EN || $type == PERSIANTOOLS_CONST::FA) {
            $post_close = $dir;
            $paren_state = 5;
          }
          break;
      }
      if ($paren_state == 4 && $i == $len - 1) {
        $post_close = PERSIANTOOLS_CONST::RTL;
        $paren_state = 5;
      }
      if ($paren_state == 5) {
        if ($pre_open == $post_open) {
          $open_dir = $pre_open;
        }
        else {
          $open_dir = PERSIANTOOLS_CONST::RTL;
        }
        if ($pre_close == $post_close) {
          $close_dir = $pre_close;
        }
        else {
          $close_dir = PERSIANTOOLS_CONST::RTL;
        }
        if ($open_dir != $close_dir) {
          if ($pre_open == PERSIANTOOLS_CONST::RTL) {
            $str = persiantools_insert_str($str, '&rlm;', $closed_pos);
            $len += 5;
            $i += 5;
          }
          elseif ($pre_open == PERSIANTOOLS_CONST::LTR) {

            // &#8234; lre (Left to Right Embedding).
            // &#8236: pdf (Pop Directional Formatting).
            $str = persiantools_insert_str($str, '&#8234;', $opening_pos);
            $str = persiantools_insert_str($str, '&#8236;', $closed_pos + 8);
            $len += 14;
            $i += 14;
          }
          $paren_state = 0;
        }
      }

      // Fix misplaced dot in English Sentences inside RTL direction.
      if ($is_all_en && $dir == PERSIANTOOLS_CONST::LTR && $type == PERSIANTOOLS_CONST::EOS) {
        if ($i < $len - 1) {
          $next_ch = drupal_substr($str, $i + 1, 1);
          $next_type = persiantools_get_char_type($next_ch);
          if ($next_type == PERSIANTOOLS_CONST::EN) {
            continue;
          }
        }
        $str = persiantools_insert_str($str, '&lrm;', $i + 1);
        $i += 5;
        $len += 5;
      }
    }
  }
  return $str;
}

/**
 * Fix mixed-up paths in rtl blocks.
 * Logic: Gets triggered once a starting '.' or '/' is detected after a whitespace.
 *        The correcting symbol is inserted once an english char is seen inside the path.
 */
function persiantools_fix_mixed_path($str, $ch, $type, $i, $len) {
  static $maybe_path = TRUE;
  static $is_path = FALSE;
  static $path_pos = -1;
  $changed = FALSE;
  if ($i == 0) {
    $maybe_path = TRUE;
  }
  if ($type == PERSIANTOOLS_CONST::WS) {
    $maybe_path = TRUE;
  }
  elseif ($is_path) {
    if ($type == PERSIANTOOLS_CONST::EN) {
      $str = persiantools_insert_str($str, '&lrm;', $path_pos);
      $changed = TRUE;
    }
    $path_pos = -1;
    $is_path = FALSE;
  }
  elseif ($maybe_path) {
    if ($type == PERSIANTOOLS_CONST::SLASH) {
      $is_path = TRUE;
      if ($path_pos < 0) {
        $path_pos = $i;
      }
    }
    elseif ($ch == '.') {
      if ($path_pos < 0) {
        $path_pos = $i;
      }
    }
    else {
      $maybe_path = FALSE;
      $is_path = FALSE;
      $path_pos = -1;
    }
  }

  // Detect trailing slashes in paths.
  if ($type == PERSIANTOOLS_CONST::SLASH && $i > 0) {
    $prev_ch = drupal_substr($str, $i - 1, 1);
    $prev_ch_type = persiantools_get_char_type($prev_ch);
    $is_last_char = $i == $len - 1;
    if ($prev_ch_type == PERSIANTOOLS_CONST::EN && ($is_last_char || drupal_substr($str, $i + 1, 1) == ' ')) {
      $str = persiantools_insert_str($str, '&lrm;', $i + 1);
      $changed = TRUE;
    }
  }
  return array(
    $str,
    $changed,
  );
}

/**
 * Detects and returns a character's type.
 */
function persiantools_get_char_type($ch) {
  if ($ch >= 'آ' && $ch <= 'ي' || $ch == 'ی') {
    $type = PERSIANTOOLS_CONST::FA;
  }
  elseif ($ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z') {
    $type = PERSIANTOOLS_CONST::EN;
  }
  elseif (in_array($ch, PERSIANTOOLS_CONST::$OPENING_SYMS)) {
    $type = PERSIANTOOLS_CONST::OPENING;
  }
  elseif ($ch >= '0' && $ch <= '9') {
    $type = PERSIANTOOLS_CONST::DIGIT;
  }
  elseif (in_array($ch, PERSIANTOOLS_CONST::$STATMENT_END)) {
    $type = PERSIANTOOLS_CONST::EOS;
  }
  elseif ($ch == '/') {
    $type = PERSIANTOOLS_CONST::SLASH;
  }
  elseif ($ch == ' ' || $ch == '\\n') {
    $type = PERSIANTOOLS_CONST::WS;
  }
  else {

    // Type not detected.
    $type = PERSIANTOOLS_CONST::UN;
  }
  return $type;
}

/**
 * Returns the matching closing char for an opening char.
 */
function persiantools_get_closing_char($char) {
  switch ($char) {
    case '(':
      return ')';
    case '{':
      return '}';
    case '[':
      return ']';
    case '\'':
    case '"':
      return $char;
  }
}

/**
 * A simple function to insert a unicode char in a str.
 */
function persiantools_insert_str($str, $char, $pos) {
  return drupal_substr($str, 0, $pos) . $char . drupal_substr($str, $pos);
}

Functions

Namesort descending Description
persiantools_ajax_render_alter Implements hook_ajax_render_alter().
persiantools_convert_data
persiantools_convert_sm Main function for multiple features and fixes of persiantools module.
persiantools_fix_mixed_path Fix mixed-up paths in rtl blocks. Logic: Gets triggered once a starting '.' or '/' is detected after a whitespace. The correcting symbol is inserted once an english char is seen inside the path.
persiantools_get_char_type Detects and returns a character's type.
persiantools_get_closing_char Returns the matching closing char for an opening char.
persiantools_insert_str A simple function to insert a unicode char in a str.
persiantools_menu Implements hook_menu().
persiantools_preprocess_html Implements hook_preprocess_html().

Classes

Namesort descending Description
PERSIANTOOLS_CONST Canstants Definition