non security modules update
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoHeader
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gettext PO header handler.
|
||||
*
|
||||
* Possible Gettext PO header elements are explained in
|
||||
* http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry,
|
||||
* but we only support a subset of these directly.
|
||||
*
|
||||
* Example header:
|
||||
*
|
||||
* "Project-Id-Version: Drupal core (7.11)\n"
|
||||
* "POT-Creation-Date: 2012-02-12 22:59+0000\n"
|
||||
* "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
|
||||
* "Language-Team: Catalan\n"
|
||||
* "MIME-Version: 1.0\n"
|
||||
* "Content-Type: text/plain; charset=utf-8\n"
|
||||
* "Content-Transfer-Encoding: 8bit\n"
|
||||
* "Plural-Forms: nplurals=2; plural=(n>1);\n"
|
||||
*/
|
||||
class PoHeader {
|
||||
|
||||
/**
|
||||
* Language code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* Formula for the plural form.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_pluralForms;
|
||||
|
||||
/**
|
||||
* Author(s) of the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_authors;
|
||||
|
||||
/**
|
||||
* Date the po file got created.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_po_date;
|
||||
|
||||
/**
|
||||
* Human readable language name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_languageName;
|
||||
|
||||
/**
|
||||
* Name of the project the translation belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_projectName;
|
||||
|
||||
/**
|
||||
* Constructor, creates a PoHeader with default values.
|
||||
*
|
||||
* @param string $langcode
|
||||
* Language code.
|
||||
*/
|
||||
public function __construct($langcode = NULL) {
|
||||
$this->_langcode = $langcode;
|
||||
// Ignore errors when run during site installation before
|
||||
// date_default_timezone_set() is called.
|
||||
$this->_po_date = @date("Y-m-d H:iO");
|
||||
$this->_pluralForms = 'nplurals=2; plural=(n > 1);';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plural form.
|
||||
*
|
||||
* @return string
|
||||
* Plural form component from the header, for example:
|
||||
* 'nplurals=2; plural=(n > 1);'.
|
||||
*/
|
||||
function getPluralForms() {
|
||||
return $this->_pluralForms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the human readable language name.
|
||||
*
|
||||
* @param string $languageName
|
||||
* Human readable language name.
|
||||
*/
|
||||
function setLanguageName($languageName) {
|
||||
$this->_languageName = $languageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human readable language name.
|
||||
*
|
||||
* @return string
|
||||
* The human readable language name.
|
||||
*/
|
||||
function getLanguageName() {
|
||||
return $this->_languageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the project name.
|
||||
*
|
||||
* @param string $projectName
|
||||
* Human readable project name.
|
||||
*/
|
||||
function setProjectName($projectName) {
|
||||
$this->_projectName = $projectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project name.
|
||||
*
|
||||
* @return string
|
||||
* The human readable project name.
|
||||
*/
|
||||
function getProjectName() {
|
||||
return $this->_projectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate internal values from a string.
|
||||
*
|
||||
* @param string $header
|
||||
* Full header string with key-value pairs.
|
||||
*/
|
||||
public function setFromString($header) {
|
||||
// Get an array of all header values for processing.
|
||||
$values = $this->parseHeader($header);
|
||||
|
||||
// There is only one value relevant for our header implementation when
|
||||
// reading, and that is the plural formula.
|
||||
if (!empty($values['Plural-Forms'])) {
|
||||
$this->_pluralForms = $values['Plural-Forms'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Gettext PO formatted header string based on data set earlier.
|
||||
*/
|
||||
public function __toString() {
|
||||
$output = '';
|
||||
|
||||
$isTemplate = empty($this->_languageName);
|
||||
|
||||
$output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n";
|
||||
if (!empty($this->_authors)) {
|
||||
$output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n";
|
||||
}
|
||||
$output .= "#\n";
|
||||
|
||||
// Add the actual header information.
|
||||
$output .= "msgid \"\"\n";
|
||||
$output .= "msgstr \"\"\n";
|
||||
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
|
||||
$output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n";
|
||||
$output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n";
|
||||
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
|
||||
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
|
||||
$output .= "\"MIME-Version: 1.0\\n\"\n";
|
||||
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
|
||||
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
|
||||
$output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n";
|
||||
$output .= "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Plural-Forms entry from a Gettext Portable Object file header.
|
||||
*
|
||||
* @param string $pluralforms
|
||||
* The Plural-Forms entry value.
|
||||
*
|
||||
* @return
|
||||
* An array containing the number of plural forms and the converted version
|
||||
* of the formula that can be evaluated with PHP later.
|
||||
*/
|
||||
function parsePluralForms($pluralforms) {
|
||||
// First, delete all whitespace.
|
||||
$pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
|
||||
|
||||
// Select the parts that define nplurals and plural.
|
||||
$nplurals = strstr($pluralforms, "nplurals=");
|
||||
if (strpos($nplurals, ";")) {
|
||||
// We want the string from the 10th char, because "nplurals=" length is 9.
|
||||
$nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
$plural = strstr($pluralforms, "plural=");
|
||||
if (strpos($plural, ";")) {
|
||||
// We want the string from the 8th char, because "plural=" length is 7.
|
||||
$plural = substr($plural, 7, strpos($plural, ";") - 7);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Get PHP version of the plural formula.
|
||||
$plural = $this->parseArithmetic($plural);
|
||||
|
||||
if ($plural !== FALSE) {
|
||||
return array($nplurals, $plural);
|
||||
}
|
||||
else {
|
||||
throw new Exception('The plural formula could not be parsed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Gettext Portable Object file header.
|
||||
*
|
||||
* @param string $header
|
||||
* A string containing the complete header.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of key-value pairs.
|
||||
*/
|
||||
private function parseHeader($header) {
|
||||
$header_parsed = array();
|
||||
$lines = array_map('trim', explode("\n", $header));
|
||||
foreach ($lines as $line) {
|
||||
if ($line) {
|
||||
list($tag, $contents) = explode(":", $line, 2);
|
||||
$header_parsed[trim($tag)] = trim($contents);
|
||||
}
|
||||
}
|
||||
return $header_parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and sanitizes an arithmetic formula into a PHP expression.
|
||||
*
|
||||
* While parsing, we ensure, that the operators have the right
|
||||
* precedence and associativity.
|
||||
*
|
||||
* @param string $string
|
||||
* A string containing the arithmetic formula.
|
||||
*
|
||||
* @return
|
||||
* A version of the formula to evaluate with PHP later.
|
||||
*/
|
||||
private function parseArithmetic($string) {
|
||||
// Operator precedence table.
|
||||
$precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
|
||||
// Right associativity.
|
||||
$right_associativity = array("?" => 1, ":" => 1);
|
||||
|
||||
$tokens = $this->tokenizeFormula($string);
|
||||
|
||||
// Parse by converting into infix notation then back into postfix
|
||||
// Operator stack - holds math operators and symbols.
|
||||
$operator_stack = array();
|
||||
// Element Stack - holds data to be operated on.
|
||||
$element_stack = array();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$current_token = $token;
|
||||
|
||||
// Numbers and the $n variable are simply pushed into $element_stack.
|
||||
if (is_numeric($token)) {
|
||||
$element_stack[] = $current_token;
|
||||
}
|
||||
elseif ($current_token == "n") {
|
||||
$element_stack[] = '$n';
|
||||
}
|
||||
elseif ($current_token == "(") {
|
||||
$operator_stack[] = $current_token;
|
||||
}
|
||||
elseif ($current_token == ")") {
|
||||
$topop = array_pop($operator_stack);
|
||||
while (isset($topop) && ($topop != "(")) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
}
|
||||
elseif (!empty($precedence[$current_token])) {
|
||||
// If it's an operator, then pop from $operator_stack into
|
||||
// $element_stack until the precedence in $operator_stack is less
|
||||
// than current, then push into $operator_stack.
|
||||
$topop = array_pop($operator_stack);
|
||||
while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
if ($topop) {
|
||||
// Return element to top.
|
||||
$operator_stack[] = $topop;
|
||||
}
|
||||
// Parentheses are not needed.
|
||||
$operator_stack[] = $current_token;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush operator stack.
|
||||
$topop = array_pop($operator_stack);
|
||||
while ($topop != NULL) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
|
||||
// Now extract formula from stack.
|
||||
$previous_size = count($element_stack) + 1;
|
||||
while (count($element_stack) < $previous_size) {
|
||||
$previous_size = count($element_stack);
|
||||
for ($i = 2; $i < count($element_stack); $i++) {
|
||||
$op = $element_stack[$i];
|
||||
if (!empty($precedence[$op])) {
|
||||
$f = "";
|
||||
if ($op == ":") {
|
||||
$f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
|
||||
}
|
||||
elseif ($op == "?") {
|
||||
$f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
|
||||
}
|
||||
else {
|
||||
$f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
|
||||
}
|
||||
array_splice($element_stack, $i - 2, 3, $f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only one element is left, the number of operators is appropriate.
|
||||
if (count($element_stack) == 1) {
|
||||
return $element_stack[0];
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize the formula.
|
||||
*
|
||||
* @param string $formula
|
||||
* A string containing the arithmetic formula.
|
||||
*
|
||||
* @return array
|
||||
* List of arithmetic tokens identified in the formula.
|
||||
*/
|
||||
private function tokenizeFormula($formula) {
|
||||
$formula = str_replace(" ", "", $formula);
|
||||
$tokens = array();
|
||||
for ($i = 0; $i < strlen($formula); $i++) {
|
||||
if (is_numeric($formula[$i])) {
|
||||
$num = $formula[$i];
|
||||
$j = $i + 1;
|
||||
while ($j < strlen($formula) && is_numeric($formula[$j])) {
|
||||
$num .= $formula[$j];
|
||||
$j++;
|
||||
}
|
||||
$i = $j - 1;
|
||||
$tokens[] = $num;
|
||||
}
|
||||
elseif ($pos = strpos(" =<>!&|", $formula[$i])) {
|
||||
$next = $formula[$i + 1];
|
||||
switch ($pos) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
if ($next == '=') {
|
||||
$tokens[] = $formula[$i] . '=';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if ($next == '&') {
|
||||
$tokens[] = '&&';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if ($next == '|') {
|
||||
$tokens[] = '||';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoItem.
|
||||
*/
|
||||
|
||||
/**
|
||||
* PoItem handles one translation.
|
||||
*/
|
||||
class PoItem {
|
||||
|
||||
/**
|
||||
* The language code this translation is in.
|
||||
*
|
||||
* @car string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* The context this translation belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_context = '';
|
||||
|
||||
/**
|
||||
* The source string or array of strings if it has plurals.
|
||||
*
|
||||
* @var string or array
|
||||
* @see $_plural
|
||||
*/
|
||||
private $_source;
|
||||
|
||||
/**
|
||||
* Flag indicating if this translation has plurals.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $_plural;
|
||||
|
||||
/**
|
||||
* The comment of this translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_comment;
|
||||
|
||||
/**
|
||||
* The translation string or array of strings if it has plurals.
|
||||
*
|
||||
* @var string or array
|
||||
* @see $_plural
|
||||
*/
|
||||
private $_translation;
|
||||
|
||||
/**
|
||||
* Get the language code of the currently used language.
|
||||
*
|
||||
* @return string with langcode
|
||||
*/
|
||||
function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language code of the current language.
|
||||
*
|
||||
* @param string $langcode
|
||||
*/
|
||||
function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context this translation belongs to.
|
||||
*
|
||||
* @return string $context
|
||||
*/
|
||||
function getContext() {
|
||||
return $this->_context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context this translation belongs to.
|
||||
*
|
||||
* @param string $context
|
||||
*/
|
||||
function setContext($context) {
|
||||
$this->_context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @return string or array $translation
|
||||
*/
|
||||
function getSource() {
|
||||
return $this->_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @param string or array $source
|
||||
*/
|
||||
function setSource($source) {
|
||||
$this->_source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translation string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @return string or array $translation
|
||||
*/
|
||||
function getTranslation() {
|
||||
return $this->_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the translation string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @param string or array $translation
|
||||
*/
|
||||
function setTranslation($translation) {
|
||||
$this->_translation = $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the translation has plural values.
|
||||
*
|
||||
* @param boolean $plural
|
||||
*/
|
||||
function setPlural($plural) {
|
||||
$this->_plural = $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the translation has plural values.
|
||||
*
|
||||
* @return boolean $plural
|
||||
*/
|
||||
function isPlural() {
|
||||
return $this->_plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment of this translation.
|
||||
*
|
||||
* @return String $comment
|
||||
*/
|
||||
function getComment() {
|
||||
return $this->_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment of this translation.
|
||||
*
|
||||
* @param String $comment
|
||||
*/
|
||||
function setComment($comment) {
|
||||
$this->_comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the PoItem from a structured array.
|
||||
*
|
||||
* @param array values
|
||||
*/
|
||||
public function setFromArray(array $values = array()) {
|
||||
if (isset($values['context'])) {
|
||||
$this->setContext($values['context']);
|
||||
}
|
||||
if (isset($values['source'])) {
|
||||
$this->setSource($values['source']);
|
||||
}
|
||||
if (isset($values['translation'])) {
|
||||
$this->setTranslation($values['translation']);
|
||||
}
|
||||
if (isset($values['comment'])){
|
||||
$this->setComment($values['comment']);
|
||||
}
|
||||
if (isset($this->_source) &&
|
||||
strpos($this->_source, L10N_UPDATE_PLURAL_DELIMITER) !== FALSE) {
|
||||
$this->setSource(explode(L10N_UPDATE_PLURAL_DELIMITER, $this->_source));
|
||||
$this->setTranslation(explode(L10N_UPDATE_PLURAL_DELIMITER, $this->_translation));
|
||||
$this->setPlural(count($this->_translation) > 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the PoItem as a string.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->formatItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the POItem as a string.
|
||||
*/
|
||||
private function formatItem() {
|
||||
$output = '';
|
||||
|
||||
// Format string context.
|
||||
if (!empty($this->_context)) {
|
||||
$output .= 'msgctxt ' . $this->formatString($this->_context);
|
||||
}
|
||||
|
||||
// Format translation.
|
||||
if ($this->_plural) {
|
||||
$output .= $this->formatPlural();
|
||||
}
|
||||
else {
|
||||
$output .= $this->formatSingular();
|
||||
}
|
||||
|
||||
// Add one empty line to separate the translations.
|
||||
$output .= "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a plural translation.
|
||||
*/
|
||||
private function formatPlural() {
|
||||
$output = '';
|
||||
|
||||
// Format source strings.
|
||||
$output .= 'msgid ' . $this->formatString($this->_source[0]);
|
||||
$output .= 'msgid_plural ' . $this->formatString($this->_source[1]);
|
||||
|
||||
foreach ($this->_translation as $i => $trans) {
|
||||
if (isset($this->_translation[$i])) {
|
||||
$output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
|
||||
}
|
||||
else {
|
||||
$output .= 'msgstr[' . $i . '] ""' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a singular translation.
|
||||
*/
|
||||
private function formatSingular() {
|
||||
$output = '';
|
||||
$output .= 'msgid ' . $this->formatString($this->_source);
|
||||
$output .= 'msgstr ' . (isset($this->_translation) ? $this->formatString($this->_translation) : '""');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string for output on multiple lines.
|
||||
*/
|
||||
private function formatString($string) {
|
||||
// Escape characters for processing.
|
||||
$string = addcslashes($string, "\0..\37\\\"");
|
||||
|
||||
// Always include a line break after the explicit \n line breaks from
|
||||
// the source string. Otherwise wrap at 70 chars to accommodate the extra
|
||||
// format overhead too.
|
||||
$parts = explode("\n", wordwrap(str_replace('\n', "\\n\n", $string), 70, " \n"));
|
||||
|
||||
// Multiline string should be exported starting with a "" and newline to
|
||||
// have all lines aligned on the same column.
|
||||
if (count($parts) > 1) {
|
||||
return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
|
||||
}
|
||||
// Single line strings are output on the same line.
|
||||
else {
|
||||
return "\"$parts[0]\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoMemoryWriter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a Gettext PO memory writer, to be used by the installer.
|
||||
*/
|
||||
class PoMemoryWriter implements PoWriterInterface {
|
||||
|
||||
/**
|
||||
* Array to hold all PoItem elements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_items;
|
||||
|
||||
/**
|
||||
* Constructor, initialize empty items.
|
||||
*/
|
||||
function __construct() {
|
||||
$this->_items = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItem().
|
||||
*/
|
||||
public function writeItem(PoItem $item) {
|
||||
if (is_array($item->getSource())) {
|
||||
$item->setSource(implode(L10N_UPDATE_PLURAL_DELIMITER, $item->getSource()));
|
||||
$item->setTranslation(implode(L10N_UPDATE_PLURAL_DELIMITER, $item->getTranslation()));
|
||||
}
|
||||
$context = $item->getContext();
|
||||
$this->_items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItems().
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stored PoItem's.
|
||||
*
|
||||
* @return array PoItem
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:setLangcode().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function setLangcode($langcode) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:getLangcode().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function getLangcode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:getHeader().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function getHeader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:setHeader().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function setHeader(PoHeader $header) {
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoMetadataInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Methods required for both reader and writer implementations.
|
||||
*
|
||||
* @see Drupal\Component\Gettext\PoReaderInterface
|
||||
* @see Drupal\Component\Gettext\PoWriterInterface
|
||||
*/
|
||||
interface PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Set language code.
|
||||
*
|
||||
* @param string $langcode
|
||||
* Language code string.
|
||||
*/
|
||||
public function setLangcode($langcode);
|
||||
|
||||
/**
|
||||
* Get language code.
|
||||
*
|
||||
* @return string
|
||||
* Language code string.
|
||||
*/
|
||||
public function getLangcode();
|
||||
|
||||
/**
|
||||
* Set header metadata.
|
||||
*
|
||||
* @param PoHeader $header
|
||||
* Header object representing metadata in a PO header.
|
||||
*/
|
||||
public function setHeader(PoHeader $header);
|
||||
|
||||
/**
|
||||
* Get header metadata.
|
||||
*
|
||||
* @return PoHeader $header
|
||||
* Header instance representing metadata in a PO header.
|
||||
*/
|
||||
public function getHeader();
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoReaderInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared interface definition for all Gettext PO Readers.
|
||||
*/
|
||||
interface PoReaderInterface extends PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Reads and returns a PoItem (source/translation pair).
|
||||
*
|
||||
* @return PoItem
|
||||
* Wrapper for item data instance.
|
||||
*/
|
||||
public function readItem();
|
||||
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoStreamInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common functions for file/stream based PO readers/writers.
|
||||
*
|
||||
* @see PoReaderInterface
|
||||
* @see PoWriterInterface
|
||||
*/
|
||||
interface PoStreamInterface {
|
||||
|
||||
/**
|
||||
* Open the stream. Set the URI for the stream earlier with setURI().
|
||||
*/
|
||||
public function open();
|
||||
|
||||
/**
|
||||
* Close the stream.
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Get the URI of the PO stream that is being read or written.
|
||||
*
|
||||
* @return
|
||||
* URI string for this stream.
|
||||
*/
|
||||
public function getURI();
|
||||
|
||||
/**
|
||||
* Set the URI of the PO stream that is going to be read or written.
|
||||
*
|
||||
* @param $uri
|
||||
* URI string to set for this stream.
|
||||
*/
|
||||
public function setURI($uri);
|
||||
|
||||
}
|
@@ -0,0 +1,603 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Component\Gettext\PoStreamReader.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements Gettext PO stream reader.
|
||||
*
|
||||
* The PO file format parsing is implemented according to the documentation at
|
||||
* http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
|
||||
*/
|
||||
class PoStreamReader implements PoStreamInterface, PoReaderInterface {
|
||||
|
||||
/**
|
||||
* Source line number of the stream being parsed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_line_number = 0;
|
||||
|
||||
/**
|
||||
* Parser context for the stream reader state machine.
|
||||
*
|
||||
* Possible contexts are:
|
||||
* - 'COMMENT' (#)
|
||||
* - 'MSGID' (msgid)
|
||||
* - 'MSGID_PLURAL' (msgid_plural)
|
||||
* - 'MSGCTXT' (msgctxt)
|
||||
* - 'MSGSTR' (msgstr or msgstr[])
|
||||
* - 'MSGSTR_ARR' (msgstr_arg)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_context = 'COMMENT';
|
||||
|
||||
/**
|
||||
* Current entry being read. Incomplete.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_current_item = array();
|
||||
|
||||
/**
|
||||
* Current plural index for plural translations.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_current_plural_index = 0;
|
||||
|
||||
/**
|
||||
* URI of the PO stream that is being read.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_uri = '';
|
||||
|
||||
/**
|
||||
* Language code for the PO stream being read.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode = NULL;
|
||||
|
||||
/**
|
||||
* Size of the current PO stream.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_size;
|
||||
|
||||
/**
|
||||
* File handle of the current PO stream.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $_fd;
|
||||
|
||||
/**
|
||||
* The PO stream header.
|
||||
*
|
||||
* @var PoHeader
|
||||
*/
|
||||
private $_header;
|
||||
|
||||
/**
|
||||
* Object wrapper for the last read source/translation pair.
|
||||
*
|
||||
* @var PoItem
|
||||
*/
|
||||
private $_last_item;
|
||||
|
||||
/**
|
||||
* Indicator of whether the stream reading is finished.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $_finished;
|
||||
|
||||
/**
|
||||
* Array of translated error strings recorded on reading this stream so far.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_errors;
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getLangcode().
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setLangcode().
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getHeader().
|
||||
*/
|
||||
public function getHeader() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setHeader().
|
||||
*
|
||||
* Not applicable to stream reading and therefore not implemented.
|
||||
*/
|
||||
public function setHeader(PoHeader $header) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::getURI().
|
||||
*/
|
||||
public function getURI() {
|
||||
return $this->_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::setURI().
|
||||
*/
|
||||
public function setURI($uri) {
|
||||
$this->_uri = $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::open().
|
||||
*
|
||||
* Opens the stream and reads the header. The stream is ready for reading
|
||||
* items after.
|
||||
*
|
||||
* @throws Exception
|
||||
* If the URI is not yet set.
|
||||
*/
|
||||
public function open() {
|
||||
if (!empty($this->_uri)) {
|
||||
$this->_fd = fopen($this->_uri, 'rb');
|
||||
$this->_size = ftell($this->_fd);
|
||||
$this->readHeader();
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Cannot open stream without URI set.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::close().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the stream is not open.
|
||||
*/
|
||||
public function close() {
|
||||
if ($this->_fd) {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Cannot close stream that is not open.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoReaderInterface::readItem().
|
||||
*/
|
||||
public function readItem() {
|
||||
// Clear out the last item.
|
||||
$this->_last_item = NULL;
|
||||
|
||||
// Read until finished with the stream or a complete item was identified.
|
||||
while (!$this->_finished && is_null($this->_last_item)) {
|
||||
$this->readLine();
|
||||
}
|
||||
|
||||
return $this->_last_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the seek position for the current PO stream.
|
||||
*
|
||||
* @param int $seek
|
||||
* The new seek position to set.
|
||||
*/
|
||||
public function setSeek($seek) {
|
||||
fseek($this->_fd, $seek);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pointer position of the current PO stream.
|
||||
*/
|
||||
public function getSeek() {
|
||||
return ftell($this->_fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the header from the PO stream.
|
||||
*
|
||||
* The header is a special case PoItem, using the empty string as source and
|
||||
* key-value pairs as translation. We just reuse the item reader logic to
|
||||
* read the header.
|
||||
*/
|
||||
private function readHeader() {
|
||||
$item = $this->readItem();
|
||||
// Handle the case properly when the .po file is empty (0 bytes).
|
||||
if (!$item) {
|
||||
return;
|
||||
}
|
||||
$header = new PoHeader;
|
||||
$header->setFromString(trim($item->getTranslation()));
|
||||
$this->_header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a line from the PO stream and stores data internally.
|
||||
*
|
||||
* Expands $this->_current_item based on new data for the current item. If
|
||||
* this line ends the current item, it is saved with setItemFromArray() with
|
||||
* data from $this->_current_item.
|
||||
*
|
||||
* An internal state machine is maintained in this reader using $this->_context
|
||||
* as the reading state. PO items are inbetween COMMENT states (when items have
|
||||
* at least one line or comment inbetween them or indicated by MSGSTR or
|
||||
* MSGSTR_ARR followed immediately by an MSGID or MSGCTXT (when items closely
|
||||
* follow each other).
|
||||
*
|
||||
* @return
|
||||
* FALSE if an error was logged, NULL otherwise. The errors are considered
|
||||
* non-blocking, so reading can continue, while the errors are collected
|
||||
* for later presentation.
|
||||
*/
|
||||
private function readLine() {
|
||||
// Read a line and set the stream finished indicator if it was not
|
||||
// possible anymore.
|
||||
$line = fgets($this->_fd);
|
||||
$this->_finished = ($line === FALSE);
|
||||
|
||||
if (!$this->_finished) {
|
||||
|
||||
if ($this->_line_number == 0) {
|
||||
// The first line might come with a UTF-8 BOM, which should be removed.
|
||||
$line = str_replace("\xEF\xBB\xBF", '', $line);
|
||||
// Current plurality for 'msgstr[]'.
|
||||
$this->_current_plural_index = 0;
|
||||
}
|
||||
|
||||
// Track the line number for error reporting.
|
||||
$this->_line_number++;
|
||||
|
||||
// Initialize common values for error logging.
|
||||
$log_vars = array(
|
||||
'%uri' => $this->getURI(),
|
||||
'%line' => $this->_line_number,
|
||||
);
|
||||
|
||||
// Trim away the linefeed. \\n might appear at the end of the string if
|
||||
// another line continuing the same string follows. We can remove that.
|
||||
$line = trim(strtr($line, array("\\\n" => "")));
|
||||
|
||||
if (!strncmp('#', $line, 1)) {
|
||||
// Lines starting with '#' are comments.
|
||||
|
||||
if ($this->_context == 'COMMENT') {
|
||||
// Already in comment context, add to current comment.
|
||||
$this->_current_item['#'][] = substr($line, 1);
|
||||
}
|
||||
elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
|
||||
// Start a new entry for the comment.
|
||||
$this->_current_item = array();
|
||||
$this->_current_item['#'][] = substr($line, 1);
|
||||
|
||||
$this->_context = 'COMMENT';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// A comment following any other context is a syntax error.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgid_plural', $line, 12)) {
|
||||
// A plural form for the current source string.
|
||||
|
||||
if ($this->_context != 'MSGID') {
|
||||
// A plural form can only be added to an msgid directly.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgid_plural' and trim away whitespace.
|
||||
$line = trim(substr($line, 12));
|
||||
|
||||
// Only the plural source string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The plural form must be wrapped in quotes.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains a syntax error on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Append the plural source to the current entry.
|
||||
if (is_string($this->_current_item['msgid'])) {
|
||||
// The first value was stored as string. Now we know the context is
|
||||
// plural, it is converted to array.
|
||||
$this->_current_item['msgid'] = array($this->_current_item['msgid']);
|
||||
}
|
||||
$this->_current_item['msgid'][] = $quoted;
|
||||
|
||||
$this->_context = 'MSGID_PLURAL';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgid', $line, 5)) {
|
||||
// Starting a new message.
|
||||
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
|
||||
// Start a new context for the msgid.
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif ($this->_context == 'MSGID') {
|
||||
// We are currently already in the context, meaning we passed an id with no data.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgid' and trim away whitespace.
|
||||
$line = trim(substr($line, 5));
|
||||
|
||||
// Only the message id string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The message id must be wrapped in quotes.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgid'] = $quoted;
|
||||
$this->_context = 'MSGID';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgctxt', $line, 7)) {
|
||||
// Starting a new context.
|
||||
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif (!empty($this->_current_item['msgctxt'])) {
|
||||
// A context cannot apply to another context.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgctxt' and trim away whitespaces.
|
||||
$line = trim(substr($line, 7));
|
||||
|
||||
// Only the msgctxt string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The context string must be quoted.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgctxt'] = $quoted;
|
||||
|
||||
$this->_context = 'MSGCTXT';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgstr[', $line, 7)) {
|
||||
// A message string for a specific plurality.
|
||||
|
||||
if (($this->_context != 'MSGID') &&
|
||||
($this->_context != 'MSGCTXT') &&
|
||||
($this->_context != 'MSGID_PLURAL') &&
|
||||
($this->_context != 'MSGSTR_ARR')) {
|
||||
// Plural message strings must come after msgid, msgxtxt,
|
||||
// msgid_plural, or other msgstr[] entries.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Ensure the plurality is terminated.
|
||||
if (strpos($line, ']') === FALSE) {
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract the plurality.
|
||||
$frombracket = strstr($line, '[');
|
||||
$this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
|
||||
|
||||
// Skip to the next whitespace and trim away any further whitespace,
|
||||
// bringing $line to the message text only.
|
||||
$line = trim(strstr($line, " "));
|
||||
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The string must be quoted.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
|
||||
$this->_current_item['msgstr'] = array();
|
||||
}
|
||||
|
||||
$this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
|
||||
|
||||
$this->_context = 'MSGSTR_ARR';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp("msgstr", $line, 6)) {
|
||||
// A string pair for an msgidid (with optional context).
|
||||
|
||||
if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
|
||||
// Strings are only valid within an id or context scope.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgstr' and trim away away whitespaces.
|
||||
$line = trim(substr($line, 6));
|
||||
|
||||
// Only the msgstr string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The string must be quoted.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgstr'] = $quoted;
|
||||
|
||||
$this->_context = 'MSGSTR';
|
||||
return;
|
||||
}
|
||||
elseif ($line != '') {
|
||||
// Anything that is not a token may be a continuation of a previous token.
|
||||
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// This string must be quoted.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Append the string to the current item.
|
||||
if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
|
||||
if (is_array($this->_current_item['msgid'])) {
|
||||
// Add string to last array element for plural sources.
|
||||
$last_index = count($this->_current_item['msgid']) - 1;
|
||||
$this->_current_item['msgid'][$last_index] .= $quoted;
|
||||
}
|
||||
else {
|
||||
// Singular source, just append the string.
|
||||
$this->_current_item['msgid'] .= $quoted;
|
||||
}
|
||||
}
|
||||
elseif ($this->_context == 'MSGCTXT') {
|
||||
// Multiline context name.
|
||||
$this->_current_item['msgctxt'] .= $quoted;
|
||||
}
|
||||
elseif ($this->_context == 'MSGSTR') {
|
||||
// Multiline translation string.
|
||||
$this->_current_item['msgstr'] .= $quoted;
|
||||
}
|
||||
elseif ($this->_context == 'MSGSTR_ARR') {
|
||||
// Multiline plural translation string.
|
||||
$this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
|
||||
}
|
||||
else {
|
||||
// No valid context to append to.
|
||||
$this->_errors[] = format_string('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty line read or EOF of PO stream, close out the last entry.
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif ($this->_context != 'COMMENT') {
|
||||
$this->_errors[] = format_string('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the parsed values as a PoItem object.
|
||||
*/
|
||||
public function setItemFromArray($value) {
|
||||
$plural = FALSE;
|
||||
|
||||
$comments = '';
|
||||
if (isset($value['#'])) {
|
||||
$comments = $this->shortenComments($value['#']);
|
||||
}
|
||||
|
||||
if (is_array($value['msgstr'])) {
|
||||
// Sort plural variants by their form index.
|
||||
ksort($value['msgstr']);
|
||||
$plural = TRUE;
|
||||
}
|
||||
|
||||
$item = new PoItem();
|
||||
$item->setContext(isset($value['msgctxt']) ? $value['msgctxt'] : '');
|
||||
$item->setSource($value['msgid']);
|
||||
$item->setTranslation($value['msgstr']);
|
||||
$item->setPlural($plural);
|
||||
$item->setComment($comments);
|
||||
$item->setLangcode($this->_langcode);
|
||||
|
||||
$this->_last_item = $item;
|
||||
|
||||
$this->_context = 'COMMENT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string in quotes.
|
||||
*
|
||||
* @param $string
|
||||
* A string specified with enclosing quotes.
|
||||
*
|
||||
* @return
|
||||
* The string parsed from inside the quotes.
|
||||
*/
|
||||
function parseQuoted($string) {
|
||||
if (substr($string, 0, 1) != substr($string, -1, 1)) {
|
||||
// Start and end quotes must be the same.
|
||||
return FALSE;
|
||||
}
|
||||
$quote = substr($string, 0, 1);
|
||||
$string = substr($string, 1, -1);
|
||||
if ($quote == '"') {
|
||||
// Double quotes: strip slashes.
|
||||
return stripcslashes($string);
|
||||
}
|
||||
elseif ($quote == "'") {
|
||||
// Simple quote: return as-is.
|
||||
return $string;
|
||||
}
|
||||
else {
|
||||
// Unrecognized quote.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a short, one-string version of the passed comment array.
|
||||
*
|
||||
* @param $comment
|
||||
* An array of strings containing a comment.
|
||||
*
|
||||
* @return
|
||||
* Short one-string version of the comment.
|
||||
*/
|
||||
private function shortenComments($comment) {
|
||||
$comm = '';
|
||||
while (count($comment)) {
|
||||
$test = $comm . substr(array_shift($comment), 1) . ', ';
|
||||
if (strlen($comm) < 130) {
|
||||
$comm = $test;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trim(substr($comm, 0, -2));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoStreamWriter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines a Gettext PO stream writer.
|
||||
*/
|
||||
class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
|
||||
|
||||
/**
|
||||
* URI of the PO stream that is being written.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_uri;
|
||||
|
||||
/**
|
||||
* The Gettext PO header.
|
||||
*
|
||||
* @var PoHeader
|
||||
*/
|
||||
private $_header;
|
||||
|
||||
/**
|
||||
* File handle of the current PO stream.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $_fd;
|
||||
|
||||
/**
|
||||
* Get the PO header of the current stream.
|
||||
*
|
||||
* @return PoHeader
|
||||
* The Gettext PO header.
|
||||
*/
|
||||
public function getHeader() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PO header for the current stream.
|
||||
*
|
||||
* @param PoHeader $header
|
||||
* The Gettext PO header to set.
|
||||
*/
|
||||
public function setHeader(PoHeader $header) {
|
||||
$this->_header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current language code used.
|
||||
*
|
||||
* @return string
|
||||
* The language code.
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language code.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::open().
|
||||
*/
|
||||
public function open() {
|
||||
// Open in write mode. Will overwrite the stream if it already exists.
|
||||
$this->_fd = fopen($this->getURI(), 'w');
|
||||
// Write the header at the start.
|
||||
$this->writeHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::close().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the stream is not open.
|
||||
*/
|
||||
public function close() {
|
||||
if ($this->_fd) {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
else {
|
||||
throw new Exception('Cannot close stream that is not open.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $data
|
||||
* Piece of string to write to the stream. If the value is not directly a
|
||||
* string, casting will happen in writing.
|
||||
*
|
||||
* @throws Exception
|
||||
* If writing the data is not possible.
|
||||
*/
|
||||
private function write($data) {
|
||||
$result = fputs($this->_fd, $data);
|
||||
if ($result === FALSE) {
|
||||
throw new Exception('Unable to write data: ' . substr($data, 0, 20));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the PO header to the stream.
|
||||
*/
|
||||
private function writeHeader() {
|
||||
$this->write($this->_header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItem().
|
||||
*/
|
||||
public function writeItem(PoItem $item) {
|
||||
$this->write($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItems().
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::getURI().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the URI is not set.
|
||||
*/
|
||||
public function getURI() {
|
||||
if (empty($this->_uri)) {
|
||||
throw new Exception('No URI set.');
|
||||
}
|
||||
return $this->_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoStreamInterface::setURI().
|
||||
*/
|
||||
public function setURI($uri) {
|
||||
$this->_uri = $uri;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Component\Gettext\PoWriterInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared interface definition for all Gettext PO Writers.
|
||||
*/
|
||||
interface PoWriterInterface extends PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Writes the given item.
|
||||
*
|
||||
* @param PoItem $item
|
||||
* One specific item to write.
|
||||
*/
|
||||
public function writeItem(PoItem $item);
|
||||
|
||||
/**
|
||||
* Writes all or the given amount of items.
|
||||
*
|
||||
* @param PoReaderInterface $reader
|
||||
* Reader to read PoItems from.
|
||||
* @param $count
|
||||
* Amount of items to read from $reader to write. If -1, all items are
|
||||
* read from $reader.
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1);
|
||||
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Gettext class.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Static class providing Drupal specific Gettext functionality.
|
||||
*
|
||||
* The operations are related to pumping data from a source to a destination,
|
||||
* for example:
|
||||
* - Remote files http://*.po to memory
|
||||
* - File public://*.po to database
|
||||
*/
|
||||
class Gettext {
|
||||
|
||||
/**
|
||||
* Reads the given PO files into the database.
|
||||
*
|
||||
* @param stdClass $file
|
||||
* File object with an URI property pointing at the file's path.
|
||||
* - "langcode": The language the strings will be added to.
|
||||
* - "uri": File URI.
|
||||
* @param array $options
|
||||
* An array with options that can have the following elements:
|
||||
* - 'overwrite_options': Overwrite options array as defined in
|
||||
* PoDatabaseWriter. Optional, defaults to an empty array.
|
||||
* - 'customized': Flag indicating whether the strings imported from $file
|
||||
* are customized translations or come from a community source. Use
|
||||
* L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED. Optional, defaults to
|
||||
* L10N_UPDATE_NOT_CUSTOMIZED.
|
||||
* - 'seek': Specifies from which position in the file should the reader
|
||||
* start reading the next items. Optional, defaults to 0.
|
||||
* - 'items': Specifies the number of items to read. Optional, defaults to
|
||||
* -1, which means that all the items from the stream will be read.
|
||||
*
|
||||
* @return array
|
||||
* Report array as defined in PoDatabaseWriter.
|
||||
*
|
||||
* @see PoDatabaseWriter
|
||||
*/
|
||||
static function fileToDatabase($file, $options) {
|
||||
// Add the default values to the options array.
|
||||
$options += array(
|
||||
'overwrite_options' => array(),
|
||||
'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
|
||||
'items' => -1,
|
||||
'seek' => 0,
|
||||
);
|
||||
// Instantiate and initialize the stream reader for this file.
|
||||
$reader = new PoStreamReader();
|
||||
$reader->setLangcode($file->langcode);
|
||||
$reader->setURI($file->uri);
|
||||
|
||||
try {
|
||||
$reader->open();
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
$header = $reader->getHeader();
|
||||
if (!$header) {
|
||||
throw new \Exception('Missing or malformed header.');
|
||||
}
|
||||
|
||||
// Initialize the database writer.
|
||||
$writer = new PoDatabaseWriter();
|
||||
$writer->setLangcode($file->langcode);
|
||||
$writer_options = array(
|
||||
'overwrite_options' => $options['overwrite_options'],
|
||||
'customized' => $options['customized'],
|
||||
);
|
||||
$writer->setOptions($writer_options);
|
||||
$writer->setHeader($header);
|
||||
|
||||
// Attempt to pipe all items from the file to the database.
|
||||
try {
|
||||
if ($options['seek']) {
|
||||
$reader->setSeek($options['seek']);
|
||||
}
|
||||
$writer->writeItems($reader, $options['items']);
|
||||
}
|
||||
catch (\Exception $exception) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// Report back with an array of status information.
|
||||
$report = $writer->getReport();
|
||||
|
||||
// Add the seek position to the report. This is useful for the batch
|
||||
// operation.
|
||||
$report['seek'] = $reader->getSeek();
|
||||
return $report;
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of PoDatabaseReader.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gettext PO reader working with the locale module database.
|
||||
*/
|
||||
class PoDatabaseReader implements PoReaderInterface {
|
||||
|
||||
/**
|
||||
* An associative array indicating which type of strings should be read.
|
||||
*
|
||||
* Elements of the array:
|
||||
* - not_customized: boolean indicating if not customized strings should be
|
||||
* read.
|
||||
* - customized: boolean indicating if customized strings should be read.
|
||||
* - no_translated: boolean indicating if non-translated should be read.
|
||||
*
|
||||
* The three options define three distinct sets of strings, which combined
|
||||
* cover all strings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_options;
|
||||
|
||||
/**
|
||||
* Language code of the language being read from the database.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* Store the result of the query so it can be iterated later.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $_result;
|
||||
|
||||
/**
|
||||
* Database storage to retrieve the strings from.
|
||||
*
|
||||
* @var StringDatabaseStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Constructor, initializes with default options.
|
||||
*/
|
||||
function __construct() {
|
||||
$this->setOptions(array());
|
||||
$this->storage = new StringDatabaseStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getLangcode().
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setLangcode().
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options used by the reader.
|
||||
*/
|
||||
function getOptions() {
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options for the current reader.
|
||||
*/
|
||||
function setOptions(array $options) {
|
||||
$options += array(
|
||||
'customized' => FALSE,
|
||||
'not_customized' => FALSE,
|
||||
'not_translated' => FALSE,
|
||||
);
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getHeader().
|
||||
*/
|
||||
function getHeader() {
|
||||
return new PoHeader($this->getLangcode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setHeader().
|
||||
*
|
||||
* @throws Exception
|
||||
* Always, because you cannot set the PO header of a reader.
|
||||
*/
|
||||
function setHeader(PoHeader $header) {
|
||||
throw new \Exception('You cannot set the PO header in a reader.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and executes a database query based on options set earlier.
|
||||
*/
|
||||
private function loadStrings() {
|
||||
$langcode = $this->_langcode;
|
||||
$options = $this->_options;
|
||||
$conditions = array();
|
||||
|
||||
if (array_sum($options) == 0) {
|
||||
// If user asked to not include anything in the translation files,
|
||||
// that would not make sense, so just fall back on providing a template.
|
||||
$langcode = NULL;
|
||||
// Force option to get both translated and untranslated strings.
|
||||
$options['not_translated'] = TRUE;
|
||||
}
|
||||
// Build and execute query to collect source strings and translations.
|
||||
if (!empty($langcode)) {
|
||||
$conditions['language'] = $langcode;
|
||||
// Translate some options into field conditions.
|
||||
if ($options['customized']) {
|
||||
if (!$options['not_customized']) {
|
||||
// Filter for customized strings only.
|
||||
$conditions['customized'] = L10N_UPDATE_CUSTOMIZED;
|
||||
}
|
||||
// Else no filtering needed in this case.
|
||||
}
|
||||
else {
|
||||
if ($options['not_customized']) {
|
||||
// Filter for non-customized strings only.
|
||||
$conditions['customized'] = L10N_UPDATE_NOT_CUSTOMIZED;
|
||||
}
|
||||
else {
|
||||
// Filter for strings without translation.
|
||||
$conditions['translated'] = FALSE;
|
||||
}
|
||||
}
|
||||
if (!$options['not_translated']) {
|
||||
// Filter for string with translation.
|
||||
$conditions['translated'] = TRUE;
|
||||
}
|
||||
return $this->storage->getTranslations($conditions);
|
||||
}
|
||||
else {
|
||||
// If no language, we don't need any of the target fields.
|
||||
return $this->storage->getStrings($conditions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database result resource for the given language and options.
|
||||
*/
|
||||
private function readString() {
|
||||
if (!isset($this->_result)) {
|
||||
$this->_result = $this->loadStrings();
|
||||
}
|
||||
return array_shift($this->_result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoReaderInterface::readItem().
|
||||
*/
|
||||
function readItem() {
|
||||
if ($string = $this->readString()) {
|
||||
$values = (array)$string;
|
||||
$poItem = new PoItem();
|
||||
$poItem->setFromArray($values);
|
||||
return $poItem;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of PoDatabaseWriter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gettext PO writer working with the locale module database.
|
||||
*/
|
||||
class PoDatabaseWriter implements PoWriterInterface {
|
||||
|
||||
/**
|
||||
* An associative array indicating what data should be overwritten, if any.
|
||||
*
|
||||
* Elements of the array:
|
||||
* - override_options
|
||||
* - not_customized: boolean indicating that not customized strings should
|
||||
* be overwritten.
|
||||
* - customized: boolean indicating that customized strings should be
|
||||
* overwritten.
|
||||
* - customized: the strings being imported should be saved as customized.
|
||||
* One of L10N_UPDATE_CUSTOMIZED or L10N_UPDATE_NOT_CUSTOMIZED.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_options;
|
||||
|
||||
/**
|
||||
* Language code of the language being written to the database.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* Header of the po file written to the database.
|
||||
*
|
||||
* @var PoHeader
|
||||
*/
|
||||
private $_header;
|
||||
|
||||
/**
|
||||
* Associative array summarizing the number of changes done.
|
||||
*
|
||||
* Keys for the array:
|
||||
* - additions: number of source strings newly added
|
||||
* - updates: number of translations updated
|
||||
* - deletes: number of translations deleted
|
||||
* - skips: number of strings skipped due to disallowed HTML
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_report;
|
||||
|
||||
/**
|
||||
* Database storage to store the strings in.
|
||||
*
|
||||
* @var StringDatabaseStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Constructor, initialize reporting array.
|
||||
*/
|
||||
function __construct() {
|
||||
$this->setReport();
|
||||
$this->storage = new StringDatabaseStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getLangcode().
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setLangcode().
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the report of the write operations.
|
||||
*/
|
||||
public function getReport() {
|
||||
return $this->_report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the report array of write operations.
|
||||
*
|
||||
* @param array $report
|
||||
* Associative array with result information.
|
||||
*/
|
||||
function setReport($report = array()) {
|
||||
$report += array(
|
||||
'additions' => 0,
|
||||
'updates' => 0,
|
||||
'deletes' => 0,
|
||||
'skips' => 0,
|
||||
'strings' => array(),
|
||||
);
|
||||
$this->_report = $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options used by the writer.
|
||||
*/
|
||||
function getOptions() {
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options for the current writer.
|
||||
*/
|
||||
function setOptions(array $options) {
|
||||
if (!isset($options['overwrite_options'])) {
|
||||
$options['overwrite_options'] = array();
|
||||
}
|
||||
$options['overwrite_options'] += array(
|
||||
'not_customized' => FALSE,
|
||||
'customized' => FALSE,
|
||||
);
|
||||
$options += array(
|
||||
'customized' => L10N_UPDATE_NOT_CUSTOMIZED,
|
||||
);
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::getHeader().
|
||||
*/
|
||||
function getHeader() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoMetadataInterface::setHeader().
|
||||
*
|
||||
* Sets the header and configure Drupal accordingly.
|
||||
*
|
||||
* Before being able to process the given header we need to know in what
|
||||
* context this database write is done. For this the options must be set.
|
||||
*
|
||||
* A langcode is required to set the current header's PluralForm.
|
||||
*
|
||||
* @param PoHeader $header
|
||||
* Header metadata.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function setHeader(PoHeader $header) {
|
||||
$this->_header = $header;
|
||||
$languages = language_list();
|
||||
|
||||
// Check for options.
|
||||
$options = $this->getOptions();
|
||||
if (empty($options)) {
|
||||
throw new \Exception('Options should be set before assigning a PoHeader.');
|
||||
}
|
||||
$overwrite_options = $options['overwrite_options'];
|
||||
|
||||
// Check for langcode.
|
||||
$langcode = $this->_langcode;
|
||||
if (empty($langcode)) {
|
||||
throw new \Exception('Langcode should be set before assigning a PoHeader.');
|
||||
}
|
||||
|
||||
// Check is language is already created.
|
||||
if (!isset($languages[$langcode])) {
|
||||
throw new \Exception('Language should be known before using it.');
|
||||
}
|
||||
|
||||
if (array_sum($overwrite_options) || empty($languages[$langcode]->plurals)) {
|
||||
// Get and store the plural formula if available.
|
||||
$plural = $header->getPluralForms();
|
||||
if (isset($plural) && $p = $header->parsePluralForms($plural)) {
|
||||
list($nplurals, $formula) = $p;
|
||||
db_update('languages')
|
||||
->fields(array(
|
||||
'plurals' => $nplurals,
|
||||
'formula' => $formula,
|
||||
))
|
||||
->condition('language', $langcode)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItem().
|
||||
*/
|
||||
function writeItem(PoItem $item) {
|
||||
if ($item->isPlural()) {
|
||||
$item->setSource(join(L10N_UPDATE_PLURAL_DELIMITER, $item->getSource()));
|
||||
$item->setTranslation(join(L10N_UPDATE_PLURAL_DELIMITER, $item->getTranslation()));
|
||||
}
|
||||
$this->importString($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements PoWriterInterface::writeItems().
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports one string into the database.
|
||||
*
|
||||
* @param PoItem $item
|
||||
* The item being imported.
|
||||
*
|
||||
* @return int
|
||||
* The string ID of the existing string modified or the new string added.
|
||||
*/
|
||||
private function importString(PoItem $item) {
|
||||
// Initialize overwrite options if not set.
|
||||
$this->_options['overwrite_options'] += array(
|
||||
'not_customized' => FALSE,
|
||||
'customized' => FALSE,
|
||||
);
|
||||
$overwrite_options = $this->_options['overwrite_options'];
|
||||
$customized = $this->_options['customized'];
|
||||
|
||||
$context = $item->getContext();
|
||||
$source = $item->getSource();
|
||||
$translation = $item->getTranslation();
|
||||
|
||||
// Look up the source string and any existing translation.
|
||||
$strings = $this->storage->getTranslations(array(
|
||||
'language' => $this->_langcode,
|
||||
'source' => $source,
|
||||
'context' => $context
|
||||
));
|
||||
$string = reset($strings);
|
||||
|
||||
if (!empty($translation)) {
|
||||
// Skip this string unless it passes a check for dangerous code.
|
||||
if (!locale_string_is_safe($translation)) {
|
||||
watchdog('l10n_update', 'Import of string "%string" was skipped because of disallowed or malformed HTML.', array('%string' => $translation), WATCHDOG_ERROR);
|
||||
$this->_report['skips']++;
|
||||
return 0;
|
||||
}
|
||||
elseif ($string) {
|
||||
$string->setString($translation);
|
||||
if ($string->isNew()) {
|
||||
// No translation in this language.
|
||||
$string->setValues(array(
|
||||
'language' => $this->_langcode,
|
||||
'customized' => $customized
|
||||
));
|
||||
$string->save();
|
||||
$this->_report['additions']++;
|
||||
}
|
||||
elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
|
||||
// Translation exists, only overwrite if instructed.
|
||||
$string->customized = $customized;
|
||||
$string->save();
|
||||
$this->_report['updates']++;
|
||||
}
|
||||
$this->_report['strings'][] = $string->getId();
|
||||
return $string->lid;
|
||||
}
|
||||
else {
|
||||
// No such source string in the database yet.
|
||||
$string = $this->storage->createString(array('source' => $source, 'context' => $context))
|
||||
->save();
|
||||
$target = $this->storage->createTranslation(array(
|
||||
'lid' => $string->getId(),
|
||||
'language' => $this->_langcode,
|
||||
'translation' => $translation,
|
||||
'customized' => $customized,
|
||||
))->save();
|
||||
|
||||
$this->_report['additions']++;
|
||||
$this->_report['strings'][] = $string->getId();
|
||||
return $string->lid;
|
||||
}
|
||||
}
|
||||
elseif ($string && !$string->isNew() && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
|
||||
// Empty translation, remove existing if instructed.
|
||||
$string->delete();
|
||||
$this->_report['deletes']++;
|
||||
$this->_report['strings'][] = $string->lid;
|
||||
return $string->lid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of SourceString.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale source string object.
|
||||
*
|
||||
* This class represents a module-defined string value that is to be translated.
|
||||
* This string must at least contain a 'source' field, which is the raw source
|
||||
* value, and is assumed to be in English language.
|
||||
*/
|
||||
class SourceString extends StringBase {
|
||||
/**
|
||||
* Implements StringInterface::isSource().
|
||||
*/
|
||||
public function isSource() {
|
||||
return isset($this->source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::isTranslation().
|
||||
*/
|
||||
public function isTranslation() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LocaleString::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return isset($this->source) ? $this->source : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LocaleString::setString().
|
||||
*/
|
||||
public function setString($string) {
|
||||
$this->source = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LocaleString::isNew().
|
||||
*/
|
||||
public function isNew() {
|
||||
return empty($this->lid);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of StringBase.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale string base class.
|
||||
*
|
||||
* This is the base class to be used for locale string objects and contains
|
||||
* the common properties and methods for source and translation strings.
|
||||
*/
|
||||
abstract class StringBase implements StringInterface {
|
||||
/**
|
||||
* The string identifier.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $lid;
|
||||
|
||||
/**
|
||||
* The string locations indexed by type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $locations;
|
||||
|
||||
/**
|
||||
* The source string.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $source;
|
||||
|
||||
/**
|
||||
* The string context.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* The string version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $version;
|
||||
|
||||
/**
|
||||
* The locale storage this string comes from or is to be saved to.
|
||||
*
|
||||
* @var StringStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Constructs a new locale string object.
|
||||
*
|
||||
* @param object|array $values
|
||||
* Object or array with initial values.
|
||||
*/
|
||||
public function __construct($values = array()) {
|
||||
$this->setValues((array)$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getId().
|
||||
*/
|
||||
public function getId() {
|
||||
return isset($this->lid) ? $this->lid : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setId().
|
||||
*/
|
||||
public function setId($lid) {
|
||||
$this->lid = $lid;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getVersion().
|
||||
*/
|
||||
public function getVersion() {
|
||||
return isset($this->version) ? $this->version : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setVersion().
|
||||
*/
|
||||
public function setVersion($version) {
|
||||
$this->version = $version;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getPlurals().
|
||||
*/
|
||||
public function getPlurals() {
|
||||
return explode(L10N_UPDATE_PLURAL_DELIMITER, $this->getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setPlurals().
|
||||
*/
|
||||
public function setPlurals($plurals) {
|
||||
$this->setString(implode(L10N_UPDATE_PLURAL_DELIMITER, $plurals));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getStorage().
|
||||
*/
|
||||
public function getStorage() {
|
||||
return isset($this->storage) ? $this->storage : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setStorage().
|
||||
*/
|
||||
public function setStorage($storage) {
|
||||
$this->storage = $storage;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setValues().
|
||||
*/
|
||||
public function setValues(array $values, $override = TRUE) {
|
||||
foreach ($values as $key => $value) {
|
||||
if (property_exists($this, $key) && ($override || !isset($this->$key))) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getValues().
|
||||
*/
|
||||
public function getValues(array $fields) {
|
||||
$values = array();
|
||||
foreach ($fields as $field) {
|
||||
if (isset($this->$field)) {
|
||||
$values[$field] = $this->$field;
|
||||
}
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LocaleString::save().
|
||||
*/
|
||||
public function save() {
|
||||
if ($storage = $this->getStorage()) {
|
||||
$storage->save($this);
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be saved because its not bound to a storage: @string', array(
|
||||
'@string' => $this->getString()
|
||||
)));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements LocaleString::delete().
|
||||
*/
|
||||
public function delete() {
|
||||
if (!$this->isNew()) {
|
||||
if ($storage = $this->getStorage()) {
|
||||
$storage->delete($this);
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be deleted because its not bound to a storage: @string', array(
|
||||
'@string' => $this->getString()
|
||||
)));
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,518 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of StringDatabaseStorage.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale string class.
|
||||
*
|
||||
* This is the base class for SourceString and TranslationString.
|
||||
*/
|
||||
class StringDatabaseStorage implements StringStorageInterface {
|
||||
|
||||
/**
|
||||
* Additional database connection options to use in queries.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = array();
|
||||
|
||||
/**
|
||||
* Constructs a new StringStorage controller.
|
||||
*
|
||||
* @param array $options
|
||||
* (optional) Any additional database connection options to use in queries.
|
||||
*/
|
||||
public function __construct(array $options = array()) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::getStrings().
|
||||
*/
|
||||
public function getStrings(array $conditions = array(), array $options = array()) {
|
||||
return $this->dbStringLoad($conditions, $options, 'SourceString');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::getTranslations().
|
||||
*/
|
||||
public function getTranslations(array $conditions = array(), array $options = array()) {
|
||||
return $this->dbStringLoad($conditions, array('translation' => TRUE) + $options, 'TranslationString');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::findString().
|
||||
*/
|
||||
public function findString(array $conditions) {
|
||||
$values = $this->dbStringSelect($conditions)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
|
||||
if (!empty($values)) {
|
||||
$string = new SourceString($values);
|
||||
$string->setStorage($this);
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::findTranslation().
|
||||
*/
|
||||
public function findTranslation(array $conditions) {
|
||||
$values = $this->dbStringSelect($conditions, array('translation' => TRUE))
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
|
||||
if (!empty($values)) {
|
||||
$string = new TranslationString($values);
|
||||
$this->checkVersion($string, VERSION);
|
||||
$string->setStorage($this);
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::countStrings().
|
||||
*/
|
||||
public function countStrings() {
|
||||
return $this->dbExecute("SELECT COUNT(*) FROM {locales_source}")->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::countTranslations().
|
||||
*/
|
||||
public function countTranslations() {
|
||||
return $this->dbExecute("SELECT t.language, COUNT(*) AS translated FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid GROUP BY t.language")->fetchAllKeyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::save().
|
||||
*/
|
||||
public function save($string) {
|
||||
if ($string->isNew()) {
|
||||
$result = $this->dbStringInsert($string);
|
||||
if ($string->isSource() && $result) {
|
||||
// Only for source strings, we set the locale identifier.
|
||||
$string->setId($result);
|
||||
}
|
||||
$string->setStorage($this);
|
||||
}
|
||||
else {
|
||||
$this->dbStringUpdate($string);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the string version matches a given version, fix it if not.
|
||||
*
|
||||
* @param StringInterface $string
|
||||
* The string object.
|
||||
* @param string $version
|
||||
* Drupal version to check against.
|
||||
*/
|
||||
protected function checkVersion($string, $version) {
|
||||
if ($string->getId() && $string->getVersion() != $version) {
|
||||
$string->setVersion($version);
|
||||
db_update('locales_source', $this->options)
|
||||
->condition('lid', $string->getId())
|
||||
->fields(array('version' => $version))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::delete().
|
||||
*/
|
||||
public function delete($string) {
|
||||
if ($keys = $this->dbStringKeys($string)) {
|
||||
$this->dbDelete('locales_target', $keys)->execute();
|
||||
if ($string->isSource()) {
|
||||
$this->dbDelete('locales_source', $keys)->execute();
|
||||
$this->dbDelete('locales_location', $keys)->execute();
|
||||
$string->setId(NULL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be deleted because it lacks some key fields: @string', array(
|
||||
'@string' => $string->getString()
|
||||
)));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::deleteLanguage().
|
||||
*/
|
||||
public function deleteStrings($conditions) {
|
||||
$lids = $this->dbStringSelect($conditions, array('fields' => array('lid')))->execute()->fetchCol();
|
||||
if ($lids) {
|
||||
$this->dbDelete('locales_target', array('lid' => $lids))->execute();
|
||||
$this->dbDelete('locales_source', array('lid' => $lids))->execute();
|
||||
$this->dbDelete('locales_location', array('sid' => $lids))->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::deleteLanguage().
|
||||
*/
|
||||
public function deleteTranslations($conditions) {
|
||||
$this->dbDelete('locales_target', $conditions)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::createString().
|
||||
*/
|
||||
public function createString($values = array()) {
|
||||
return new SourceString($values + array('storage' => $this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringStorageInterface::createTranslation().
|
||||
*/
|
||||
public function createTranslation($values = array()) {
|
||||
return new TranslationString($values + array(
|
||||
'storage' => $this,
|
||||
'is_new' => TRUE
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets table alias for field.
|
||||
*
|
||||
* @param string $field
|
||||
* Field name to find the table alias for.
|
||||
*
|
||||
* @return string
|
||||
* Either 's', 't' or 'l' depending on whether the field belongs to source,
|
||||
* target or location table table.
|
||||
*/
|
||||
protected function dbFieldTable($field) {
|
||||
if (in_array($field, array('language', 'translation', 'customized'))) {
|
||||
return 't';
|
||||
}
|
||||
elseif (in_array($field, array('type', 'name'))) {
|
||||
return 'l';
|
||||
}
|
||||
else {
|
||||
return 's';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets table name for storing string object.
|
||||
*
|
||||
* @param StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return string
|
||||
* The table name.
|
||||
*/
|
||||
protected function dbStringTable($string) {
|
||||
if ($string->isSource()) {
|
||||
return 'locales_source';
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
return 'locales_target';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets keys values that are in a database table.
|
||||
*
|
||||
* @param StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return array
|
||||
* Array with key fields if the string has all keys, or empty array if not.
|
||||
*/
|
||||
protected function dbStringKeys($string) {
|
||||
if ($string->isSource()) {
|
||||
$keys = array('lid');
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$keys = array('lid', 'language');
|
||||
}
|
||||
if (!empty($keys) && ($values = $string->getValues($keys)) && count($keys) == count($values)) {
|
||||
return $values;
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads multiple string objects.
|
||||
*
|
||||
* @param array $conditions
|
||||
* Any of the conditions used by dbStringSelect().
|
||||
* @param array $options
|
||||
* Any of the options used by dbStringSelect().
|
||||
* @param string $class
|
||||
* Class name to use for fetching returned objects.
|
||||
*
|
||||
* @return array
|
||||
* Array of objects of the class requested.
|
||||
*/
|
||||
protected function dbStringLoad(array $conditions, array $options, $class) {
|
||||
$strings = array();
|
||||
$result = $this->dbStringSelect($conditions, $options)->execute();
|
||||
foreach ($result as $item) {
|
||||
$string = new $class($item);
|
||||
$string->setStorage($this);
|
||||
$strings[] = $string;
|
||||
}
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a SELECT query with multiple conditions and fields.
|
||||
*
|
||||
* The query uses both 'locales_source' and 'locales_target' tables.
|
||||
* Note that by default, as we are selecting both translated and untranslated
|
||||
* strings target field's conditions will be modified to match NULL rows too.
|
||||
*
|
||||
* @param array $conditions
|
||||
* An associative array with field => value conditions that may include
|
||||
* NULL values. If a language condition is included it will be used for
|
||||
* joining the 'locales_target' table.
|
||||
* @param array $options
|
||||
* An associative array of additional options. It may contain any of the
|
||||
* options used by StringStorageInterface::getStrings() and these additional
|
||||
* ones:
|
||||
* - 'translation', Whether to include translation fields too. Defaults to
|
||||
* FALSE.
|
||||
* @return SelectQuery
|
||||
* Query object with all the tables, fields and conditions.
|
||||
*/
|
||||
protected function dbStringSelect(array $conditions, array $options = array()) {
|
||||
// Change field 'customized' into 'l10n_status'. This enables the Drupal 8
|
||||
// backported code to work with the Drupal 7 style database tables.
|
||||
if (isset($conditions['customized'])) {
|
||||
$conditions['l10n_status'] = $conditions['customized'];
|
||||
unset($conditions['customized']);
|
||||
}
|
||||
if (isset($options['customized'])) {
|
||||
$options['l10n_status'] = $options['customized'];
|
||||
unset($options['customized']);
|
||||
}
|
||||
// Start building the query with source table and check whether we need to
|
||||
// join the target table too.
|
||||
$query = db_select('locales_source', 's', $this->options)
|
||||
->fields('s');
|
||||
|
||||
// Figure out how to join and translate some options into conditions.
|
||||
if (isset($conditions['translated'])) {
|
||||
// This is a meta-condition we need to translate into simple ones.
|
||||
if ($conditions['translated']) {
|
||||
// Select only translated strings.
|
||||
$join = 'innerJoin';
|
||||
}
|
||||
else {
|
||||
// Select only untranslated strings.
|
||||
$join = 'leftJoin';
|
||||
$conditions['translation'] = NULL;
|
||||
}
|
||||
unset($conditions['translated']);
|
||||
}
|
||||
else {
|
||||
$join = !empty($options['translation']) ? 'leftJoin' : FALSE;
|
||||
}
|
||||
|
||||
if ($join) {
|
||||
if (isset($conditions['language'])) {
|
||||
// If we've got a language condition, we use it for the join.
|
||||
$query->$join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(
|
||||
':langcode' => $conditions['language']
|
||||
));
|
||||
unset($conditions['language']);
|
||||
}
|
||||
else {
|
||||
// Since we don't have a language, join with locale id only.
|
||||
$query->$join('locales_target', 't', "t.lid = s.lid");
|
||||
}
|
||||
if (!empty($options['translation'])) {
|
||||
// We cannot just add all fields because 'lid' may get null values.
|
||||
$query->addField('t', 'language');
|
||||
$query->addField('t', 'translation');
|
||||
$query->addField('t', 'l10n_status', 'customized');
|
||||
}
|
||||
}
|
||||
|
||||
// If we have conditions for location's type or name, then we need the
|
||||
// location table, for which we add a subquery.
|
||||
if (isset($conditions['type']) || isset($conditions['name'])) {
|
||||
$subquery = db_select('locales_location', 'l', $this->options)
|
||||
->fields('l', array('sid'));
|
||||
foreach (array('type', 'name') as $field) {
|
||||
if (isset($conditions[$field])) {
|
||||
$subquery->condition('l.' . $field, $conditions[$field]);
|
||||
unset($conditions[$field]);
|
||||
}
|
||||
}
|
||||
$query->condition('s.lid', $subquery, 'IN');
|
||||
}
|
||||
|
||||
// Add conditions for both tables.
|
||||
foreach ($conditions as $field => $value) {
|
||||
$table_alias = $this->dbFieldTable($field);
|
||||
$field_alias = $table_alias . '.' . $field;
|
||||
if (is_null($value)) {
|
||||
$query->isNull($field_alias);
|
||||
}
|
||||
elseif ($table_alias == 't' && $join === 'leftJoin') {
|
||||
// Conditions for target fields when doing an outer join only make
|
||||
// sense if we add also OR field IS NULL.
|
||||
$query->condition(db_or()
|
||||
->condition($field_alias, $value)
|
||||
->isNull($field_alias)
|
||||
);
|
||||
}
|
||||
else {
|
||||
$query->condition($field_alias, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Process other options, string filter, query limit, etc...
|
||||
if (!empty($options['filters'])) {
|
||||
if (count($options['filters']) > 1) {
|
||||
$filter = db_or();
|
||||
$query->condition($filter);
|
||||
}
|
||||
else {
|
||||
// If we have a single filter, just add it to the query.
|
||||
$filter = $query;
|
||||
}
|
||||
foreach ($options['filters'] as $field => $string) {
|
||||
$filter->condition($this->dbFieldTable($field) . '.' . $field, '%' . db_like($string) . '%', 'LIKE');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($options['pager limit'])) {
|
||||
$query = $query->extend('PagerDefault')->limit($options['pager limit']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Createds a database record for a string object.
|
||||
*
|
||||
* @param StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return bool|int
|
||||
* If the operation failed, returns FALSE.
|
||||
* If it succeeded returns the last insert ID of the query, if one exists.
|
||||
*
|
||||
* @throws StringStorageException
|
||||
* If the string is not suitable for this storage, an exception ithrown.
|
||||
*/
|
||||
protected function dbStringInsert($string) {
|
||||
if ($string->isSource()) {
|
||||
$string->setValues(array('context' => '', 'version' => 'none'), FALSE);
|
||||
$fields = $string->getValues(array('source', 'context', 'version'));
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$string->setValues(array('customized' => 0), FALSE);
|
||||
$fields = $string->getValues(array('lid', 'language', 'translation', 'customized'));
|
||||
}
|
||||
if (!empty($fields)) {
|
||||
// Change field 'customized' into 'l10n_status'. This enables the Drupal 8
|
||||
// backported code to work with the Drupal 7 style database tables.
|
||||
if (isset($fields['customized'])) {
|
||||
$fields['l10n_status'] = $fields['customized'];
|
||||
unset($fields['customized']);
|
||||
}
|
||||
|
||||
return db_insert($this->dbStringTable($string), $this->options)
|
||||
->fields($fields)
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be saved: @string', array(
|
||||
'@string' => $string->getString()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates string object in the database.
|
||||
*
|
||||
* @param StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return bool|int
|
||||
* If the record update failed, returns FALSE. If it succeeded, returns
|
||||
* SAVED_NEW or SAVED_UPDATED.
|
||||
*
|
||||
* @throws StringStorageException
|
||||
* If the string is not suitable for this storage, an exception is thrown.
|
||||
*/
|
||||
protected function dbStringUpdate($string) {
|
||||
if ($string->isSource()) {
|
||||
$values = $string->getValues(array('source', 'context', 'version'));
|
||||
}
|
||||
elseif ($string->isTranslation()) {
|
||||
$values = $string->getValues(array('translation', 'customized'));
|
||||
}
|
||||
if (!empty($values) && $keys = $this->dbStringKeys($string)) {
|
||||
// Change field 'customized' into 'l10n_status'. This enables the Drupal 8
|
||||
// backported code to work with the Drupal 7 style database tables.
|
||||
if (isset($keys['customized'])) {
|
||||
$keys['l10n_status'] = $keys['customized'];
|
||||
unset($keys['customized']);
|
||||
}
|
||||
if (isset($values['customized'])) {
|
||||
$values['l10n_status'] = $values['customized'];
|
||||
unset($values['customized']);
|
||||
}
|
||||
|
||||
return db_merge($this->dbStringTable($string), $this->options)
|
||||
->key($keys)
|
||||
->fields($values)
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
throw new StringStorageException(format_string('The string cannot be updated: @string', array(
|
||||
'@string' => $string->getString()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates delete query.
|
||||
*
|
||||
* @param string $table
|
||||
* The table name.
|
||||
* @param array $keys
|
||||
* Array with object keys indexed by field name.
|
||||
*
|
||||
* @return DeleteQuery
|
||||
* Returns a new DeleteQuery object for the active database.
|
||||
*/
|
||||
protected function dbDelete($table, $keys) {
|
||||
$query = db_delete($table, $this->options);
|
||||
// Change field 'customized' into 'l10n_status'. This enables the Drupal 8
|
||||
// backported code to work with the Drupal 7 style database tables.
|
||||
if (isset($keys['customized'])) {
|
||||
$keys['l10n_status'] = $keys['customized'];
|
||||
unset($keys['customized']);
|
||||
}
|
||||
|
||||
foreach ($keys as $field => $value) {
|
||||
$query->condition($field, $value);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes an arbitrary SELECT query string.
|
||||
*/
|
||||
protected function dbExecute($query, array $args = array()) {
|
||||
return db_query($query, $args, $this->options);
|
||||
}
|
||||
}
|
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of StringInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale string interface.
|
||||
*/
|
||||
interface StringInterface {
|
||||
|
||||
/**
|
||||
* Gets the string unique identifier.
|
||||
*
|
||||
* @return int
|
||||
* The string identifier.
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* Sets the string unique identifier.
|
||||
*
|
||||
* @param int $id
|
||||
* The string identifier.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setId($id);
|
||||
|
||||
/**
|
||||
* Gets the string version.
|
||||
*
|
||||
* @return string
|
||||
* Version identifier.
|
||||
*/
|
||||
public function getVersion();
|
||||
|
||||
/**
|
||||
* Sets the string version.
|
||||
*
|
||||
* @param string $version
|
||||
* Version identifier.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setVersion($version);
|
||||
|
||||
/**
|
||||
* Gets plain string contained in this object.
|
||||
*
|
||||
* @return string
|
||||
* The string contained in this object.
|
||||
*/
|
||||
public function getString();
|
||||
|
||||
/**
|
||||
* Sets the string contained in this object.
|
||||
*
|
||||
* @param string $string
|
||||
* String to set as value.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setString($string);
|
||||
|
||||
/**
|
||||
* Splits string to work with plural values.
|
||||
*
|
||||
* @return array
|
||||
* Array of strings that are plural variants.
|
||||
*/
|
||||
public function getPlurals();
|
||||
|
||||
/**
|
||||
* Sets this string using array of plural values.
|
||||
*
|
||||
* Serializes plural variants in one string glued by L10N_UPDATE_PLURAL_DELIMITER.
|
||||
*
|
||||
* @param array $plurals
|
||||
* Array of strings with plural variants.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setPlurals($plurals);
|
||||
|
||||
/**
|
||||
* Gets the string storage.
|
||||
*
|
||||
* @return StringStorageInterface
|
||||
* The storage used for this string.
|
||||
*/
|
||||
public function getStorage();
|
||||
|
||||
/**
|
||||
* Sets the string storage.
|
||||
*
|
||||
* @param StringStorageInterface $storage
|
||||
* The storage to use for this string.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setStorage($storage);
|
||||
|
||||
/**
|
||||
* Checks whether the object is not saved to storage yet.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the object exists in the storage, FALSE otherwise.
|
||||
*/
|
||||
public function isNew();
|
||||
|
||||
/**
|
||||
* Checks whether the object is a source string.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the object is a source string, FALSE otherwise.
|
||||
*/
|
||||
public function isSource();
|
||||
|
||||
/**
|
||||
* Checks whether the object is a translation string.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the object is a translation string, FALSE otherwise.
|
||||
*/
|
||||
public function isTranslation();
|
||||
|
||||
/**
|
||||
* Sets an array of values as object properties.
|
||||
*
|
||||
* @param array $values
|
||||
* Array with values indexed by property name,
|
||||
* @param bool $override
|
||||
* (optional) Whether to override already set fields, defaults to TRUE.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*/
|
||||
public function setValues(array $values, $override = TRUE);
|
||||
|
||||
/**
|
||||
* Gets field values that are set for given field names.
|
||||
*
|
||||
* @param array $fields
|
||||
* Array of field names.
|
||||
*
|
||||
* @return array
|
||||
* Array of field values indexed by field name.
|
||||
*/
|
||||
public function getValues(array $fields);
|
||||
|
||||
/**
|
||||
* Saves string object to storage.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*
|
||||
* @throws StringStorageException
|
||||
* In case of failures, an exception is thrown.
|
||||
*/
|
||||
public function save();
|
||||
|
||||
/**
|
||||
* Deletes string object from storage.
|
||||
*
|
||||
* @return LocaleString
|
||||
* The called object.
|
||||
*
|
||||
* @throws StringStorageException
|
||||
* In case of failures, an exception is thrown.
|
||||
*/
|
||||
public function delete();
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Entity\StringStorageException.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines an exception thrown when storage operations fail.
|
||||
*/
|
||||
class StringStorageException extends \Exception { }
|
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \StringStorageInterface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale string storage interface.
|
||||
*/
|
||||
interface StringStorageInterface {
|
||||
|
||||
/**
|
||||
* Loads multiple source string objects.
|
||||
*
|
||||
* @param array $conditions
|
||||
* (optional) Array with conditions that will be used to filter the strings
|
||||
* returned and may include any of the following elements:
|
||||
* - Any simple field value indexed by field name.
|
||||
* - 'translated', TRUE to get only translated strings or FALSE to get only
|
||||
* untranslated strings. If not set it returns both translated and
|
||||
* untranslated strings that fit the other conditions.
|
||||
* Defaults to no conditions which means that it will load all strings.
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options. It may contain
|
||||
* any of the following optional keys:
|
||||
* - 'filters': Array of string filters indexed by field name.
|
||||
* - 'pager limit': Use pager and set this limit value.
|
||||
*
|
||||
* @return array
|
||||
* Array of \StringInterface objects matching the conditions.
|
||||
*/
|
||||
public function getStrings(array $conditions = array(), array $options = array());
|
||||
|
||||
/**
|
||||
* Loads multiple string translation objects.
|
||||
*
|
||||
* @param array $conditions
|
||||
* (optional) Array with conditions that will be used to filter the strings
|
||||
* returned and may include all of the conditions defined by getStrings().
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options. It may contain
|
||||
* any of the options defined by getStrings().
|
||||
*
|
||||
* @return array
|
||||
* Array of \StringInterface objects matching the conditions.
|
||||
*
|
||||
* @see StringStorageInterface::getStrings()
|
||||
*/
|
||||
public function getTranslations(array $conditions = array(), array $options = array());
|
||||
|
||||
/**
|
||||
* Loads a string source object, fast query.
|
||||
*
|
||||
* These 'fast query' methods are the ones in the critical path and their
|
||||
* implementation must be optimized for speed, as they may run many times
|
||||
* in a single page request.
|
||||
*
|
||||
* @param array $conditions
|
||||
* (optional) Array with conditions that will be used to filter the strings
|
||||
* returned and may include all of the conditions defined by getStrings().
|
||||
*
|
||||
* @return \SourceString|null
|
||||
* Minimal TranslationString object if found, NULL otherwise.
|
||||
*/
|
||||
public function findString(array $conditions);
|
||||
|
||||
/**
|
||||
* Loads a string translation object, fast query.
|
||||
*
|
||||
* This function must only be used when actually translating strings as it
|
||||
* will have the effect of updating the string version. For other purposes
|
||||
* the getTranslations() method should be used instead.
|
||||
*
|
||||
* @param array $conditions
|
||||
* (optional) Array with conditions that will be used to filter the strings
|
||||
* returned and may include all of the conditions defined by getStrings().
|
||||
*
|
||||
* @return \TranslationString|null
|
||||
* Minimal TranslationString object if found, NULL otherwise.
|
||||
*/
|
||||
public function findTranslation(array $conditions);
|
||||
|
||||
/**
|
||||
* Save string object to storage.
|
||||
*
|
||||
* @param \StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return \StringStorageInterface
|
||||
* The called object.
|
||||
*
|
||||
* @throws \StringStorageException
|
||||
* In case of failures, an exception is thrown.
|
||||
*/
|
||||
public function save($string);
|
||||
|
||||
/**
|
||||
* Delete string from storage.
|
||||
*
|
||||
* @param \StringInterface $string
|
||||
* The string object.
|
||||
*
|
||||
* @return \StringStorageInterface
|
||||
* The called object.
|
||||
*
|
||||
* @throws \StringStorageException
|
||||
* In case of failures, an exception is thrown.
|
||||
*/
|
||||
public function delete($string);
|
||||
|
||||
/**
|
||||
* Deletes source strings and translations using conditions.
|
||||
*
|
||||
* @param array $conditions
|
||||
* Array with simple field conditions for source strings.
|
||||
*/
|
||||
public function deleteStrings($conditions);
|
||||
|
||||
/**
|
||||
* Deletes translations using conditions.
|
||||
*
|
||||
* @param array $conditions
|
||||
* Array with simple field conditions for string translations.
|
||||
*/
|
||||
public function deleteTranslations($conditions);
|
||||
|
||||
/**
|
||||
* Counts source strings.
|
||||
*
|
||||
* @return int
|
||||
* The number of source strings contained in the storage.
|
||||
*/
|
||||
public function countStrings();
|
||||
|
||||
/**
|
||||
* Counts translations.
|
||||
*
|
||||
* @return array
|
||||
* The number of translations for each language indexed by language code.
|
||||
*/
|
||||
public function countTranslations();
|
||||
|
||||
/**
|
||||
* Creates a source string object bound to this storage but not saved.
|
||||
*
|
||||
* @param array $values
|
||||
* (optional) Array with initial values. Defaults to empty array.
|
||||
*
|
||||
* @return \SourceString
|
||||
* New source string object.
|
||||
*/
|
||||
public function createString($values = array());
|
||||
|
||||
/**
|
||||
* Creates a string translation object bound to this storage but not saved.
|
||||
*
|
||||
* @param array $values
|
||||
* (optional) Array with initial values. Defaults to empty array.
|
||||
*
|
||||
* @return \TranslationString
|
||||
* New string translation object.
|
||||
*/
|
||||
public function createTranslation($values = array());
|
||||
}
|
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of TranslationString.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines the locale translation string object.
|
||||
*
|
||||
* This class represents a translation of a source string to a given language,
|
||||
* thus it must have at least a 'language' which is the language code and a
|
||||
* 'translation' property which is the translated text of the the source string
|
||||
* in the specified language.
|
||||
*/
|
||||
class TranslationString extends StringBase {
|
||||
/**
|
||||
* The language code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $language;
|
||||
|
||||
/**
|
||||
* The string translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $translation;
|
||||
|
||||
/**
|
||||
* Integer indicating whether this string is customized.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $customized;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether the string object is new.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $is_new;
|
||||
|
||||
/**
|
||||
* Overrides StringBase::__construct().
|
||||
*/
|
||||
public function __construct($values = array()) {
|
||||
parent::__construct($values);
|
||||
if (!isset($this->is_new)) {
|
||||
// We mark the string as not new if it is a complete translation.
|
||||
// This will work when loading from database, otherwise the storage
|
||||
// controller that creates the string object must handle it.
|
||||
$this->is_new = !$this->isTranslation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string as customized / not customized.
|
||||
*
|
||||
* @param bool $customized
|
||||
* (optional) Whether the string is customized or not. Defaults to TRUE.
|
||||
*
|
||||
* @return TranslationString
|
||||
* The called object.
|
||||
*/
|
||||
public function setCustomized($customized = TRUE) {
|
||||
$this->customized = $customized ? L10N_UPDATE_CUSTOMIZED : L10N_UPDATE_NOT_CUSTOMIZED;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::isSource().
|
||||
*/
|
||||
public function isSource() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::isTranslation().
|
||||
*/
|
||||
public function isTranslation() {
|
||||
return !empty($this->lid) && !empty($this->language) && isset($this->translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::getString().
|
||||
*/
|
||||
public function getString() {
|
||||
return isset($this->translation) ? $this->translation : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::setString().
|
||||
*/
|
||||
public function setString($string) {
|
||||
$this->translation = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::isNew().
|
||||
*/
|
||||
public function isNew() {
|
||||
return $this->is_new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::save().
|
||||
*/
|
||||
public function save() {
|
||||
parent::save();
|
||||
$this->is_new = FALSE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements StringInterface::delete().
|
||||
*/
|
||||
public function delete() {
|
||||
parent::delete();
|
||||
$this->is_new = TRUE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of TranslationStreamWrapper.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Drupal interface translations (translations://) stream wrapper class.
|
||||
*
|
||||
* Supports storing translation files.
|
||||
*/
|
||||
class TranslationsStreamWrapper extends DrupalLocalStreamWrapper {
|
||||
/**
|
||||
* Implements abstract public function getDirectoryPath()
|
||||
*/
|
||||
public function getDirectoryPath() {
|
||||
return variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides getExternalUrl().
|
||||
*/
|
||||
function getExternalUrl() {
|
||||
throw new Exception('PO files URL should not be public.');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user