diff --git a/include/ajax.content.php b/include/ajax.content.php index b7c0700f27775e780467fef361bdac466835e351..68297e991fc891140c69c49ee714d9cd5cde943e 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -138,12 +138,12 @@ class ContentAjaxAPI extends AjaxController { $content = Page::lookup($id, $lang); $langs = $cfg->getSecondaryLanguages(); - $tag = $content->getTranslateTag('title:body'); - $translations = CustomDataTranslation::allTranslations($tag, 'article'); + $translations = $content->getAllTranslations(); foreach ($translations as $t) { - list($title, $body) = explode("\x04", $t->text); - $info['title'][$t->lang] = $title; - $info['body'][$t->lang] = $body; + if (!($data = $t->getComplex())) + continue; + $info['title'][$t->lang] = $data['name']; + $info['body'][$t->lang] = $data['body']; } include STAFFINC_DIR . 'templates/content-manage.tmpl.php'; diff --git a/include/class.page.php b/include/class.page.php index 5bf1e7b4e6b0cb54c0dbb8828024225375594b5b..7c29fab113a9b25406ddea40fb0102c6d40be4e5 100644 --- a/include/class.page.php +++ b/include/class.page.php @@ -80,26 +80,29 @@ class Page { return Format::viewableImages($this->getLocalBody(), ROOT_PATH.'image.php'); } - function _fetchTranslation($lang=false) { - if (!isset($this->_local) || $lang) { - $tag = $this->getTranslateTag('title:body'); - $this->_local = CustomDataTranslation::allTranslations($tag, 'article', $lang); - } - return $this->_local; - } function _getLocal($what, $lang=false) { - if (!$lang) + if (!$lang) { $lang = Internationalization::getCurrentLanguage(); - foreach ($this->_fetchTranslation($lang) as $t) { + } + $translations = $this->getAllTranslations(); + foreach ($translations as $t) { if ($lang == $t->lang) { - list($title, $body) = explode("\x04", $t->text, 2); - return $what == 'body' ? $body : $title; + $data = $t->getComplex(); + if (isset($data[$what])) + return $data[$what]; } } - return $this->ht[$what]; } + function getAllTranslations() { + if (!isset($this->_local)) { + $tag = $this->getTranslateTag('name:body'); + $this->_local = CustomDataTranslation::allTranslations($tag, 'article'); + } + return $this->_local; + } + function getNotes() { return $this->ht['notes']; } @@ -323,18 +326,21 @@ class Page { function saveTranslations($vars, &$errors) { global $thisstaff; - $tag = $this->getTranslateTag('title:body'); + $tag = $this->getTranslateTag('name:body'); $translations = CustomDataTranslation::allTranslations($tag, 'article'); foreach ($translations as $t) { $title = @$vars['trans'][$t->lang]['title']; $body = @$vars['trans'][$t->lang]['body']; if (!$title && !$body) continue; + // Content is not new and shouldn't be added below unset($vars['trans'][$t->lang]['title']); unset($vars['trans'][$t->lang]['body']); - $content = $title . "\x04" . Format::sanitize($body); - if ($content == $t->text) + $content = array('name' => $title, 'body' => Format::sanitize($body)); + + // Don't update content which wasn't updated + if ($content == $t->getComplex()) continue; $t->text = $content; @@ -345,10 +351,8 @@ class Page { } // New translations (?) foreach ($vars['trans'] as $lang=>$parts) { - $title = @$parts['title']; - $body = @$parts['body']; - $content = $title . "\x04" . Format::sanitize($body); - if ($content == "\x04") + $content = array('name' => @$parts['title'], 'body' => Format::sanitize(@$parts['body'])); + if (!array_filter($content)) continue; $t = CustomDataTranslation::create(array( 'type' => 'article', diff --git a/include/class.translation.php b/include/class.translation.php index 8951857b2ae5eb8be42fb91fc5d7f8ecc933fecf..141e13051c98dd709bce0a4bcc705a1aaba7751e 100644 --- a/include/class.translation.php +++ b/include/class.translation.php @@ -866,6 +866,9 @@ class CustomDataTranslation extends VerySimpleModel { const FLAG_FUZZY = 0x01; // Source string has been changed const FLAG_UNAPPROVED = 0x02; // String has been reviewed by an authority const FLAG_CURRENT = 0x04; // If more than one version exist, this is current + const FLAG_COMPLEX = 0x08; // Multiple strings in one translation. For instance article title and body + + var $_complex; static function lookup($msgid, $flags=0) { if (!is_string($msgid)) @@ -930,10 +933,92 @@ class CustomDataTranslation extends VerySimpleModel { return $msgid; } + /** + * Decode complex translation message. Format is given in the $text + * parameter description. Complex data should be stored with the + * FLAG_COMPLEX flag set, and allows for complex key:value paired data + * to be translated. This is useful for strings which are translated + * together, such as the title and the body of an article. Storing the + * data in a single, complex record allows for a single database query + * to fetch or update all data for a particular object, such as a + * knowledgebase article. It also simplifies search indexing as only one + * translation record could be added for all the translatable elements + * for a single translatable object. + * + * Caveats: + * ::$text will return the stored, complex text. Use ::getComplex() to + * decode the complex storage format and retrieve the array. + * + * Parameters: + * $text - (string) - encoded text with the following format + * version \x03 key \x03 item1 \x03 key \x03 item2 ... + * + * Returns: + * (array) key:value pairs of translated content + */ + function decodeComplex($text) { + $blocks = explode("\x03", $text); + $version = array_shift($blocks); + + $data = array(); + switch ($version) { + case 'A': + while (count($blocks) > 1) { + $key = array_shift($blocks); + $data[$key] = array_shift($blocks); + } + break; + default: + throw new Exception($version . ': Unknown complex format'); + } + + return $data; + } + + /** + * Encode complex content using the format outlined in ::decodeComplex. + * + * Caveats: + * This method does not set the FLAG_COMPLEX flag for this record, which + * should be set when storing complex data. + */ + static function encodeComplex(array $data) { + $encoded = 'A'; + foreach ($data as $key=>$text) { + $encoded .= "\x03{$key}\x03{$text}"; + } + return $encoded; + } + + function getComplex() { + if (!$this->flags && self::FLAG_COMPLEX) + throw new Exception('Data consistency error. Translation is not complex'); + if (!isset($this->_complex)) + $this->_complex = $this->decodeComplex($this->text); + return $this->_complex; + } + static function translateArticle($msgid, $locale=false) { return static::translate($msgid, $locale, false, 'article'); } + function save($refetch=false) { + if (isset($this->text) && is_array($this->text)) { + $this->text = static::encodeComplex($this->text); + $this->flags |= self::FLAG_COMPLEX; + } + return parent::save($refetch); + } + + static function create(array $ht=array()) { + if (is_array($ht['text'])) { + // The parent constructor does not honor arrays + $ht['text'] = static::encodeComplex($ht['text']); + $ht['flags'] = ($ht['flags'] ?: 0) | self::FLAG_COMPLEX; + } + return parent::create($ht); + } + static function allTranslations($msgid, $type='phrase', $lang=false) { $criteria = array('type' => $type);