summaryrefslogblamecommitdiffstats
path: root/modules/translation.inc.php
blob: fc04fc52af16d504c00aa5e9a80967324f2275ea (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13

     
                                   
 
 







                                                                        
                                  

                                
                                
                              
 

                                         
                             
 



                                                           
 








                                                                                                               

                                                           

         

                                     
                                                        
 
                                                       

                                        




                                                                               
                                   
                                      





















                                                                                                      




                                                                                  
                                                              






                                                                                               
                                                                     

                                                                                       
                                   



                                                                                       
                 
         
 



                                                                                     
                                            
         
                                 
 

                                                
                                                        
 
                                                          

                                                                       
                                                                 
                                         
                                         







                                                                                                              
                                         


                                                            

                          

                              
         
 



                                                        

                                        
                                 

                                                                                               
                                                        




                                                                                 
                         
                 
                              
         
 
           
















                                                                                               


                                               
                                                             
                                                                                                         
           
                                                               
         
                                                          
                             
                                                     
                 

                                                                





                                                           

                                                                            
                                                   



                                                             
                         
                 

                                                 

                                                    

                                         






                                                                                 
         
 
           


                                                                                                   
           
                                                     
         
                                          












                                                                                                         
                                             
                                                 

                                                                             

                                     
 
                                                                   
                                                                 
                                                                             
























































                                                                                                                                                  
                                






                                                                                        

                         
 
                                                                      
                                               
                                                                       

                                                 
                                                                     






                                                                                          
                         
                 


                                                                                                    

                                                                                    
                 
                                           
         
 





                                                                         
                                                   
         
                                                                            
                                      
                                        
 
                                                                                  

                                         
                                                   
                          
         
 

                                                                    
           






                                                        
                                                          
                                                    
                                                     
                                                                    
                                         
                                        
                                             









                                                                                

                                                                           
                         
                 



                                                                                                               
                                                  
                                                   




                                                                                                       

                                                                                                                                            
                                                                                
                                                                         
                                                                      
                                       

                         
                                                    
         
 
           




                                                           

                                               


                                                                                                               
                                                         
                                                        
                                               
                                                                   
                                           
                                                                                                                                       
                                                                                  
                                                


                                                                           


                                                   
 
 
<?php

class Page_Translation extends Page
{

	/**
	 * The pages where you can administrate the website translations
	 * @var string|boolean holds the target template
	 * @var string|boolean used to choose which page to load
	 * @var string|boolean used check if there should be an update
	 * @var string|boolean used check if there should be a deletion
	 * @var array|boolean holds the tags of the selected template
	 */
	private $template = false;
	private $page = false;
	private $update = false;
	private $delete = false;
	private $tags = false;

	protected function doPreprocess()
	{
		User::load();

		if (!User::hasPermission('superadmin')) {
			Message::addError('no-permission');
			Util::redirect('?do=Main');
		}

		if (Request::post('update')) {
			$this->updateJson();
			Util::redirect('?do=Translation');
		}
		if (Request::post('delete')) {
			$this->deleteTag(Request::post('path'), Request::post('delete'));
			Util::redirect('?do=Translation'); // TODO: Ajax post for delete so we stay on the page
		}

		$this->template = Request::get('template');
		$this->page = Request::get('page');
	}

	protected function doRender()
	{
		$langs = Dictionary::getLanguages(true);

		//load the page accordingly to the link
		switch ($this->page) {
			case 'messages':
				//renders the message edit page
				Render::addTemplate('translation/edit', array(
					'path' => 'messages',
					'langs' => $langs,
					'tags' => $this->loadMessageEditArray()
				));
				break;
			case 'hardcoded':
				//renders the hardcoded messages edit page
				Render::addTemplate('translation/edit', array(
					'path' => 'messages-hardcoded',
					'langs' => $langs,
					'tags' => $this->loadHardcodedStringEditArray()
				));
				break;
			case 'settings':
				//renders the settings related edit page
				Render::addTemplate('translation/edit', array(
					'path' => 'settings/cat_setting',
					'langs' => $langs,
					'tags' => $this->buildTranslationTable('settings/cat_setting')
				));
				Render::addTemplate('translation/edit', array(
					'path' => 'settings/setting',
					'langs' => $langs,
					'tags' => $this->buildTranslationTable('settings/setting')
				));
				break;
			case 'template':
				$this->template = Util::safePath($this->template);
				if ($this->template === false) {
					Message::addError('invalid-path');
					Util::redirect('?do=Translation');
				}
				//renders the tag edition page
				Render::addTemplate('translation/edit', array(
					'path' => 'templates/' . $this->template,
					'langs' => $langs,
					'tags' => $this->loadTemplateEditArray($this->template)
				));
				break;
			case 'templates':
				//renders the template selection page
				Render::addTemplate('translation/template-list', array(
					'table' => $this->loadTemplatesList(),
				));
				break;
			default:
				//renders main page with selection of what part to edit
				Render::addTemplate('translation/_page');
		}
	}

	/**
	 * Load the main table with all the website's templates and it's informations
	 * @return array with the templates' information
	 */
	private function loadTemplatesList()
	{
		$table = array();

		//loads every template
		$files = $this->listTemplates();
		$langs = Dictionary::getLanguages(true);

		//checks the JSON tags from every language
		foreach ($files as $file) {
			$tags = $this->loadTemplateTags($file['path']);
			// Don't list templates without lang tags
			if (empty($tags))
				continue;
			$msgs = '';
			foreach ($langs as $lang) {
				$msg = $this->checkJson($file['path'], $lang['cc'], $tags);
				if (!empty($msg))
					$msgs .= "<div><span class='badge'>{$lang['name']}:</span>$msg</div>";
			}
			if (empty($msgs))
				$msgs = 'OK';
			$table[] = array(
				'template' => $file['name'],
				'link' => $file['name'],
				'status' => $msgs
			);
		}
		sort($table);
		return $table;
	}

	/**
	 * Finds and returns all the website's templates
	 * @return array
	 */
	private function listTemplates()
	{
		$files = array();
		$dir = 'templates/';
		$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
		foreach ($objects as $name => $object) {
			if (substr($name, -5) === '.html') {
				$files[] = array(
					'path' => substr($name, 0, -5),
					'name' => substr($name, strlen($dir), -5)
				);
			}
		}
		return $files;
	}

	/**
	 * Finds and returns all PHP files of slxadmin
	 * @return array of all php files
	 */
	private function listPhp()
	{
		$php = array();
		$dir = '.';
		$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
		foreach ($objects as $name => $object) {
			if (substr($name, -4) === '.php') {
				$php[] = $name;
			}
		}
		return $php;
	}

	/**
	 * Checks the JSON tags from a template
	 * @param string the template's path
	 * @param string the selected language
	 * @param string tags that should be in the json file
	 * @return string|boolean the information about the JSON tags, false if template has no lang-tags
	 */
	private function checkJson($path, $lang, $expectedTags)
	{
		//if there was not a valid template's path
		if (!$path) {
			return "Translation missing";
		}
		// How many tags do we expect in the translation
		$htmlCount = count($expectedTags);

		//initialize the count variables
		$matchCount = 0;
		$unusedCount = 0;

		//loads the JSON tags and count the matches
		$json = Dictionary::getArray($path, $lang);
		//return print_r($json) . "\nvs\n" . print_r($expectedTags);
		foreach ($json as $key => $value) {
			if (!in_array($key, $expectedTags)) {
				$unusedCount++;
			} else if (!empty($value)) {
				$matchCount++;
			}
		}
		$diff = $htmlCount - $matchCount;

		if ($diff == 0 && $unusedCount == 0)
			return '';
		//build the return string
		$str = "";
		if ($diff > 0)
			$str .= $diff . " JSON tag(s) are missing";
		if ($diff > 0 && $unusedCount > 0)
			$str .= "<br>";
		if ($unusedCount > 0)
			$str .= $unusedCount . " JSON tag(s) are not being used";
		return $str;
	}

	/**
	 * Get array to pass to edit page with all the tags and translations for the given template
	 * @param string $path the template's path
	 * @return array the information about the JSON tags
	 */
	private function loadTemplateEditArray($path)
	{
		$path = "templates/$path";
		$tags = $this->loadTemplateTags($path);
		if ($tags === false)
			return false;
		return $this->buildTranslationTable($path, $tags);
	}

	/**
	 * Load array of tags used in given template.
	 * @param string $path the path of the template, relative to templates/, without .html extension.
	 * @return array all tags in template
	 */
	private function loadTemplateTags($path)
	{
		$templateFile = "$path.html";
		//checks if the template is valid
		if (!file_exists($templateFile)) {
			Message::addError('invalid-template', $templateFile);
			return false;
		}

		//finds every mustache tag within the html template
		$htmlTemplate = file_get_contents($templateFile);
		preg_match_all('/{{(lang_.*?)}}/s', $htmlTemplate, $matches);
		if (!isset($matches[1]) || !is_array($matches[1]))
			return array();
		return array_unique($matches[1]);
	}

	/**
	 * Load array of tags and translations of all messages
	 * @return array the information about the JSON tags
	 */
	private function loadMessageEditArray()
	{
		$tags = $this->loadTagsFromPhp('/Message\s*::\s*add\w+\s*\(\s*[\'"](.*?)[\'"]\s*[\)\,]/i');
		if ($tags === false)
			return false;
		return $this->buildTranslationTable('messages', $tags);
	}

	/**
	 * Load array of tags and translations of all strings found in the php files.
	 * @return array the information about the JSON tags
	 */
	private function loadHardcodedStringEditArray()
	{
		$tags = $this->loadTagsFromPhp('/Dictionary\s*::\s*translate\s*\(\s*[\'"](.*?)[\'"]\s*[\)\,]/i');
		if ($tags === false)
			return false;
		return $this->buildTranslationTable('messages-hardcoded', $tags);
	}

	/**
	 * Load array of tags used in all the php files, by given regexp. Capture group 1 should return
	 * the exact tag name.
	 * @param string $regexp regular expression matching all tags in capture group 1
	 * @return array of all tags found
	 */
	private function loadTagsFromPhp($regexp)
	{
		// Get all php files, so we can find all strings that need to be translated
		$php = $this->listPhp();
		$tags = array();
		// Now find all tags in all php files. Only works for literal usage, not something like $foo = 'bar'; Dictionary::translate($foo);
		foreach ($php as $file) {
			$content = @file_get_contents($file);
			if ($content === false || preg_match_all($regexp, $content, $out) < 1)
				continue;
			foreach ($out[1] as $id) {
				$tags[$id] = true;
			}
		}
		return array_keys($tags);
	}

	private function buildTranslationTable($path, $requiredTags = false)
	{
		// All languages
		$langArray = Dictionary::getLanguages();

		$tags = array();
		if ($requiredTags !== false) {
			foreach ($requiredTags as $tagName) {
				$tags[$tagName] = array('tag' => $tagName);
				foreach ($langArray as $lang) {
					$tags[$tagName]['langs'][$lang]['lang'] = $lang;
					$tags[$tagName]['missing'] = count($langArray);
				}
			}
		}

		//finds every JSON tag withing the JSON language files
		foreach ($langArray as $lang) {
			$jsonTags = Dictionary::getArray($path, $lang);
			if (!is_array($jsonTags))
				continue;
			foreach ($jsonTags as $tag => $translation) {
				$tags[$tag]['langs'][$lang]['translation'] = $translation;
				$tags[$tag]['langs'][$lang]['lang'] = $lang;
				$tags[$tag]['tag'] = $tag;
				if (!isset($tags[$tag]['missing']))
					$tags[$tag]['missing'] = 0;
				if (!empty($translation))
					$tags[$tag]['missing'] --;
			}
		}
		// Finally remove $lang from the keys so mustache will iterate over them via {{#..}}
		foreach ($tags as &$tag) {
			$tag['langs'] = array_values($tag['langs']);
			if ($requiredTags !== false)
				$tag['class'] = $this->getTagColor($tag['missing']);
		}
		return array_values($tags);
	}

	/**
	 * Change the color of the table line according to the tag status
	 * @param string the JSON's path
	 * @param string the selected tag
	 * @return string the css class of the line
	 */
	private function getTagColor($missingCount)
	{
		//return danger in case the tag is not found in the template
		if ($missingCount < 0)
			return 'danger';

		//return warning in case at least one of the tag's values is empty
		if ($missingCount > 0)
			return 'warning';
		//if it's ok don't change the class
		return '';
	}

	/**
	 * Updates a JSON file with it's new tags or/and tags values
	 */
	private function updateJson()
	{
		$langArray = Dictionary::getLanguages();
		foreach ($langArray as $lang) {
			$json[$lang] = array();
		}

		//find the tag requests to change the file
		foreach ($_POST as $key => $value) {
			$str = explode('#', $key, 3);
			if (count($str) !== 3 || $str[0] !== 'lang')
				continue;
			$lang = $str[1];
			$tag = trim($str[2]);
			if (!isset($json[$lang])) {
				Message::addWarning('i18n-invalid-lang', $lang);
				continue;
			}
			if (empty($tag)) {
				Message::addWarning('i18n-empty-tag');
				continue;
			}
			if ($tag !== 'newtag') {
				$json[$lang][$tag] = $value;
			} else {
				$json[$lang][$_REQUEST['newtag']] = $value;
			}
		}

		// JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
		require_once('inc/up_json_encode.php');

		//saves the new values on the file
		foreach ($json as $key => $array) {
			$path = Util::safePath('lang/' . $key . '/' . Request::post('path') . '.json');
			if ($path === false) {
				Message::addError('invalid-path');
				Util::redirect('?do=Translation');
			}
			ksort($array); // Sort by key, so the diff on the output is cleaner
			$json = up_json_encode($array, JSON_PRETTY_PRINT); // Also for better diffability of the json files, we pretty print
			//exits the function in case the action was unsuccessful
			if (@file_put_contents($path, $json) === false) {
				Message::addError('invalid-template');
				return;
			}
		}
		Message::addSuccess('updated-tags');
	}

	/**
	 * Delete a specific JSON tag from a JSON files
	 * @var string the JSON's file path
	 * @var the JSON tag to be deleted
	 * @return boolean if the action was not successful
	 */
	private function deleteTag($path, $tag)
	{
		// JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
		require_once('inc/up_json_encode.php');

		//delete the tag from every language file
		$langArray = Dictionary::getLanguages();
		foreach ($langArray as $lang) {
			$json = Dictionary::getArray($path, $lang);
			unset($json[$tag]);
			$result = file_put_contents('lang/' . $lang . '/' . $path . '.json', up_json_encode($json, JSON_PRETTY_PRINT));
			//add warning and exit in case the action was unsuccessful
			if ($result === false) {
				Message::addWarning('unsuccessful-action');
				return false;
			}
		}
		Message::addSuccess('deleted-tag');
	}

}