You are here

svg_embed.module in SVG Embed 7

SVG Embed. Provides a filter for text formats that includes and on the fly translates SVG files into text fields.

File

svg_embed.module
View source
<?php

/**
 * @file
 * SVG Embed. Provides a filter for text formats that includes and on the
 * fly translates SVG files into text fields.
 */

/**
 * Implements hook_filter_info().
 *
 * @return array
 *   filters to add
 */
function svg_embed_filter_info() {
  $filters['filter_svg_embed'] = array(
    'title' => t('SVG Embed Filter'),
    'description' => t('Embed SVG files into content and translate them on the fly.'),
    'process callback' => 'svg_embed_filter_svg_embed_process',
    'tips callback' => 'svg_embed_filter_svg_embed_tips',
    'cache' => FALSE,
  );
  return $filters;
}

/**
 * Implements hook_locale().
 *
 * @param string $op
 *   the operation
 *
 * @return array
 *   groups to add
 */
function svg_embed_locale($op = 'groups') {
  switch ($op) {
    case 'groups':
      return array(
        'svg_embed' => t('Embedded SVG files'),
      );
  }
  return array();
}

/**
 * Implements hook_menu().
 */
function svg_embed_menu() {
  $items['admin/config/regional/translate/svgextract'] = array(
    'title' => 'Embedded SVG',
    'description' => 'Extracts strings from embedded SVG files into po files for translation and re-imports them into the proper text group.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'svg_embed_manage_po_files',
    ),
    'access arguments' => array(
      'translate interface',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 99,
  );
  $items['svg-embed/%/%file/fallback.png'] = array(
    'title' => 'Fallback PNG',
    'page callback' => 'svg_embed_fallback_png',
    'page arguments' => array(
      1,
      2,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_filter_FILTER_process().
 */
function svg_embed_filter_svg_embed_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  if (empty($text)) {
    return '';
  }
  if ($langcode == LANGUAGE_NONE) {
    global $user;
    $language = user_preferred_language($user);
    $langcode = $language->language;
  }
  if (preg_match_all("/\\[svg:([^\\:;\\?\\*\\{\\}\\/]+)\\]/", $text, $matches, PREG_SET_ORDER)) {
    foreach ($matches as $match) {
      if (is_numeric($match[1])) {
        $field = 'fid';
      }
      else {
        $field = 'filename';
      }
      $file = db_select('file_managed', 'f')
        ->fields('f', array(
        'fid',
        'uri',
      ))
        ->condition('f.' . $field, $match[1])
        ->execute()
        ->fetch();
      if (!empty($file)) {
        $text = str_replace('[svg:' . $match[1] . ']', _svg_embed_get_svg($file, $langcode), $text);
      }
    }
  }
  return $text;
}

/**
 * Implements hook_filter_FILTER_tips().
 */
function svg_embed_filter_svg_embed_tips($filter, $format, $long) {
  if ($long) {
    return t('To insert an SVG graphic include [svg:FID] into the text. The file should already be uploaded to your Drupal site and you should then replace FID with either the file id (if known) or with the filename.');
  }
  else {
    return t('To insert an SVG graphic include [svg:FID] into the text.');
  }
}

/**
 * Implements hook_file_download().
 */
function svg_embed_file_download($uri) {
  global $user;
  if (strpos(file_uri_target($uri), 'svg_embed/') === 0) {
    if (user_access('translate interface')) {
      return array(
        'Pragma' => 'public',
        'Expires' => 0,
        'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
        'Content-Type' => 'application/force-download',
      );
    }
    else {
      return -1;
    }
  }
  return NULL;
}

/**
 * Form to download and upload PO files for embedded SVG files.
 *
 * @param array $form
 *   form definition
 * @param array $form_state
 *   form state
 *
 * @return array
 *   renderable form
 */
function svg_embed_manage_po_files($form, $form_state) {
  $strings = array();
  $files = db_select('file_managed', 'f')
    ->fields('f', array(
    'uri',
  ))
    ->condition('f.filemime', 'image/svg+xml')
    ->execute()
    ->fetchAll();
  foreach ($files as $file) {
    $svg = file_get_contents($file->uri);
    if (!empty($svg)) {
      $xml = new SimpleXMLElement($svg);
      if (!empty($xml)) {
        _svg_embed_extract($xml, $strings);
      }
    }
  }
  $directory = 'temporary://svg_embed';
  file_prepare_directory($directory, FILE_CREATE_DIRECTORY + FILE_MODIFY_PERMISSIONS);
  $all_languages = language_list('enabled');
  $languages = array_shift($all_languages);
  $output = '';
  foreach ($languages as $langcode => $item) {
    $po = 'msgid ""' . PHP_EOL;
    $po .= 'msgstr "PO-Revision-Date: 2012-03-10 14:34+0100\\nMIME-Version: 1.0\\nContent-Type: text/plain; charset=utf-8\\nContent-Transfer-Encoding: 8bit\\n\\n"' . PHP_EOL;
    foreach ($strings as $string => $lng) {
      $po .= PHP_EOL;
      $po .= 'msgid "' . str_replace('"', '\\"', $string) . '"' . PHP_EOL;
      $po .= 'msgstr "' . str_replace('"', '\\"', $lng[$langcode]) . '"' . PHP_EOL;
    }
    $filename = $directory . '/' . $langcode . '.po';
    file_put_contents($filename, $po);
    $output .= '<div>' . l($langcode, 'system/temporary/svg_embed/' . $langcode . '.po') . '</div>';
  }
  $form['download'] = array(
    '#type' => 'fieldset',
    '#title' => t('Download PO-files'),
  );
  $form['download']['links'] = array(
    '#markup' => $output,
  );

  // Get all languages, except English.
  drupal_static_reset('language_list');
  $names = locale_language_list('name');
  unset($names['en']);
  if (!count($names)) {
    $languages = _locale_prepare_predefined_list();
    $default = key($languages);
  }
  else {
    $languages = array(
      t('Already added languages') => $names,
      t('Languages not yet added') => _locale_prepare_predefined_list(),
    );
    $default = key($names);
  }
  $form['upload'] = array(
    '#type' => 'fieldset',
    '#title' => t('Upload PO file'),
  );
  $form['upload']['langcode'] = array(
    '#type' => 'select',
    '#title' => t('Import into'),
    '#options' => $languages,
    '#default_value' => $default,
    '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, it will be added.'),
  );
  $form['upload']['file'] = array(
    '#type' => 'file',
    '#title' => t('Language file'),
    '#size' => 50,
    '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
  );
  $form['upload']['mode'] = array(
    '#type' => 'radios',
    '#title' => t('Mode'),
    '#default_value' => LOCALE_IMPORT_KEEP,
    '#options' => array(
      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added. The plural format is updated.'),
      LOCALE_IMPORT_KEEP => t('Existing strings and the plural format are kept, only new strings are added.'),
    ),
  );
  $form['upload']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  return $form;
}

/**
 * Process the import form submission.
 */
function svg_embed_manage_po_files_submit($form, &$form_state) {
  $validators = array(
    'file_validate_extensions' => array(
      'po',
    ),
  );

  // Ensure we have the file uploaded.
  if ($file = file_save_upload('file', $validators)) {

    // Now import strings into the language.
    if ($return = _locale_import_po($file, $form_state['values']['langcode'], $form_state['values']['mode'], 'svg_embed') == FALSE) {
      $variables = array(
        '%filename' => $file->filename,
      );
      drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
      watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
    }
  }
  else {
    drupal_set_message(t('File to import not found.'), 'error');
  }
  $form_state['redirect'] = 'admin/config/regional/translate/svgextract';
}

/**
 * Menu callback to localise and convert the SVG to PNG which will then be
 * delivered to the requesting browser.
 *
 * @param string $langcode
 * @param stdClass $file
 */
function svg_embed_fallback_png($langcode, $file) {
  if (!class_exists('Imagick')) {
    drupal_not_found();
    exit;
  }
  $svg = _svg_embed_get_svg($file, $langcode, FALSE);
  $im = new Imagick();
  $im
    ->readImageBlob($svg);
  $im
    ->setImageFormat("png24");
  header("Content-Type: image/png");
  print $im
    ->getImagesBlob();
  exit;
}

/**
 * Helper function to read the SCG, localise it and prepare it for output.
 *
 * @param stdClass $file
 * @param string $langcode
 * @param bool $include_fallback
 * @return string
 */
function _svg_embed_get_svg($file, $langcode, $include_fallback = TRUE) {
  $svg = file_get_contents($file->uri);
  if (!empty($svg)) {
    $xml = new SimpleXMLElement($svg);
    if (!empty($xml)) {
      if ($langcode != LANGUAGE_NONE) {
        _svg_embed_translate($xml, $langcode);
      }
      if ($include_fallback && class_exists('Imagick')) {
        $fallback = $xml
          ->addChild('image');
        $fallback
          ->addAttribute('src', url('svg-embed/' . $langcode . '/' . $file->fid . '/fallback.png'));
      }
      $svg = $xml
        ->asXML();
      $svg_tag = strpos($svg, '<svg');
      return $include_fallback ? substr($svg, $svg_tag) : $svg;
    }
  }
  return '';
}

/**
 * Helper function called recursively to translate all strings in an SVG file.
 *
 * @param SimpleXMLElement $xml
 *   the SVG graphic code
 * @param string $langcode
 *   the language code to which we need to translate
 */
function _svg_embed_translate($xml, $langcode) {
  foreach ($xml as $child) {
    _svg_embed_translate($child, $langcode);
    if (isset($child->text) || isset($child->tspan)) {
      if (isset($child->text->tspan)) {
        $text = $child->text->tspan;
      }
      elseif (isset($child->tspan)) {
        $text = $child->tspan;
      }
      else {
        $text = $child->text;
      }
      $i = 0;
      while (TRUE) {
        $string = (string) $text[$i];
        if (empty($string)) {
          break;
        }
        $string = trim($string);
        if (!empty($string)) {
          $query = db_select('locales_source', 's');
          $query
            ->leftJoin('locales_target', 't', 's.lid = t.lid');
          $translation = $query
            ->fields('t', array(
            'translation',
          ))
            ->condition('s.source', $string)
            ->condition('s.textgroup', 'svg_embed')
            ->condition('t.language', $langcode)
            ->execute()
            ->fetchField();
          $text[$i][0] = empty($translation) ? $string : $translation;
        }
        $i++;
      }
    }
  }
}

/**
 * Helper function to find all strings and all their existing translations, prepared to export all to a po file.
 *
 * @param SimpleXMLElement $xml
 *   the svg graphic code
 * @param array $strings
 *   array containing all strings and translations
 */
function _svg_embed_extract($xml, &$strings) {
  $all_languages = language_list('enabled');
  $languages = array_shift($all_languages);
  foreach ($xml as $child) {
    _svg_embed_extract($child, $strings);
    if (isset($child->text) || isset($child->tspan)) {
      if (isset($child->text->tspan)) {
        $text = $child->text->tspan;
      }
      elseif (isset($child->tspan)) {
        $text = $child->tspan;
      }
      else {
        $text = $child->text;
      }
      $i = 0;
      while (TRUE) {
        $string = (string) $text[$i];
        if (empty($string)) {
          break;
        }
        $string = trim($string);
        if (!empty($string)) {
          if (empty($strings[$string])) {
            $strings[$string] = array();
            foreach ($languages as $langcode => $language) {
              $strings[$string][$langcode] = '';
            }
            $query = db_select('locales_source', 's');
            $query
              ->leftJoin('locales_target', 't', 's.lid = t.lid');
            $translations = $query
              ->fields('t', array(
              'translation',
              'language',
            ))
              ->condition('s.source', $string)
              ->condition('s.textgroup', 'svg_embed')
              ->condition('t.language', 'en', '<>')
              ->execute()
              ->fetchAll();
            foreach ($translations as $translation) {
              $strings[$string][$translation->language] = $translation->translation;
            }
          }
        }
        $i++;
      }
    }
  }
}

Functions

Namesort descending Description
svg_embed_fallback_png Menu callback to localise and convert the SVG to PNG which will then be delivered to the requesting browser.
svg_embed_file_download Implements hook_file_download().
svg_embed_filter_info Implements hook_filter_info().
svg_embed_filter_svg_embed_process Implements hook_filter_FILTER_process().
svg_embed_filter_svg_embed_tips Implements hook_filter_FILTER_tips().
svg_embed_locale Implements hook_locale().
svg_embed_manage_po_files Form to download and upload PO files for embedded SVG files.
svg_embed_manage_po_files_submit Process the import form submission.
svg_embed_menu Implements hook_menu().
_svg_embed_extract Helper function to find all strings and all their existing translations, prepared to export all to a po file.
_svg_embed_get_svg Helper function to read the SCG, localise it and prepare it for output.
_svg_embed_translate Helper function called recursively to translate all strings in an SVG file.