You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1241 lines
42 KiB

<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
require_once(__DIR__ . '/../debug/Tracer.php');
//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
header("location: index.php");
exit;
}
/**
*
*/
class MultilingualLib extends TikiLib
{
public $mtEnabled = 'y';
/*
* Create the translation of wiki page $srcPageName.
*/
public function createTranslationOfPage($srcPageName, $srcLang, $targPageName, $targLang, $targPageContent)
{
global $tikilib, $user;
$tikilib->create_page($targPageName, 0, $targPageContent, null, '', null, $user, '', $targLang);
$this->insertTranslation('wiki page', $srcPageName, $srcLang, $targPageName, $targLang);
}
/**
* @brief add an object and its transaltion set into the set of translations of another one
* @param: type = (idem tiki_categ) 'wiki page'...
* @param: srcId = id of the source
* @param: srcLang = lang of the source
* @param: objId = id of the translation
* @param: objLang = lang of the translation
* @requirment: no translation of the source in this lang must exist
*/
public function insertTranslation($type, $srcId, $srcLang, $objId, $objLang)
{
global $prefs;
$srcTrads = $this->getTrads($type, $srcId);
$objTrads = $this->getTrads($type, $objId);
if (! $srcTrads && ! $objTrads) {
$query = "insert into `tiki_translated_objects` (`type`,`objId`,`lang`) values (?,?,?)";
$this->query($query, [$type, $srcId, $srcLang]);
$query = "select max(`traId`) from `tiki_translated_objects` where `type`=? and `objId`=?";
$tmp_traId = $this->getOne($query, [ $type, $srcId ]);
$query = "insert into `tiki_translated_objects` (`type`,`objId`,`traId`,`lang`) values (?,?,?,?)";
$this->query($query, [$type, $objId, $tmp_traId, $objLang]);
return null;
} elseif (! $srcTrads) {
if ($this->exist($objTrads, $srcLang, 'lang')) {
return "alreadyTrad";
}
$query = "insert into `tiki_translated_objects` (`type`,`objId`,`traId`,`lang`) values (?,?,?,?)";
$this->query($query, [$type, $srcId, $objTrads[0]['traId'], $srcLang]);
return null;
} elseif (! $objTrads) {
if ($this->exist($srcTrads, $objLang, 'lang')) {
return "alreadyTrad";
}
$query = "insert into `tiki_translated_objects` (`type`,`objId`,`traId`,`lang`) values (?,?,?,?)";
$this->query($query, [$type, $objId, $srcTrads[0]['traId'], $objLang]);
return null;
} elseif ($srcTrads[0]['traId'] == $objTrads[0]['traId']) {
return "alreadySet";
} else {
foreach ($srcTrads as $t) {
if ($this->exist($objTrads, $t['lang'], 'lang')) {
return "alreadyTrad";
}
}
$query = "update `tiki_translated_objects`set `traId`=? where `traId`=?";
$this->query = $this->query($query, [$srcTrads[0]['traId'], $objTrads[0]['traId']]);
return null;
}
}
/**
* @brief update the object for the language of a translation set
* @param $objId: new object for the translation of $srcId of type $type in the language $objLang
*/
public function updateTranslation($type, $srcId, $objId, $objLang)
{
$query = "update `tiki_translated_objects` set `objId`=? where `type`=? and `srcId`=? and `lang`=?";
$this->query($query, [$objId, $type, $srcId, $objLang]);
}
/**
* @brief get the translation in a language of an object if exists
* @return array(objId, traId)
*/
public function getTranslation($type, $srcId, $objLang)
{
$query =
"select t2.`objId`, t2.`traId`" .
" from `tiki_translated_objects` as t1, `tiki_translated_objects` as t2" .
" where t1.`traId`=t2.`traId` and t1.`type`=? and t1.`objId`=? and t2.`lang`=?";
return $this->getOne($query, [$type, $srcId, $objLang]);
}
/**
* @param $type
* @param $objId
* @return array
*/
public function getTrads($type, $objId)
{
$query =
"select t2.`traId`, t2.`objId`, t2.`lang`" .
" from `tiki_translated_objects` as t1, `tiki_translated_objects` as t2" .
" where t1.`traId`=t2.`traId` and t1.`type`=? and t1.`objId`=?";
$result = $this->query($query, [$type, (string) $objId]);
$ret = [];
while ($res = $result->fetchRow()) {
$ret[] = $res;
}
return $ret;
}
/*
* @brief gets all the translations of an object
* @param type = (idem tiki_categ) 'wiki page'...
* @param objId = object Id
* @param long = Whether the language name returned (langName) should be in long format
* @return: array(objId, objName, lang, langName) with langName=localized language name
*/
/**
* @param $type
* @param $objId
* @param string $objName
* @param string $objLang
* @param bool $long
* @return array
* @throws Exception
*/
public function getTranslations($type, $objId, $objName = '', $objLang = '', $long = false)
{
if ($type == 'wiki page') {
$query =
"select t2.`objId`, t2.`lang`, p.`pageName`as `objName`" .
" from `tiki_translated_objects` as t1, `tiki_translated_objects` as t2" .
" LEFT JOIN `tiki_pages` p ON p.`page_id`= t2.`objId`" .
" where t1.`traId`=t2.`traId` and t2.`objId`!= t1.`objId` and t1.`type`=? and t1.`objId`=?";
} elseif ($type == 'article') {
$query =
"select t2.`objId`, t2.`lang`, a.`title` as `objName`" .
" from `tiki_translated_objects` as t1, `tiki_translated_objects` as t2, `tiki_articles` as a" .
" where t1.`traId`=t2.`traId` and t2.`objId`!= t1.`objId` and t1.`type`=? and t1.`objId`=? and a.`articleId`=t2.`objId`";
} else {
throw new Exception("Unsupported type");
// Generic version, should set objName
//$query = "select t2.`objId`, t2.`lang` from `tiki_translated_objects` as t1, `tiki_translated_objects` as t2 where t1.`traId`=t2.`traId` and t2.`objId`!= t1.`objId` and t1.`type`=? and t1.`objId`=?";
}
$result = $this->query($query, [$type, $objId]);
$ret = [];
$langLib = TikiLib::lib('language');
$l = $langLib->format_language_list([$objLang], $long ? 'n' : 'y');
$ret0 = ['objId' => $objId, 'objName' => $objName, 'lang' => $objLang, 'langName' => empty($l) ? '' : $l[0]['name']];
while ($res = $result->fetchRow()) {
$l = $langLib->format_language_list([$res['lang']], $long ? 'n' : 'y');
$res['langName'] = $l[0]['name'];
$ret[] = $res;
}
usort($ret, ['MultilingualLib', 'compare_lang']);
array_unshift($ret, $ret0);
return $ret;
}
/**
* @brief sort function on langName string
*/
public function compare_lang($l1, $l2)
{
return strcmp($l1['langName'], $l2['langName']);
}
/**
* @brief: update lang in all tiki pages
*/
public function updateObjectLang($type, $objId, $lang, $optimisation = false)
{
if ($this->getTranslation($type, $objId, $lang)) {
return 'alreadyTrad';
}
if (! $optimisation) {
if ($type == 'wiki page') {
$query = "update `tiki_pages` set `lang`=? where `page_id`=?";
$this->query($query, [$lang, $objId]);
} elseif ($type == 'article') {
$query = "update `tiki_articles` set `lang`=? where `articleId`=?";
$this->query($query, [$lang, $objId]);
}
}
$query = "update `tiki_translated_objects` set `lang`=? where `objId`=? and `type`=?";
$this->query($query, [$lang, $objId, $type]);
return null;
}
/**
* @brief: detach one translation
*/
public function detachTranslation(string $type, string $objId)
{
$query = "delete from `tiki_translated_objects` where `type`= ? and `objId`=?";
$this->query($query, [$type, $objId]);
//@@TODO: delete the set if only one remaining object - not necesary but will clean the table
}
/**
* @brief : test if val exists in a list of objects
*/
public function exist($tab, $val, $col)
{
foreach ($tab as $t) {
if ($t[$col] == $val) {
return true;
}
}
return false;
}
/**
* @brief : returns an ordered list of preferred languages
* @param $langContext: optional the language the user comes from
*/
public function preferredLangs($langContext = null, $include_browser_lang = null)
{
global $user, $prefs, $tikilib;
$langs = [];
if ($include_browser_lang === null) {
$include_browser_lang = ($prefs['feature_detect_language'] === 'y');
}
if ($langContext) {
$langs[] = $langContext;
if (strchr($langContext, "-")) { // add en if en-uk
$langs[] = $this->rootLang($langContext);
}
}
if ($prefs['language'] && ! in_array($prefs['language'], $langs)) {
$langs[] = $prefs['language'];
$l = $this->rootLang($prefs['language']);
if (! in_array($l, $langs)) {
$langs[] = $l;
}
}
if (isset($prefs['read_language'])) {
$tok = strtok($prefs['read_language'], ' ');
while (false !== $tok) {
if (! in_array($tok, $langs)) {
$langs[] = $tok;
}
$l = $this->rootLang($tok);
if (! in_array($l, $langs)) {
$langs[] = $l;
}
$tok = strtok(' ');
}
}
if (($include_browser_lang) && (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))) {
$ls = preg_split('/\s*,\s*/', preg_replace('/;q=[0-9.]+/', '', $_SERVER['HTTP_ACCEPT_LANGUAGE'])); // browser
foreach ($ls as $l) {
if (! in_array($l, $langs)) {
$langs[] = $l;
$l = $this->rootLang($l);
if (! in_array($l, $langs)) {
$langs[] = $l;
}
}
}
}
$l = $prefs['site_language'];
if (! in_array($l, $langs)) {
$langs[] = $l; // site language
$l = $this->rootLang($l);
if (! in_array($l, $langs)) {
$langs[] = $l;
}
}
if ($prefs['restrict_language'] === 'y' && $prefs['available_languages'] && $prefs['language_inclusion_threshold'] >= count($prefs['available_languages'])) {
foreach (array_diff($prefs['available_languages'], $langs) as $lang) {
$langs[] = $lang;
}
}
return $langs;
}
/**
* @brief : return the root language ex: en-uk returns en
*/
public function rootLang($lang)
{
return preg_replace("/(.*)-(.*)/", '$1', $lang);
}
/**
* @brief : fitler a list of object to have only one objet in the set of translations with the best language
*/
public function selectLangList($type, $listObjs, $langContext = null)
{
if (! $listObjs || count($listObjs) <= 1) {
return $listObjs;
}
$langs = $this->preferredLangs($langContext);
$max = count($listObjs);
for ($i = 0; $i < $max; ++$i) {
if (! isset($listObjs[$i]) || ! isset($listObjs[$i]['lang'])) {
continue; // previously withdrawn or no language
}
if ($type == 'wiki page') {
$objId = $listObjs[$i]['page_id'];
} elseif ($type == 'objId') {
$objId = $listObjs[$i]['objId'];
} else {
$objId = $listObjs[$i]['articleId'];
}
$trads = $this->getTrads($type, $objId);
if (! $trads) {
continue;
}
for ($j = $i + 1; $j < $max; ++$j) {
if (! isset($listObjs[$j])) {
continue;
}
if ($type == 'wiki page') {
$objId2 = $listObjs[$j]['page_id'];
} elseif ($type == 'objId') {
$objId2 = $listObjs[$j]['objId'];
} else {
$objId2 = $listObjs[$j]['articleId'];
}
if ($this->exist($trads, $objId2, 'objId')) {
$iord = array_search($listObjs[$i]['lang'], $langs);
if (! $iord && strchr($listObjs[$i]['lang'], "-")) {
$iord = array_search($this->rootLang($listObjs[$i]['lang']), $langs);
}
$jord = array_search($listObjs[$j]['lang'], $langs);
if (! $jord && strchr($listObjs[$j]['lang'], "-")) {
$jord = array_search($this->rootLang($listObjs[$j]['lang']), $langs);
}
if ($jord === false) {
unset($listObjs[$j]); // not in the pref langs
} elseif ($iord === false) {
unset($listObjs[$i]);
break;
} elseif ($iord > $jord) {
unset($listObjs[$i]);
break;
} else {
unset($listObjs[$j]);
}
// if none in the pref lang, pick the first (sorted by date)
}
}
}
return array_merge($listObjs);// take away the unset rows
}
/**
* @brief : select the object with the best language from another object
*/
public function selectLangObj($type, $objId, $langContext = null)
{
$trads = $this->getTrads($type, $objId);
if (! $trads) {
return $objId;
}
$langs = $this->preferredLangs($langContext);
foreach ($langs as $l) {
foreach ($trads as $trad) {
if ($trad['lang'] == $l) {
return $trad['objId'];
}
}
}
return $objId;
}
/**
* Determine if the best language should be used for an object, based on request and preference parameters,
*/
public function useBestLanguage()
{
/*
* Indicates whether or not content should be displayed in the user's preferred language
* (as expressed in either its Tiki or browser language preferences).
*/
global $prefs, $_REQUEST;
if ($prefs['feature_multilingual'] == 'n') {
return false;
}
if ($prefs['feature_best_language'] == 'n' && ! isset($_REQUEST['bl'])) {
// If bl is explicitly set then for backward compatibility reasons let through even if feature is off.
return false;
}
if (isset($_REQUEST['no_bl']) && $_REQUEST['no_bl'] == 'y') {
return false; // no_bl is the new flag. It has to be specified as y to have any effect
}
if (isset($_REQUEST['bl']) && $_REQUEST['bl'] == 'n') {
return false; // the old bl check was default yes once present
}
/*
* Alain Désilets (2010-01-12):
*
* There is also a bl= argument, but it seems too be used very inconsistenly.
* - Sometimes, the mere presence of bl (no matter its value) s interpreted as meaning
* that bBest Language should be used.
* - In other cases, we set bl=n, presumably to signifiy that Best Language should not be used.
* - Yet, in in lib/setup/language.php, there is a statement which, if unsets bl, if its value was n, so
* not clear that all the checks for bl=n are doing anything.
* - If the purpose of bl is to indicate that Best Language is to be used (when bl is defined),
* then it's kind of weird, because Best Language cannot be used when multilingual features
* or best_languge or detec_language are inactive. Yet, when one of those is active, Best Language
* is always used, so there is no need to say that with an argument. There may be a need to
* say that in a particular case, Best Language should NOT be used, but not to say that Best Language
* SHOULD be used.
* -- extra note by nkoth: I've cleaned this up - so all the checking is done here now.
*/
return true;
}
/**
* @param $url
* @param $no_bl_value
* @return mixed|string
*/
public function setUrlNoBestLanguageArg($url, $no_bl_value)
{
if (preg_match('/[?&]no_bl=/', $url)) {
$url = preg_replace('/([?&])no_bl=[yn]{0,1}/', '$1no_bl=$no_bl_value', $url);
} elseif (! preg_match('/[?&]lang=/', $url)) {
if (strstr($url, '?')) {
$url .= '&no_bl=$no_bl_value';
} else {
$url .= '?no_bl=$no_bl_value';
}
}
return $url;
}
/**
* @return array
*/
public function getSupportedTranslationBitFlags()
{
return [ 'critical' ];
}
/**
* @param $flags
* @return array
*/
public function normalizeTranslationBitFlags($flags)
{
if (! is_array($flags)) {
$flags = explode(',', $flags);
}
// Add supported flags as they get added
return array_intersect($flags, $this->getSupportedTranslationBitFlags());
}
/**
* @param $type
* @param $objId
* @param int $version
* @param array $flags
*/
public function createTranslationBit($type, $objId, $version = 0, $flags = [])
{
if ($type != 'wiki page') {
die('Translation sync only available for wiki pages.');
}
$flags = $this->normalizeTranslationBitFlags($flags);
$flags = implode(',', $flags);
if ($version == 0) {
$info = $this->get_page_info_from_id($objId);
$version = $info['version'];
}
$this->query(
"INSERT
INTO tiki_pages_translation_bits (`page_id`, `version`,`flags` )
VALUES(?, ?, ?)",
[ (int) $objId, (int) $version, $flags ]
);
}
/**
* @param $type
* @param $sourceId
* @param $targetId
* @param int $sourceVersion
* @param int $targetVersion
*/
public function propagateTranslationBits($type, $sourceId, $targetId, $sourceVersion = 0, $targetVersion = 0)
{
if ($type != 'wiki page') {
die('Translation sync only available for wiki pages.');
}
// TODO : Add a check to make sure both pages are in the same translation set
$sourceId = (int) $sourceId;
$sourceVersion = (int) $sourceVersion;
$targetId = (int) $targetId;
$targetVersion = (int) $targetVersion;
if ($sourceVersion == 0) {
$info = $this->get_page_info_from_id($sourceId);
$sourceVersion = (int) $info['version'];
}
if ($targetVersion == 0) {
$info = $this->get_page_info_from_id($targetId);
$targetVersion = (int) $info['version'];
}
/*
Fetch the list of translation bits from the source available in
the selected version. From the list, exclude those that originated
from the target or were already incorporated in a previous update.
*/
$result = $this->query(
"SELECT translation_bit_id, original_translation_bit, flags
FROM tiki_pages_translation_bits
WHERE
page_id = ?
AND version <= ?
AND original_translation_bit IS NULL
AND translation_bit_id NOT IN(
SELECT original_translation_bit
FROM tiki_pages_translation_bits
WHERE page_id = ? AND original_translation_bit IS NOT NULL
)
UNION
SELECT translation_bit_id, original_translation_bit, flags
FROM tiki_pages_translation_bits
WHERE
page_id = ?
AND version <= ?
AND original_translation_bit IS NOT NULL
AND original_translation_bit NOT IN(
SELECT translation_bit_id
FROM tiki_pages_translation_bits
WHERE page_id = ?
)
AND original_translation_bit NOT IN(
SELECT original_translation_bit
FROM tiki_pages_translation_bits
WHERE page_id = ? AND original_translation_bit IS NOT NULL
)",
[ $sourceId, $sourceVersion, $targetId, $sourceId, $sourceVersion, $targetId, $targetId ]
);
$query =
"INSERT INTO tiki_pages_translation_bits (
page_id,
version,
source_translation_bit,
original_translation_bit,
flags)
VALUES( ?, ?, ?, ?, ? )";
while ($row = $result->fetchRow()) {
if (empty($row['original_translation_bit'])) {
// The translation bit is the original one
$this->query(
$query,
[
$targetId,
$targetVersion,
$row['translation_bit_id'],
$row['translation_bit_id'],
$row['flags']
]
);
} else {
// The transation bit was propagated to the source
$this->query(
$query,
[
$targetId,
$targetVersion,
$row['translation_bit_id'],
$row['original_translation_bit'],
$row['flags']
]
);
}
}
}
/**
* @param $type
* @param $objId
* @param array $flags
* @param bool $page_unique
* @return array
*/
public function getMissingTranslationBits($type, $objId, $flags = [], $page_unique = false)
{
if ($type != 'wiki page') {
die('Translation sync only available for wiki pages.');
}
$objId = (int) $objId;
$flags = $this->normalizeTranslationBitFlags($flags);
$conditions = [ '1 = 1' ];
foreach ($flags as $flag) {
$conditions[] = "( FIND_IN_SET('$flag', bits.flags) > 0 )";
}
$conditions = implode(' AND ', $conditions);
$result = $this->query(
"SELECT
bits.translation_bit_id, bits.page_id
FROM
tiki_translated_objects a
INNER JOIN tiki_translated_objects b ON a.`traId` = b.`traId` AND a.`objId` <> b.`objId`
INNER JOIN tiki_pages_translation_bits bits ON b.`objId` = bits.page_id
LEFT JOIN tiki_pages_translation_bits self
ON bits.`translation_bit_id` = self.`original_translation_bit` AND self.`page_id` = ?
WHERE
a.`type` = 'wiki page'
AND b.`type` = 'wiki page'
AND a.`objId` = ?
AND bits.`original_translation_bit` IS NULL
AND self.`original_translation_bit` IS NULL
AND $conditions",
[ $objId, $objId ]
);
$bits = [];
while ($row = $result->fetchRow()) {
if ($page_unique) {
$bits[$row['bits.page_id']] = $row['translation_bit_id'];
} else {
$bits[] = $row['translation_bit_id'];
}
}
return $bits;
}
/**
* @param $translationBit
* @param $pageIdToUpdate
* @return array
*/
public function getTranslationsWithBit($translationBit, $pageIdToUpdate)
{
$pageIdToUpdate = (int) $pageIdToUpdate;
$translationBit = (int) $translationBit;
$result = $this->query(
"SELECT
`pageName` page,
lang,"
. $this->subqueryObtainUpdateVersion('pages.page_id', '?') . " last_update,
pages.version current_version
FROM
tiki_pages_translation_bits bits
INNER JOIN tiki_pages pages ON pages.page_id = bits.page_id
WHERE
translation_bit_id = ? OR original_translation_bit = ?",
[ $pageIdToUpdate, $translationBit, $translationBit ]
);
$pages = [];
global $prefs;
while ($row = $result->fetchRow()) {
if ($row['lang'] == $prefs['site_language']) {
$pages[] = $row;
}
}
return $pages;
}
/**
* @param $pageId
* @return array
*/
public function getSourceHistory($pageId)
{
$result = $this->query(
"SELECT DISTINCT
target.version as `group`,
page.page_id,
page.pageName as page,
MAX(source.version) as version
FROM
tiki_pages_translation_bits source
INNER JOIN tiki_pages_translation_bits target ON source.translation_bit_id = target.source_translation_bit
INNER JOIN tiki_pages page ON source.page_id = page.page_id
WHERE
target.page_id = ?
GROUP BY target.version, page.page_id, page.pageName",
[ $pageId ]
);
$list = [];
while ($row = $result->fetchRow()) {
$group = $row['group'];
if (! array_key_exists($group, $list)) {
$list[$group] = [];
}
$list[$group][] = $row;
}
return $list;
}
/**
* @param $pageId
* @return array
*/
public function getTargetHistory($pageId)
{
$result = $this->query(
"SELECT DISTINCT
MAX(source.version) as `group`,
page.page_id,
page.pageName as page,
target.version as version
FROM
tiki_pages_translation_bits source
INNER JOIN tiki_pages_translation_bits target ON source.translation_bit_id = target.source_translation_bit
INNER JOIN tiki_pages page ON target.page_id = page.page_id
WHERE
source.page_id = ?
GROUP BY page.page_id, target.version, page.pageName",
[ $pageId ]
);
$list = [];
while ($row = $result->fetchRow()) {
$group = $row['group'];
if (! array_key_exists($group, $list)) {
$list[$group] = [];
}
$list[$group][] = $row;
}
return $list;
}
/**
* @param $sourcePage
* @param $targetPage
* @return string
*/
public function subqueryObtainUpdateVersion($sourcePage, $targetPage)
{
// Meant to be inlined in an other query. Useful in many cases.
/*
Fetches the lowest version of source containing a bit not present
in target.
-1 is made on the version so the diff is made properly.
IFNULL defaults to 2 so no result is turned back to 1
If the actual version returned is 1, 1 should be returned and not 0.
*/
return "(
SELECT
IFNULL( IF(MIN(version) = 1, 0, MIN(version)), 0 ) - 1
FROM
tiki_pages_translation_bits
WHERE
page_id = $sourcePage
AND IFNULL( original_translation_bit, translation_bit_id ) NOT IN(
SELECT IFNULL( original_translation_bit, translation_bit_id )
FROM tiki_pages_translation_bits
WHERE page_id = $targetPage)
)";
}
/**
* @param $pageId
* @return array
*/
public function getBetterPages($pageId)
{
$pageId = (int) $pageId;
$query = "
SELECT DISTINCT
page.page_id,
page.pageName page,
" . $this->subqueryObtainUpdateVersion('a.objId', 'b.objId') . " last_update,
page.version current_version,
page.lang
FROM
tiki_translated_objects a
INNER JOIN tiki_translated_objects b ON a.traId = b.traId AND a.objId <> b.objId
INNER JOIN tiki_pages page ON page.page_id = a.objId
INNER JOIN tiki_pages_translation_bits candidate ON candidate.page_id = page.page_id
WHERE
a.type = 'wiki page'
AND b.type = 'wiki page'
AND b.objId = ?
AND IFNULL( candidate.original_translation_bit, candidate.translation_bit_id ) NOT IN(
SELECT IFNULL( original_translation_bit, translation_bit_id )
FROM tiki_pages_translation_bits
WHERE page_id = b.objId
)";
$result = $this->query($query, [ $pageId ]);
$pages = [];
while ($row = $result->fetchRow()) {
$pages[] = $row;
}
return $pages;
}
/**
* @param $pageId
* @return array
*/
public function getWorstPages($pageId)
{
$pageId = (int) $pageId;
$result = $this->query(
"SELECT DISTINCT
page.page_id,
page.pageName page,"
. $this->subqueryObtainUpdateVersion('b.objId', 'a.objId')
. " last_update,
page.lang
FROM
tiki_pages page
INNER JOIN tiki_translated_objects a ON a.objId = page.page_id
INNER JOIN tiki_translated_objects b ON a.traId = b.traId AND a.objId <> b.objId
WHERE
a.type = 'wiki page'
AND b.type = 'wiki page'
AND b.objId = ?
AND (
SELECT COUNT(*)
FROM tiki_pages_translation_bits
WHERE page_id = b.objId
) > (
SELECT COUNT(*)
FROM
tiki_pages_translation_bits self
INNER JOIN tiki_pages_translation_bits candidate
ON IFNULL(self.original_translation_bit, self.translation_bit_id)
= IFNULL(candidate.original_translation_bit, candidate.translation_bit_id)
WHERE
self.page_id = b.objId AND candidate.page_id = a.objId
)",
[ $pageId ]
);
$pages = [];
while ($row = $result->fetchRow()) {
$pages[] = $row;
}
return $pages;
}
/**
* @param $pageId
* @param $version
* @return array
*/
public function get_page_bit_flags($pageId, $version)
{
$query = "select distinct `flags` from `tiki_pages_translation_bits` where `page_id`=? and `version`=?";
$result = $this->query($query, [$pageId, $version]);
$flags = [];
while ($row = $result->fetchRow()) {
$flags[] = $row['flags'];
}
return $flags;
}
/**
* @param $pageName
* @return mixed
*/
public function getLangOfPage($pageName)
{
$pageInfo = $this->get_page_info($pageName);
$lang = $pageInfo['lang'];
return $lang;
}
/**
* @return string
*/
public function currentPageSearchLanguage()
{
/*
* Returns the language to be used for a normal page find.
*/
global $_REQUEST, $_SESSION;
$lang = '';
// First look in HTTP 'lang' argument
if (isset($_REQUEST['lang'])) { //lang='' means all languages
$lang = $_REQUEST['lang'];
}
return $lang;
}
/**
* Returns the language to be used for a Term search (terminology module).
*
* @access public
*/
public function currentTermSearchLanguage()
{
global $_REQUEST, $_SESSION;
$lang = '';
if (isset($_REQUEST['term_src']) && isset($_REQUEST['lang'])) {
$lang = $_REQUEST['lang'];
}
if ($lang == '' && array_key_exists('find_term_last_done_in_lang', $_SESSION)) {
$lang = $_SESSION['find_term_last_done_in_lang'];
}
// Remember language of this term search.
$this->storeCurrentTermSearchLanguageInSession($lang);
return $lang;
}
/**
* @param $lang
*/
public function storeCurrentTermSearchLanguageInSession($lang)
{
global $_SESSION;
$_SESSION['find_term_last_done_in_lang'] = $lang;
}
/**
* @return array
*/
public function preferredLangsInfo()
{
global $tikilib, $tracer;
// Get IDs of user's preferred languages
$userLangIDs = $this->preferredLangs();
// Get information about ALL languages supported by Tiki
$langLib = TikiLib::lib('language');
$allLangsInfo = $langLib->list_languages(false, 'y');
// Create a map of language ID (ex: 'en') to language info
$langIDs2Info = [];
foreach ($allLangsInfo as $someLangInfo) {
$langIDs2Info[$someLangInfo['value']] = $someLangInfo;
}
// Create list of language IDs AND names for user's preferred
// languages.
$userLangsInfo = [];
$lang_index = 0;
foreach ($userLangIDs as $index => $someUserLangID) {
if ($langIDs2Info[$someUserLangID] != null) {
$userLangsInfo[$lang_index] = $langIDs2Info[$someUserLangID];
$lang_index++;
}
}
return $userLangsInfo;
}
/**
* @param $section
* @param $template_name
* @param $language
* @return null
*/
public function getTemplateIDInLanguage($section, $template_name, $language)
{
$templateslib = TikiLib::lib('template');
$all_templates = $templateslib->list_templates($section, 0, -1, 'name_asc', '');
$looking_for_templates_named = ["$template_name-$language"];
foreach ($looking_for_templates_named as $looking_for_this_template) {
$looking_for_this_template = "$template_name-$language";
foreach ($all_templates['data'] as $a_template) {
$a_template_name = $a_template['name'];
if ($a_template_name == $looking_for_this_template) {
return $a_template['templateId'];
}
}
}
return null;
}
/**
* @param $on_or_off
*/
public function setMachineTranslationFeatureTo($on_or_off)
{
$this->mtEnabled = $on_or_off;
}
/**
* @param $page_id
* @param null $language
* @return mixed
*/
public function getTranslationsInProgressFlags($page_id, $language = null)
{
$fields = '`page_id`';
$valuesSpec = "?";
$values = [$page_id];
if ($language) {
$fields .= ', `language`';
$valuesSpec .= ", ?";
$values[] = $language;
}
$query = "select `language` from `tiki_translations_in_progress` where ($fields)=($valuesSpec)";
$flags = $this->fetchAll($query, $values);
return $flags;
}
/**
* @param $page_id
* @param $language
*/
public function addTranslationInProgressFlags($page_id, $language)
{
//
// First, make sure that there isn't already a row in the table
// capturing the fact that this page is being translated from that language
//
$translationInProgressForThatLanguage = $this->getTranslationsInProgressFlags($page_id, $language);
if (count($translationInProgressForThatLanguage) == 0) {
$query = "insert into `tiki_translations_in_progress` (`page_id`,`language`) values (?,?)";
$results = $this->query($query, [$page_id, $language]);
}
}
/**
* @param $page_id
* @param $language
*/
public function deleteTranslationInProgressFlags($page_id, $language)
{
$query =
"DELETE FROM `tiki_translations_in_progress`\n" .
" WHERE (`page_id`, `language`) = (?, ?)";
$results = $this->query($query, [$page_id, $language]);
}
/**
* @param $objectType
* @param $sqlObjectId
* @param $columnObjectId
* @param $langs
* @param $join
* @param $mid
* @param $bindvars
*/
public function sqlTranslationOrphan($objectType, $sqlObjectId, $columnObjectId, $langs, &$join, &$mid, &$bindvars)
{
$join .= " left join `tiki_translated_objects` tro on (tro.`type` = '$objectType' AND tro.`objId` = $sqlObjectId.`$columnObjectId`) ";
$translationOrphan_mid = " tro.`traId` IS NULL OR $sqlObjectId.`lang`IS NULL ";
foreach ($langs as $i => $lg) {
$join .= " left join `tiki_translated_objects` tro_$i on (tro_$i.`traId` = tro.`traId` AND tro_$i.`lang`=?) ";
$translationOrphan_mid .= " OR tro_$i.`traId` IS NULL ";
$bindvars[] = $lg;
}
if (! empty($mid)) {
$mid .= ' AND ';
}
$mid .= "($translationOrphan_mid)";
if (count($langs) == 1) {
$mid .= " AND ($sqlObjectId.`lang` != ? OR $sqlObjectId.`lang` IS NULL) ";
$bindvars[] = $langs[0];
}
}
public function translateLinksInPageContent($src_content, $targ_lang)
{
$targ_content = $src_content;
$regex_link = '/\(\(([^\)]*?)(\|[^\)]*)*\)\)/';
preg_match_all($regex_link, $src_content, $src_link_matches, PREG_SET_ORDER);
$callback =
function ($match) use ($targ_lang) {
$link_src_page = $match[1];
$link_targ_page = $this->getTranslation('wiki page', $link_src_page, $targ_lang);
if (isset($link_targ_page) && $link_targ_page != '') {
$anchor_text = "";
if (count($match) > 2) {
$anchor_text = $match[2];
}
$a_targ_link = "(($link_targ_page$anchor_text))";
} else {
$a_targ_link = "{TranslationOf(orig_page=\"$link_src_page\" translation_lang=\"$targ_lang\" translation_page=\"\") /}";
}
return $a_targ_link;
};
$targ_content = preg_replace_callback($regex_link, $callback, $src_content);
return $targ_content;
}
/**
* Fetches the content of $source_page, and does some partial pretranslation
* of it into $target_lang.
*
* For now, pre-translation is limited to translating links to pages, but
* eventually, we could pretranslate standard terminology captured with
* Tiki's Collaborative Multilingual Terminology profile.
*/
public function partiallyPretranslateContentOfPage($source_page, $target_lang)
{
global $tikilib, $tracer;
$orig_page_info = $tikilib->get_page_info($source_page);
$orig_page_content = $orig_page_info['data'];
$pretranslated_content = $this->translateLinksInPageContent($orig_page_content, $target_lang);
return $pretranslated_content;
}
/**
* Determines which language should be used by default for a new translation of a page
* See test MultilingualLibTest.test_defaultTargetLanguageForNewTranslation() for
* examples of use.
*/
public function defaultTargetLanguageForNewTranslation($src_lang, $langs_already_translated, $user_langs)
{
global $tracer;
$default_lang = '';
//make sure $user_langs is an array
if (isset($user_langs) && is_array($user_langs)) {
$user_langs = $user_langs;
} else {
$user_langs = $this->preferredLangs(null, null);
}
foreach ($user_langs as $a_user_lang) {
if ($a_user_lang == $src_lang) {
continue;
}
$tracer->trace('MultilingualLib.defaultTargetLanguageForNewTranslation', "** Looking at \$a_user_lang=$a_user_lang");
if (! in_array($a_user_lang, $langs_already_translated)) {
$default_lang = $a_user_lang;
break;
}
}
return $default_lang;
}
public function setupBiDi()
{
global $prefs;
// Some languages need BiDi support. Add their code names here ...
if (Language::isRTL()) {
TikiLib::lib('header')->add_cssfile('vendor_bundled/vendor/hesammousavi/bootstrap-v4-rtl/bootstrap-rtl.min.css', 99); // 99 is high rank order as it should load after all other css files
} else {
TikiLib::lib('header')->drop_cssfile('vendor_bundled/vendor/hesammousavi/bootstrap-v4-rtl/bootstrap-rtl.min.css');
}
}
}